Diffs
The file changes a chat reports, highlighted with @pierre/diffs. The thread draws them under a tool call, in the changed files of a turn and in an approval; they are exported for a review pane of your own. They draw what the host reported and write nothing.
import UnifiedDiff from '@adecore/agents-react/chat/ui/UnifiedDiff';
import EditDiff from '@adecore/agents-react/chat/ui/EditDiff';
import DiffPool from '@adecore/agents-react/chat/ui/DiffPool';All three are default exports.
import { useState } from 'react';
import { Segmented } from '@adecore/ui';
import type { ChatFileChange } from '@adecore/agent-contracts';
import DiffPool from '@adecore/agents-react/chat/ui/DiffPool';
import EditDiff from '@adecore/agents-react/chat/ui/EditDiff';
import UnifiedDiff from '@adecore/agents-react/chat/ui/UnifiedDiff';
import DiffWorker from '@pierre/diffs/worker/worker.js?worker';
const PATCH: ChatFileChange = {
path: 'src/station.ts',
kind: 'update',
diff: `@@ -29,4 +29,8 @@ export class Station {
async refresh(): Promise<void> {
- this.reading = await fetchJson<Reading>(this.url);
+ try {
+ this.reading = await fetchJson<Reading>(this.url);
+ } catch (error) {
+ console.warn('Kept the last reading', error);
+ }
this.draw();
}
`
};
const EDIT = {
path: 'src/http.ts',
before: 'const response = await fetch(url);\nif (!response.ok) {\n throw new Error(response.statusText);\n}',
after: 'const response = await fetch(url);\nif (!response.ok && attempt === attempts) {\n throw new Error(response.statusText);\n}'
};
export default function DiffsDemo() {
const [style, setStyle] = useState<'unified' | 'split'>('unified');
return (
<DiffPool workerFactory={() => new DiffWorker()}>
<div className="flex w-full flex-col gap-4">
<Segmented<'unified' | 'split'>
value={style}
label="Layout"
options={[
{ id: 'unified', label: 'Unified' },
{ id: 'split', label: 'Split' }
]}
onValueChange={setStyle}
/>
<div className="overflow-hidden rounded-lg border border-border">
<UnifiedDiff change={PATCH} diffStyle={style} />
</div>
<div className="overflow-hidden rounded-lg border border-border">
<EditDiff change={EDIT} />
</div>
</div>
</DiffPool>
);
}UnifiedDiff
A unified diff as a CLI that writes patches reports it, in a ChatFileChange. A diff with only hunks gets a file header from the path; a diff without hunks shows its lines as they are.
| Prop | Type | |
|---|---|---|
change | ChatFileChange | path, kind and diff. |
diffStyle | 'unified' | 'split' | Stacked or side by side. unified by default. |
overflow | 'wrap' | 'scroll' | wrap by default. |
fill | boolean | Grow to the height of a flex column around it. |
contents | { old, new } | Both whole texts. With them a person can unfold the lines between hunks (fullFileDiff). |
EditDiff
The text before and after one edit, for a CLI that reports edits that way: change is { path, before, after }, a FileChange.
DiffPool
Highlighting runs in two workers that every diff under the pool shares. How a worker is made is the bundler's business, so the app hands a factory. With Vite:
import DiffWorker from '@pierre/diffs/worker/worker.js?worker';
<DiffPool workerFactory={() => new DiffWorker()}>{chat}</DiffPool>;The demo above runs under such a pool. Without one the diffs highlight on the page's own thread. The pool follows the theme the host's code names; useDiffTheme() answers it.
Changes of a turn
ChatActions.turnDiff(chatId, turnId) reads what the working tree holds against the checkpoint a turn started from, or null without one. The helpers in chat/logic/tools read changes out of tool calls: fileChanges(name, input) for an edit's before and after, unifiedChanges(tool) for a patch, approvalChanges(input) for the copy an approval carries, and hasFileChanges(tool).