Conversation
Define the next NDJSON wire format as serde types in src/protocol.rs: one header, one file record per changed file, one footer. Each file carries both sources with per-side region trees paired across sides by id, replacing hunks, aligned rows, and separate fold lists. Sides are a presence-encoded Pairing; errors share one shape; syntax spans carry tree-sitter capture names. Not wired to the CLI yet. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Stats.structural is a Result so the structural-or-fallback rule cannot be violated; Outcome converts to and from Result; Region carries a SourceRange; Error is Problem to stay clear of std; Pairing gains map and as_ref; one is_default helper; Eq everywhere it holds; plain pub fields in a crate-private module. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Project DiffResult onto protocol::Event: aligned rows become leaves that tile each side, folds nest by containment with leaves split at their edges (mirrored across paired leaves), long unchanged runs collapse to the context width, and ids pair regions across sides. The manifest carries git's delta sides (oid, mode), status, category and language. Per-file failures are coded problems; a hook failure aborts the run. --syntax adds tree-sitter capture spans from the highlights query. The previous stream stays as --format ndjson-v1 while the frontends move. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
The TUI validates diffr's v2 protocol, zips each side's leaves on their ids into split and unified rows, colours tokens from --syntax capture names, and treats every region the same way: folds and context gaps collapse from one id set seeded by diffr's visibility, with multi-line labels hanging under a collapsed header. Files hidden by default show a Load diff placeholder. A settings screen over diffr config schema/show/ set opens from diffr config. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Configuration is one schema-bearing struct resolved by figment from the bundled defaults, the global file, the repository's diffr.toml, DIFFR_* variables and --set overrides. `diffr config schema|show|set` are the commands frontends drive; `diffr config [query]` opens the terminal settings screen. Doc comments become schema descriptions. Mutations run on the protocol types after projection, never on the diff internals: file mutations on the manifest, fold mutations on each file's region trees. Generated and test files start hidden, classified by git attributes then built-in path rules; deleted bodies start collapsed with a line count; new bodies of at least folds.min_lines lines are summarized as pseudocode by a built-in Gemini client over reqwest, silently off without a key, retried on transient failures and aborting the run otherwise. The JSON-RPC hook is now a fold mutation on the same stack, addresses folds by region id, and jsonrpsee is gone. JavaScript and TypeScript test callbacks carry the `test` fold tag. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
The launcher's fixtures live under test/, which the new category rules hide by default. The test exercises the viewer lifecycle, so pass --set folds.collapse_tests=false. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
…ed folds Context gaps now follow difftastic's hunks, so the syntax context it already selects (the enclosing function header) stays visible above a deep change, and only unchanged stretches of three or more lines that no hunk shows collapse. Folds that lie entirely inside a gap are dropped from the region tree instead of splitting it, and slivers a fold edge cuts off a gap stay open. A last fold mutation merges adjacent gaps where both sides agree and wraps runs of collapsed sibling folds (deleted or summarized bodies) in one collapsed group fold whose label counts them. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A new fold mutation, on by default at five lines, splits every lhs-only leaf into an open first line, a collapsed middle labelled with its line count and tagged removed, and an open last line, so a big red block reads as red without filling the screen. Leaves under an already collapsed fold are left alone. Configured by folds.collapse_removed_lines; 0 disables. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
An empty key resolves to no summarizer, so showing <redacted> for it misled the user into thinking a key was set. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Enter opens a key/value view with the description and default: booleans and enums pick from a list, numbers and strings use a text field that is masked for credential-looking keys. Enter saves through diffr config set and returns to the refreshed list; Esc returns. Both views carry footer hints, and empty values read as <unset>. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Lines inside a novel leaf that carry no change span, such as blank lines in a new block, now get the light line tint; the darker word tint stays on the spans. Tests assert the two backgrounds are distinct in both themes. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A large rewrite of src/hook.rs exceeded difftastic's 3M graph limit, so the streaming path fell back to a line diff with no folds, collapse rules, or summaries. A [diff] table now carries byte_limit, graph_limit and parse_error_limit, layered like every other key, with the DFT_* variables and the command-line flags still overriding it. The graph limit defaults to 30M, which keeps that file structural for about one extra second in a debug build. Fallback reasons name the key to raise. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Thirty million nodes across sixteen parallel files was too much memory. The limit stays configurable; large rewrites fall back to a line diff by default, and agent traces are the planned way around that. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
The domain tree is a tree of correlated ranges; the structural engine and the text diff are two producers of the line alignment, and folds are a layer over whichever one exists. When the AST match gives up (graph limit, parse-error limit) the parsed trees still yield folds and enclosing-header context, and the projection pairs folds on both engines with one rule: header lines paired in the alignment, tags equal. The parser's own fold pairing is no longer read. hook.rs goes from no folds to 34/51 folds with 17 pairs and 5 summarizable bodies. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Header lines pair when the alignment pairs them, changed or not, so a function with an edited signature stays paired. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Vendor four Helix themes (onedark and onelight as the defaults, gruvbox, solarized_light) under tui/themes with their MPL-2.0 notice, parse the TOML format with palette references and parent-scope fallback, and build the painter's palette from ui.* keys with change tints mixed from diff.plus and diff.minus. The theme comes from diffr config show at startup: theme.path if set, else theme.name; unknown names error. The t key toggles the two defaults. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Function definitions gain a `function` fold tag next to `body`, so the deleted-bodies rule and the summarizer act on real functions rather than every brace block, and the summarizer takes the outermost qualifying body only. `summarize.min_lines` (default 20) separates the summary threshold from the collapse one. Test bodies of three or more lines start collapsed on both sides and are never summarized. The prompt is one sentence of pseudocode instructions; a summary longer than half the body is discarded in code. Grouping no longer looks under a collapsed fold and labels a run of test bodies as such. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Syntax spans came only from the structural path, so a file that fell back to a line diff rendered uncoloured. The language is guessed from the path for fallbacks and both sides get spans when --syntax is set. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A strip under the menubar names the comparison, the file count, and the visible totals with GitHub's five-block bar. Headline counts are diffr's stats.visible adjusted as folds, gaps, and files toggle; the i key or a click on the totals opens a breakdown of visible, structural, and textual counts for the comparison and the current file. stats.visible is required on the wire. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
stats is textual, visible, and an optional fallback. The breakdown lists visible and textual and notes a line-diff fallback by code; the file header shows visible counts only. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
stats.visible counts the changed lines still on screen under the default visibility, computed after the mutations run so configuration and hooks change it. The matcher's own line count leaves the wire; stats.fallback alone says when the AST match did not run. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A Rust mod carrying #[cfg(test)] is tagged test (and module) so the whole module starts collapsed as one "test module" fold, with the test bodies still foldable inside. tests.rs, test.rs, *_test.rs and *_tests.rs count as test files, so src/review/tests.rs is hidden by default like the other test files. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823 Agent-Session: 9a68def0-2052-49c3-a656-a142d3152c20
Fold line spans are nested before the tree is built: when two parser folds cross on one line, such as a collection whose closer sits on the line that opens the next body, the earlier fold gives that line to the later one, and a span left with one line is dropped. A fold's wire range is now the hull of its children, whole lines, instead of the parser's byte range, so every child lies inside its parent and siblings never overlap. A debug assertion and the streaming checker walk every tree. The terminal frontend no longer hides the line after a fold whose end sits at column zero. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823 Agent-Session: 9a68def0-2052-49c3-a656-a142d3152c20 Agent-Session: 6b254b74-b802-4554-b16d-6723bf8b5f1f Agent-Session: 3ad992ce-8922-470c-a21f-ad2de0e31b10 Agent-Session: bf405baa-0eb9-435d-8f3d-0846840aff6c
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823 Agent-Session: 9a68def0-2052-49c3-a656-a142d3152c20 Agent-Session: 6b254b74-b802-4554-b16d-6723bf8b5f1f Agent-Session: 3ad992ce-8922-470c-a21f-ad2de0e31b10 Agent-Session: bf405baa-0eb9-435d-8f3d-0846840aff6c
A function fold whose header line did not align could still hold paired leaves; the deleted-bodies rule treated it as removed and hid a rewrite. A region now counts as one-sided only when nothing under it has a counterpart. Removed stretches split only when their nearest enclosing function fold (or, outside any function, the nearest fold) is one-sided, and a "functions removed" group never wraps a rewrite. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp
alignment_id says what a region is across sides: the same value on the other side is its visual counterpart, one-to-one. fold_state_id says what it moves with: regions sharing it open and close together and may span sides or bundle same-side regions. They coincide for every region today; later mutations will bundle a docstring with its function. While assigning alignment ids, folds now also pair through their content: a fold whose header line did not align pairs with the fold on the other side that holds the counterpart of one of its leaves, most paired lines first, never crossing an existing pair. A function whose signature was reflowed onto more lines is the same function again instead of a removal plus an addition. Across main..HEAD, unpaired function folds that still held paired lines fell from 10 to 4; the remaining four moved into their parent function, which is already paired. Wire version 3. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
The row zip pairs leaves by alignment_id; collapse state, fold headers, nested and gap commands, and visible counts key on fold_state_id, so regions sharing it toggle together on both sides by construction. Accepts wire version 3; the fixture is regenerated. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A file that was deleted outright starts collapsed behind a placeholder labelled "Deleted file · hidden by default", whatever its category. folds.collapse_deleted_files turns it off. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A fold whose hull runs to the last line ends at (line count, 0), one past the last line. The reader required that line to exist even though an end at column 0 never reads it, so any file whose last function ran to end of file stopped the whole view. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
File headers, the summary strip and the breakdown read stats.visible and stats.textual from the wire and nothing else. Folding changes what is on screen, never the numbers, so the local recount on every toggle is gone. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A region whose alignment_id counterpart the row zip has already passed is a move. Both copies now paint in the theme's moved tint with no +/-, under a label naming where the other copy starts (moved from / moved to line N); edits inside a copy keep the word tint. Clicking the label or pressing gm jumps to the other copy. g still goes home at once and remembers the view, so gm jumps from where the reader was. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Collapse state already keys on fold_state_id; recursive folding now unions the nested ids of every region sharing it, so a docstring bundled with its function opens and closes with it. A test covers a docstring and body sharing one id: one toggle hides both, the empty docstring label renders as a bare ellipsis, the signature stays. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A fold whose closer sat inside a long unchanged stretch that continued in its parent cut the gap into two collapsed leaves at different depths, so Review drew two unchanged bands back to back. A fold that ends inside a gap now extends to the gap's end, and a fold whose header would be hidden inside one is dropped, but only when the cut would leave two pieces long enough to collapse. The streaming checker asserts no two collapsed unchanged leaves are adjacent. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A function's docstring is the run of comment lines just above it, with only its signature and blank lines between, or in Python the string that opens its body. A new fold mutation splits those lines into leaves tagged docstring that share the function's fold_state_id, so frontends open and close them together; a paired docstring keeps the bundle only when the other side agrees. After the collapsing mutations run, a docstring whose function starts collapsed collapses too, with an empty label. The summarizer sends the docstring with each body and may get back one sentence from it; the sentence is kept only when it really occurs in the docstring, and then leads the pseudocode. Compression is judged on the pseudocode alone. folds.bundle_docstrings turns it off. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
diffr bundles a docstring leaf with its function: it starts collapsed, carries an empty label, and shares the function's fold_state_id. A leaf diffr starts collapsed is now foldable in its own right, and an empty label renders as a bare ellipsis instead of an unchanged-lines count, which only context gaps use. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A docstring leaf sharing its function's fold state now starts that function's unit, so a run of documented test bodies becomes one group again and the group begins at the first docstring. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A line diff pairs the lines of a changed block from the top. In src/hook.rs that paired the new fn qualifies signature with an old doc comment sixty lines above the old signature, leaving a long filler gap above a body that still aligned. When a block's two sides differ in length and its last lines read more alike by identifier tokens, pair those instead. Only line-diff fallbacks are realigned; the structural matcher anchors its rows on matched tokens. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A collapsed fold, its multi-line label and a bare docstring row now take the same change tint Review's bands use: the added tint for a region whose alignment_id is absent on the left (a summarized new function), the removed tint for one absent on the right (a removed body), and the neutral fold background only for paired regions and unchanged gaps. Tests cover the tint for inserted, removed and paired folds, and a docstring bundled with its function opening and closing together from either row by mouse and by zo, zc and za. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
A structural diff pairs folds exactly as the syntax matcher recorded them: parse::folds::pair_matched maps each FoldMatch::Unchanged opposite range to the fold on the other side. A line-diff fallback pairs folds through the line alignment, by header line and then by the lines they hold, in the new line_folds module, which also owns the fallback's changed-block row alignment. DiffResult carries the result as fold_pairs, and the projection only consumes it: pairing no longer lives in protocol/project.rs. Written with Claude Code. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp Agent-Session: 0de57442-ad9b-4c99-960e-4974e629b71d Agent-Session: e046556c-20f0-4bba-832b-b6784a7783c5 Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
DiffResult.fold_pairs held a second copy of fold correspondence as index
pairs. It existed because the line-diff path found partners but never
wrote them onto the folds the way the syntax matcher does. line_folds::pair
now sets FoldMatch::Unchanged { opposite } on both folds of each pair, the
field goes away, and the projection reads every file's partners from the
folds through parse::folds::pair_matched.
No output change: per-file function fold pairing across main..HEAD is
identical before and after.
Written with AI assistance (Claude Code).
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzHbAqhxaLKNQbYyqfCyTp
Agent-Session: 8837929e-0118-489c-9648-e47c1831ee8c
Agent-Session: 0025c2a5-4c51-4e7e-be92-b0659a205823
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.
the config setup is broken - there's technically a command
diffr configto add your api key but it doens't work that well.instead you'll have to run
diffr config setto actually set the API key for Gemini to create the pseudocode. sorry!!