Welcome to the Diff project tutorial! We are going to explore how this tool visualizes code changes in a terminal.
We start at the very top: Diff Dialog Orchestration.
Imagine you are directing a play. You have actors (files), scripts (code changes), and different scenes (views). You can't show everything at once. You need a Stage Manager to decide:
Without a stage manager, the application would just be a chaotic pile of text.
In our project, the DiffDialog component acts as this Stage Manager. It is the central hub that holds the "Global State" of the diff viewer. It listens for user commands and swaps out the components on the screen accordingly.
'list' mode or 'detail' mode.Before looking at code, let's visualize the flow. The Orchestrator sits between the User and the Views.
DiffDialog updates its internal counters (like selectedIndex).
Let's look at how this is built. We will break the DiffDialog.tsx file down into small, understandable pieces.
The component needs to remember three specific things to orchestrate the show.
// DiffDialog.tsx
export function DiffDialog({ messages, onDone }: Props) {
// 1. Are we looking at a list of files or inside a specific file?
const [viewMode, setViewMode] = useState<ViewMode>('list');
// 2. Which file in the list is currently highlighted?
const [selectedIndex, setSelectedIndex] = useState<number>(0);
// 3. Which version of the history are we looking at?
const [sourceIndex, setSourceIndex] = useState<number>(0);
// ...
}
viewMode: This switches the "Scene".selectedIndex: This moves the "Spotlight" up and down.sourceIndex: This controls the "Time Machine" (current changes vs. past edits).
The Orchestrator gathers data from two places: current Git changes and past conversation history (turns). It combines them into a list of sources.
To ensure the data format is consistent regardless of where it comes from, we use a helper concept called the Data Normalization Adapter.
// Combine current changes and history into one list of sources
const sources: DiffSource[] = useMemo(
() => [
{ type: 'current' }, // The live changes on disk
...turnDiffs.map((turn) => ({ type: 'turn', turn })),
],
[turnDiffs],
);
// Pick the data based on which "Tab" (sourceIndex) is active
const currentSource = sources[sourceIndex];
This is where the Orchestrator directs the show. It maps specific keys to state changes.
Notice how the logic changes depending on the viewMode.
Enter opens details.Back (or Esc) returns to the list. useKeybindings({
'diff:viewDetails': () => {
// If we are in the list, zoom in!
if (viewMode === 'list' && selectedFile) {
setViewMode('detail');
}
},
'diff:back': () => {
// If we are in details, zoom out!
if (viewMode === 'detail') {
setViewMode('list');
}
},
// ... other bindings for arrows
}, { context: 'DiffDialog' });
Finally, the component decides what to draw on the screen. This is a classic "Conditional Rendering" pattern.
return (
<Dialog title={title} onCancel={handleCancel}>
{/* 1. Show the tabs (Current vs History) */}
{sourceSelector}
{/* 2. Decide which view to show */}
{viewMode === 'list' ? (
<DiffFileList files={diffData.files} selectedIndex={selectedIndex} />
) : (
<DiffDetailView
filePath={selectedFile?.path || ''}
hunks={selectedHunks}
/>
)}
</Dialog>
);
viewMode is 'list', it renders the File List. You will learn how that works in Paginated File List.viewMode is 'detail', it renders the Detail View. You will learn about that in Detail View & Hunk Rendering.Here is exactly what happens when a user wants to view a file's changes.
The Diff Dialog Orchestration is the glue that holds the feature together.
It relies on helper components to format the data and display the actual content.
In the next chapter, we will look at how we take raw data from Git or the Chat history and convert it into a format this Orchestrator can understand.
Next Chapter: Data Normalization Adapter
Generated by Code IQ