Diff Review
Prüfe die Änderungen eines Agenten über mehrere Dateien, bevor sie übernommen werden. Ein Dateibaum mit Zählern, jede Änderung, jede Datei oder alles annehmen oder ablehnen, Kommentare zu jeder Zeile oder jedem Bereich, die an den Agenten zurückgehen, Unified- und Split-Ansicht, Hervorhebung auf Wortebene, Rückgängig, streamende Änderungen und eine Zusammenfassung „4 Dateien bearbeitet“ für den Chat.
Coding-Agenten ändern mehrere Dateien auf einmal, und Leute müssen einen Teil behalten und den Rest verwerfen. Diff Review legt jede Änderung in eine scrollende Liste mit einem Dateibaum daneben, sodass du eine einzelne Änderung, eine ganze Datei oder alles per Maus oder Tastatur annehmen oder ablehnen kannst. DiffSummary ist die Card „4 Dateien bearbeitet +120 −34“ für den Chat, mit denselben Aktionen für jede Datei.
Jede Entscheidung betrifft genau eine Änderung. Eine entschiedene Änderung klappt zu einem einzeiligen Stub mit ihrem Ergebnis, Show und Undo zusammen, und eine Datei klappt weg, sobald jede Änderung darin entschieden ist. Sammelaktionen sagen, wie viele Änderungen sie abdecken, und jede Entscheidung, auch Accept all und Reject all, lässt sich mit U oder ⌘Z rückgängig machen. „Next“ meint immer die nächste Änderung, die du noch nicht entschieden hast. Die Zähler und der Fortschrittsbalken stammen aus dem, was noch aussteht, sodass das Review mit einem klaren „All reviewed“ endet.
Fahre über eine Zeile und drücke + in der Rinne, oder fokussiere eine Zeile und drücke Enter oder C, um sie zu kommentieren. Ziehe oder klicke mit Shift auf Zeilennummern, oder nutze Shift+↑↓, um einen Bereich zu kommentieren. Der Composer öffnet sich als kleines Popover unter der Zeile, und ein gesendeter Kommentar bleibt als Thread an der Zeile, den du bearbeiten, mit Undo löschen oder einklappen kannst, mit einer Zahl in der Rinne. Entfernte Zeilen werden auf der alten Seite gemeldet, hinzugefügte oder unveränderte Zeilen auf der neuen, sodass die Zeilennummern immer zur Datei passen.
Übergib einen Unified Diff oder den Inhalt davor und danach und lass den Block den Diff berechnen. Mit dem vollständigen Inhalt kannst du die unveränderten Zeilen zwischen Änderungen anzeigen, je 20 nach oben oder unten, oder alle auf einmal, wenn weniger als 20 verborgen sind. Entfernte Zeilen tragen in der Rinne einen feinen gestreiften Balken, hinzugefügte einen durchgehenden, sodass der Unterschied nie von Farbe abhängt. Zeilen werden wie bei Code Block mit Shiki hervorgehoben, und geänderte Wörter innerhalb einer Zeile werden markiert, wenn sich die beiden Zeilen ähnlich genug sind, dass es hilft. Das Layout wechselt zur Split-Ansicht, wenn Platz ist, standardmäßig ab mindestens 900 px. Dateien, die noch streamen, werden live angezeigt und können noch nicht entschieden werden. Dateien, die sich auf der Platte geändert haben, können abgelehnt, aber nicht angenommen werden. Diffs mit über 400 geänderten Zeilen warten hinter Load diff, und ihre Aktionen auf Dateiebene funktionieren weiter.
Die Pro-Registry zu components.json hinzufügen
components.json Token hinzufügen
Erstelle auf deiner Kontoseite einen Token und trage ihn in
.env.localalsHEXTAUI_PRO_TOKENein.Den Block hinzufügen
pnpm dlx shadcn@latest add @hextaui-pro/diff-review
Mit dem AI SDK
Mache aus Edit-Tool-Aufrufen Dateien, auch bei Eingaben, die noch streamen: ein Unified Diff, der Inhalt einer neuen Datei oder old_string und new_string von Claude Code. Die Zusammenfassung kommt in den Chat und das Review daneben, beide teilen sich einen Provider.
Zeilenkommentare an den Agenten senden
onComment erhält die Datei, die Seite, die Zeilennummern und einen Diff-Ausschnitt, sodass du ihn als Folgenachricht senden kannst. Gib das Promise von sendMessage zurück, und jeder Kommentar zeigt Sending… und danach Sent to the agent.
Das Angenommene schreiben
applyDecisions baut jede Datei aus den behaltenen Hunks neu auf, anhand des ursprünglichen oder bearbeiteten Inhalts. Es gibt außerdem einen Patch nur mit den angenommenen Hunks zurück und sagt, wenn eine Datei gelöscht oder nie erstellt werden soll.
Ein schreibgeschützter Diff
DiffView zeigt die Änderungen einer Datei mit derselben Hervorhebung, denselben Rinnenbalken und derselben Erweiterung unveränderter Zeilen, ohne Review-Steuerelemente. Es wächst mit seinem Inhalt, setze es also in ein scrollendes Parent.
Aus einem Git-Diff
parsePatch zerlegt die Ausgabe von git diff oder einen einfachen Unified Diff in Dateien, mit umbenannten, neuen, gelöschten und Binärdateien.
Aufbau
Die Teile, die du zusammensetzt, von außen nach innen.
| Part | Beschreibung |
|---|---|
DiffReviewProvider | Hält die Dateien, die Entscheidungen und den Undo-Verlauf, sodass eine Zusammenfassung im Chat und ein Review-Panel synchron bleiben. |
DiffReview | Die Review-Oberfläche: Toolbar, Dateibaum, die Änderungen und die Review-Leiste mit Fortschritt, Navigation und Sammelaktionen. |
DiffSummary | Eine Card für den Chat: „4 Dateien bearbeitet“ mit Zählern, jede Datei mit Annehmen und Ablehnen, und Sammelaktionen. |
DiffView | Ein schreibgeschützter Diff einer Datei, um Änderungen zwischen Versionen zu zeigen. |
Comment threads | Kommentare unter einer Zeile oder einem Bereich, mit Autor, Zeit, Zustellstatus, Bearbeiten, Löschen und Einklappen. |
parsePatch, applyDecisions | Liest die Ausgabe von git diff in Dateien ein und macht aus Entscheidungen wieder Dateiinhalte und einen Patch nur mit den angenommenen Änderungen. |
useDiffReview | Der gemeinsame State, um innerhalb des Providers eigene Steuerelemente zu bauen. |
DiffReviewProvider
DiffReview und DiffSummary nehmen dieselben Props, wenn sie allein verwendet werden.
| Prop | Typ | Standard |
|---|---|---|
filesDie geänderten Dateien, in der Reihenfolge, in der der Agent sie bearbeitet hat. | DiffFile[] | – |
decisionsKontrollierte Entscheidungen nach Änderungs-ID. Änderungen ohne Eintrag stehen aus. | Record<string, "accepted" | "rejected"> | – |
defaultDecisionsAnfangsentscheidungen im unkontrollierten Modus. | Record<string, "accepted" | "rejected"> | – |
onDecisionsChangeWird nach jeder Aktion, auch Undo, mit den nächsten Entscheidungen aufgerufen. | (decisions) => void | – |
onDecideWird einmal pro Aktion mit der Entscheidung, ihrer Quelle (change, file, all oder undo) und den betroffenen Änderungen aufgerufen. Nutze es, um Dateien zu schreiben oder zurückzusetzen. | (event: DiffDecideEvent) => void | – |
commentsKontrollierte Kommentare. | DiffComment[] | – |
defaultCommentsAnfangskommentare im unkontrollierten Modus. | DiffComment[] | – |
onCommentsChangeWird aufgerufen, nachdem ein Kommentar hinzugefügt, bearbeitet, gelöscht oder wiederhergestellt wurde. | (comments: DiffComment[]) => void | – |
onCommentWird aufgerufen, wenn ein Kommentar gesendet wird, und erneut mit derselben ID, wenn er bearbeitet wird. Gib ein Promise zurück, um Sending… und Sent to the agent zu zeigen, oder Couldn’t send mit Retry, falls es abgelehnt wird. | (comment: DiffComment) => unknown | – |
authorWird bei neuen Kommentaren angezeigt. | { name: string; image?: string } | { name: "You" } |
| Prop | Typ | Standard |
|---|---|---|
pathDer Pfad der Datei. Bearbeitet ein Agent denselben Pfad zweimal, werden beide Änderungen aufgelistet. | string | – |
patchEin Unified Diff dieser Datei. Hunk-Header ohne Zeilennummern, CRLF und „No newline at end of file“ sind in Ordnung. | string | – |
beforeDer ursprüngliche Inhalt. Mit after wird der Diff für dich berechnet, und unveränderte Zeilen können angezeigt werden. | string | – |
afterDer bearbeitete Inhalt. Allein beschreibt er eine neue Datei. | string | – |
oldPathDer frühere Pfad einer umbenannten Datei. | string | – |
statusWird aus dem Inhalt ermittelt, wenn weggelassen. | "added" | "deleted" | "modified" | "renamed" | – |
languageShiki-Sprach-ID. Standardmäßig die Dateiendung. | string | – |
binaryZeigt „Binary file not shown“ und entscheidet die Datei als Ganzes. | boolean | – |
streamingDer Agent schreibt diese Datei noch. Sie aktualisiert sich live und kann noch nicht entschieden werden. | boolean | – |
staleDie Datei hat sich nach der Änderung auf der Platte geändert. Annehmen ist deaktiviert, bis du einen aktuellen Diff übergibst. | boolean | – |
| Prop | Typ | Standard |
|---|---|---|
viewKontrolliertes Layout. Auto ist Split, wenn der Diff-Bereich mindestens 900 px breit ist. Split fällt unter 600 px auf Unified zurück. | "auto" | "unified" | "split" | – |
defaultViewAnfangslayout im unkontrollierten Modus. | "auto" | "unified" | "split" | "auto" |
onViewChangeWird aufgerufen, wenn jemand ein Layout wählt. | (view: "unified" | "split") => void | – |
advanceSpringt nach einer Entscheidung per Tastatur zur nächsten Änderung, die noch aussteht. | boolean | true |
largeDiffLinesDateien mit mehr geänderten Zeilen als diesem Wert warten hinter Load diff. | number | 400 |
classNameGib ihm eine Höhe oder setze es in eine Flex-Spalte. Die Liste scrollt darin. | string | – |
| Prop | Typ | Standard |
|---|---|---|
onReviewZeigt Review und lässt jede Datei das Review an dieser Datei öffnen. | (path?: string) => void | – |
foldAfterFaltet längere Listen hinter „Show 3 more files“. | number | 6 |
| Prop | Typ | Standard |
|---|---|---|
pathDie Datei, auf die sich der Kommentar bezieht. | string | – |
sideDie Seite der letzten Zeile: old für eine entfernte Zeile, new für eine hinzugefügte oder unveränderte. | "old" | "new" | – |
startSideDie Seite der ersten Zeile. Sie unterscheidet sich von side, wenn ein Bereich von entfernten in hinzugefügte Zeilen reicht. | "old" | "new" | – |
linesZeilennummern in der Datei auf startSide und side. | { start: number; end: number } | – |
excerptDie gewählten Zeilen in Diff-Form, etwa „-old“ und „+new“, damit der Agent den Code auch dann sieht, wenn sich Zeilennummern verschieben. | string | – |
textWas der Leser geschrieben hat. | string | – |
id, createdAt, authorWird beim Erstellen des Kommentars ausgefüllt. | string, number, { name; image? } | – |
DiffView
Aus diff-view.tsx exportiert. Es hat keinen eigenen Scroll-Container: Es wächst mit seinem Inhalt, und seine Änderungs-Header haften am nächsten scrollenden Parent, setze es also in einen, etwa ein Element mit min-h-0 flex-1 overflow-y-auto.
| Prop | Typ | Standard |
|---|---|---|
beforeDer frühere Inhalt. | string | – |
afterDer spätere Inhalt. | string | – |
patchEin Unified Diff, statt before und after. | string | – |
languageShiki-Sprach-ID. Standardmäßig die Endung von path. | string | – |
pathWird für die Sprache und den optionalen Header verwendet. | string | "file" |
viewSplit fällt unter 600 px auf Unified zurück. | "unified" | "split" | "unified" |
wrapBricht lange Zeilen um. Split bricht immer um. | boolean | false |
headerZeigt den Pfad und die +N-/−N-Zähler über den Änderungen. | boolean | false |
classNameKlassen für das Root. | string | – |
| Prop | Typ | Standard |
|---|---|---|
decisionPending, wenn eine Entscheidung rückgängig gemacht wurde. | "accepted" | "rejected" | "pending" | – |
sourceWas die Person getan hat. | "change" | "file" | "all" | "undo" | – |
changesNur die Änderungen, deren Entscheidung sich tatsächlich geändert hat. | { file: string; path: string; id: string }[] | – |
applyDecisions(file, decisions, options?)
Gibt { path, decision, content?, deleted, patch } zurück. Ausstehende Änderungen zählen als abgelehnt, außer options.pending ist "accepted".
| Prop | Typ | Standard |
|---|---|---|
contentDie Datei nach dem Review, aus before oder after aufgebaut. Undefined bei Binärdateien oder wenn keines von beidem bekannt ist. | string | undefined | – |
deletedTrue, wenn die Datei nicht existieren soll: eine angenommene Löschung oder eine abgelehnte neue Datei. | boolean | – |
patchNur die angenommenen Hunks, neu nummeriert, sodass git apply auf der Originaldatei funktioniert. | string | – |
| Taste | Aktion |
|---|---|
| J | Springt zur nächsten Änderung, die noch aussteht. K geht zurück. |
| N | Springt zur nächsten Datei. P springt zur vorherigen. |
| Y | Nimmt die aktuelle Änderung an. ⌘↵ tut dasselbe. |
| X | Lehnt die aktuelle Änderung ab. ⌘⌫ tut dasselbe. |
| ⇧Y | Nimmt jede ausstehende Änderung in der aktuellen Datei an. ⇧X lehnt sie ab. |
| ⌘⇧↵ | Nimmt jede bereite Änderung an. ⌘⇧⌫ lehnt sie ab. |
| U | Macht die letzte Entscheidung rückgängig, auch Sammelentscheidungen. ⌘Z tut dasselbe. |
| ↑↓ | Wechselt durch den Dateibaum. Enter springt zur Datei, ← und → falten Ordner, und Tippen springt nach Name zu einer Datei. |
| Tab | Springt in die Zeilen einer Änderung. Jede Änderung ist ein Stopp, und ↑ ↓ Pos1 Ende wechseln zwischen ihren Zeilen; in der Split-Ansicht wechseln ← → die Seiten. |
| ⇧↓ | Wählt einen Zeilenbereich. ⇧↑ erweitert ihn nach oben, und Esc hebt ihn auf. |
| Enter | Öffnet den Kommentar-Composer für die fokussierte Zeile oder den gewählten Bereich. C tut dasselbe. |
| ⌘↵ | Sendet den Kommentar aus dem Composer. Esc schließt ihn und behält, was du für diese Zeile geschrieben hast. |
- Das Review ist eine Region namens „Review changes“. Jede Änderung ist eine Gruppe mit vollständigem Namen, etwa „Änderung 2 von 9, app/page.tsx, Zeilen 40–52, 3 Zeilen hinzugefügt, 1 Zeile entfernt, ausstehend“.
- Entscheidungen, Sammelaktionen und Undo werden über eine höfliche Live-Region mit dem Rest angesagt, zum Beispiel „Änderung 2 von 9 in app/page.tsx angenommen. 7 Änderungen übrig.“
- Tastenkürzel funktionieren nur, solange der Fokus im Review liegt. In Textfeldern werden sie ignoriert, und Buchstaben im Dateibaum springen nach Name zu Dateien. Nach einer Entscheidung per Tastatur springt der Fokus zur nächsten ausstehenden Änderung. Nach einem Klick springt er an derselben Stelle zum Undo-Button.
- Hinzugefügte und entfernte Zeilen werden als „Hinzugefügt:“ und „Entfernt:“ vorgelesen, sodass sie sich nicht auf Farbe verlassen. Geänderte Wörter sind im Hochkontrastmodus unterstrichen, und der Fortschritt wird zusätzlich als Progressbar bereitgestellt.
- Gefaltete Änderungen sind inert, werden also von Tab und Screenreadern übersprungen, bis du sie zeigst. Bei reduzierter Bewegung aktualisieren sich Falten, Zähler und Fortschritt ohne Animation.
- Die Zeilen jeder Änderung bilden einen Tab-Stopp mit Roving Focus, wie ein Grid. Die fokussierte Zeile zeigt eine Tönung und ein + in der Rinne, und der Forced-Colors-Modus zeichnet stattdessen eine Kontur.
- Beim Öffnen des Composers springt der Fokus hinein, und die Zeile bleibt hervorgehoben. Esc gibt den Fokus sichtbar an die Zeile zurück. Nach dem Senden oder Wegklicken geht der Fokus ohne Fokusanzeige zur Zeile zurück, sodass die nächste Pfeiltaste von dort weitermacht.
- Entfernte und hinzugefügte Zeilen tragen neben den Zeichen − und + einen gestreiften oder durchgehenden Rinnenbalken, und die Balken bleiben im Forced-Colors-Modus sichtbar.
- Hinzufügen, Bearbeiten, Löschen und Wiederherstellen von Kommentaren werden angesagt, und gelöschte Kommentare lassen sich einige Sekunden lang wiederherstellen.
Gebaut mit
Die kostenlosen HextaUI-Komponenten, aus denen Diff Review besteht. Jede lässt sich einzeln installieren.
Code
13 Dateien, hinzugefügt zu components/blocks/diff-review.