feat(runtime): Edit/Write/FormatJson emit file_diff results for both renderers - #2264
Merged
Conversation
Edit, Write, and FormatJson now carry an optional unified diff on their worker results, computed where both contents are already in memory. Write reads the previous content before overwriting so an overwrite shows what was lost; a new file renders as pure additions. Oversized or binary-ish content degrades to no diff, preserving the existing summary fallback.
…tent The three writing tools now return the canonical file_diff result kind when the executor reports a diff, so both renderers (desktop FileDiffPreview and the TUI diff view) finally receive real data. Write degrades to the existing file_write descriptor when no diff is available. The model gets a bounded one-line summary via toModelOutput — path plus addition/deletion counts — instead of the full diff or the old anonymous json blob.
ToolTrow counts +/- lines from a file_diff result and passes them to the Astryx ChatToolCalls row, which already knew how to paint green +N and red -N but never received them.
The ---/+++ file markers and index hashes repeat what the diff panel's heading and the tool card already say, so both renderers now drop them visually; the @@ hunk header stays as the only line-number anchor, and diff --git stays as the in-body boundary between files. The durable result and the desktop copyable body keep the full standard unified diff.
A pure-additions write painted a red -0 next to its green +N; zero counts now stay unpainted.
Covers the states #2232 makes reachable in the product: an Edit row, a new-file Write (pure additions), and an overwrite Write, as collapsed rows with +/- stats and as expanded diff panels.
Each rendered diff line now carries its line number in a left gutter — old-side for deletions, new-side for additions and context — counted from the @@ hunk headers, which no longer render as rows of their own; a jump in the numbering is what marks a hunk boundary. A foreign diff with no hunk headers degrades to unnumbered rows, and the copyable body still holds the full standard unified diff.
Parsing driven by the declared hunk counts, not per-line prefix heuristics: inside a hunk body the first character is the marker, so a deletion of a SQL -- comment (--- a in the diff) or an added ++i (+++i) can no longer be mistaken for a file header — the bug class that hid real change lines, desynced line numbers, and undercounted +/- stats across three heuristic copies.
… replay The durable ledger keeps the full diff for the UI, but every model re-read of history — prior turns, compaction, resume — materializes the stored result, so after the first editing turn the model received the entire diff as JSON on each request despite seeing a bounded summary live. Project file_diff/file_write results to the same one-line summary at the replay plan seam, following the projectBashToolResultForModel precedent. The summary counting now comes from the shared structural parse in @maka/core, so an added ++i line is counted rather than skipped as a false header.
…erwrites Read-before-write treated every read failure as a missing file, so a write-only file (or an image on the executor path) overwritten by Write was reported as --- /dev/null with the model told 'Created <path>'. Only ENOENT/ENOTDIR now means new file; any other unreadable or binary previous state yields no diff.
The chat diff panel and row badges now consume parseUnifiedDiffRows / countDiffLineStats from @maka/core, deleting the local heuristic parser and counter. Hunk-body classification can no longer hide a deleted -- -prefixed line as a file header or desync the gutter after one, and zero-count badges stay unpainted. A file_write result renders as 'Wrote N bytes to <path>' instead of a bare [file_write] tag.
…rser tui-diff shrinks to coloring over parseUnifiedDiffRows; the transcript's expanded diff drops file headers as a parse result rather than a separate heuristic filter, and compactDiffSummary counts via countDiffLineStats. The CLI's bare-prefix classifier (which dimmed a deleted YAML --- line as a header) is gone.
…ation The edit fixture's hunk header now matches its own body (7/8) and no longer shows a removal line that never existed in the repo; the two other fixtures were already consistent. EditWriteDiffDetails names its real path, fixing the story-annotations CI check.
Astro-Han
marked this pull request as ready for review
August 5, 2026 17:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2232.
Edit,Write, andFormatJsonnow produce a unified diff at the write site — where both file contents are already in memory — and return it as the canonicalfile_difftool-result kind. That kind has had consumers on every surface (desktopFileDiffPreview, TUIrenderDiffResult/compactDiffSummary, storage schema) but no producer outside fixtures; this PR is the producer, which also makes the #2227 line-level diff rendering reachable in the product.Decisions (from the issue's open questions):
toModelOutputreturns one line live (Edited a.ts (+1 -1),Created new.md (+2)), and the same summary is what the model sees on every later re-read: a replay-plan projection (theprojectBashToolResultForModelprecedent) keeps prior turns, compaction, and resume from materializing the stored full diff into the prompt.Writeon a new file renders the whole content as additions under--- /dev/null; overwrites read the previous content first (inside the per-file write lock). OnlyENOENT/ENOTDIRcounts as "new file" — an unreadable or binary previous state yields no diff rather than a false "Created".Writethen falls back to afile_writedescriptor (Wrote N bytes to <path>on every surface).FormatJsonis covered by the same seam and never leaks its diff through the stripped diagnostic json.Rendering, on top of the raw diff becoming reachable:
+N/-Nbadges, TUI transcript, model summaries — goes through one structural parser in@maka/core, driven by the declared hunk counts: inside a hunk body the first character is the marker, so a deleted SQL-- acomment (--- ain the diff) or an added++i(+++i) can no longer be hidden as a file header, desync line numbers, or escape the +/- counts. This replaced three divergent heuristic classifiers.@@as its line-number anchor.Product/Tool Activity → EditWriteDiffRows/EditWriteDiffDetails) cover the newly reachable states with self-consistent fixtures.Deferred from review:
matchedViais no longer surfaced to the model when a diff exists (thefile_diffcontract is exact-shape; fuzzy matches are rare and unambiguous; the no-diff fallback path still returns it). Headless keeps its own result shapes (separate harness contract, no rendering surface).Verification
file_diffmapping,file_writefallback, no diff leaking as json, boundedtoModelOutputincl.++-prefixed content.file_diffmaterializes asEdited a.ts (+1 -1)for the model while the durable event keeps the full diff.--/++-prefixed content,\\ No newlinemarkers, multi-file/multi-hunk numbering, foreign-diff degradation, header/hunk exclusion from counts.-0suppression,file_writedescriptor; TUI expanded diff drops headers, keeps@@.EditWriteDiffRows/EditWriteDiffDetailsreviewed against the live Storybook dev server after the parser migration.