In the previous chapter, Diff Dialog Orchestration, we set up the "Stage Manager" that controls our application. However, a manager is useless without something to manage.
Our app needs to display code changes from two very different places:
Imagine you are traveling. Your laptop has a US plug, but the wall socket is European. You can't plug it in directly.
In our code:
DiffData.TurnDiff.The UI doesn't care where the data comes from; it just wants to know "What files changed?" and "What lines were added?".
We build a Data Normalization Adapter. It acts like a universal travel adapter. It takes the complex TurnDiff object, strips away the chat-specific context (like prompts and timestamps), and reformats it into the generic DiffData that our UI understands.
DiffDataThis is the "Socket" shape our UI is waiting for. It is simple and purely about file stats.
// The generic format our UI loves
type DiffData = {
files: Array<{ path: string; linesAdded: number; ... }>;
hunks: Map<string, Hunk[]>; // The actual code snippets
stats: {
filesCount: number;
linesAdded: number;
linesRemoved: number;
};
};
Before looking at the code, let's trace the data.
TurnDiff).DiffData.
We implement this in a function called turnDiffToDiffData. It resides inside DiffDialog.tsx because it's a helper specifically for bridging data to the dialog.
The history object stores files in a Map. We need to convert that into a simple array and sort them alphabetically so the user sees a clean list.
function turnDiffToDiffData(turn: TurnDiff): DiffData {
// Convert the Map of files into a simple Array
const files = Array.from(turn.files.values()).map(f => ({
path: f.filePath,
linesAdded: f.linesAdded,
linesRemoved: f.linesRemoved,
isNewFile: f.isNewFile,
// Set defaults for things history doesn't track
isBinary: false,
isLargeFile: false,
}));
// Sort alphabetically so the list is easy to scan
files.sort((a, b) => a.path.localeCompare(b.path));
// ... continued below
A "Hunk" is a technical term for a continuous block of code changes. The Detail View needs to look these up quickly by filename.
// Create a lookup map for code snippets (hunks)
const hunks = new Map<string, StructuredPatchHunk[]>();
for (const f of turn.files.values()) {
// Map the file path directly to its code changes
hunks.set(f.filePath, f.hunks);
}
Finally, we bundle the files and hunks with some high-level statistics (how many lines changed in total?) to return the final object.
return {
stats: {
filesCount: turn.stats.filesChanged,
linesAdded: turn.stats.linesAdded,
linesRemoved: turn.stats.linesRemoved,
},
files, // The array we created in Step 1
hunks, // The map we created in Step 2
loading: false,
};
}
Now, let's see how the Orchestrator (from Chapter 1) uses this tool.
Inside DiffDialog, we have a variable currentTurn. If currentTurn exists, it means we are looking at history. If it's null, we are looking at live Git changes.
// DiffDialog.tsx
const diffData = useMemo(() => {
// If we are looking at history (currentTurn exists)...
if (currentTurn) {
// ... USE THE ADAPTER!
return turnDiffToDiffData(currentTurn);
}
// Otherwise, use the live git data (which is already formatted)
return gitDiffData;
}, [currentTurn, gitDiffData]);
By using this pattern, the rest of our application (the file list and detail view) never needs to know if it is looking at the past or the present. It just receives diffData and renders it.
The Data Normalization Adapter allows us to decouple our data sources from our UI.
DiffData format.Now that we have our data normalized into a clean list of files, we need to display them to the user.
Next Chapter: Paginated File List
Generated by Code IQ