Skip to content

diffr TUI: review features, and @diffr/viewer for other frontends - #1065

Merged
sidkmenon merged 25 commits into
mainfrom
sm/diffr-tui
Oct 9, 2026
Merged

sidkmenon merged 25 commits into
mainfrom
sm/diffr-tui

Conversation

@sidkmenon

@sidkmenon sidkmenon commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

The TUI gains review features, and its code that does not depend on OpenTUI moves into a new package, @diffr/viewer, so other frontends (the Claude Code mod in #1019) can draw the same view.

Toolchain: every package in diffr/packages is a pnpm workspace member. There is no bun.lock. Bun only runs and compiles the OpenTUI packages. Bun and Node both run @diffr/viewer from source, so one package manager must own its dependencies. cargo xtask install first runs pnpm --filter @diffr/tui... install, so it now needs Node and pnpm as well as Rust and Bun. The release build job installs them. The README install line from a checkout is marked TODO.

Commit by commit:

  • 1–4: Prepare the TUI. Split out the renderer-free code, fix two fold hover issues, move the TUI under diffr/packages (no code changes), and parse themes with smol-toml.
  • 5–8: Move the renderer-free code, cell planning and viewer state into @diffr/viewer. Commit 5 also makes the pnpm change. Commit 7 fixes a rail click.
  • 9–13: Clean up theme and span layering, and delete Hunk code that diffr does not use.
  • 14–23: Review features: copy a selection for an agent (Y), mark files viewed, search (/ n N), go to a file (Ctrl-P), ]c and [c, close open views on blur, lay out very large files, the selection bar (selected lines are blue in every theme), cross-side selections as Git patches, and wider fold hit areas.
  • 24: Test @diffr/viewer and the TUI in CI.

Testing

  • pnpm typecheck and pnpm test in diffr/packages/viewer (70 pass), and bun test in diffr/packages/tui (44 pass), from a fresh pnpm install --frozen-lockfile.
  • cargo fmt --check and cargo test.

Stack: #1065 → #1066 → #1019 → #1023 → #1024 → #1025 → #1026

https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo

palette.ts holds the palette, Helix theme mapping and theme config with no
Bun.TOML or node:fs, and events.ts parses NDJSON text with stream.ts left to
decode bytes. The model layer can then be bundled outside Bun. The bundled
dark and light palettes move to theme.ts with the rest of theme loading.

Agent-Session: 01a118c6-60a6-7373-bad2-7f763da2c3c2
Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…its rail (AI-assisted)

A collapsed fold's chevron and label arm it, so its chevron takes the accent
as an open scope's does. A syntax body folded behind a multi-line summary
keeps its chevron on the opener, stands the summary beside the scope's own
rail, and boxes its brackets while armed, as the Whiteboard client does.
Label lines name their fold with labelOf, so clicking the summary opens it.

Agent-Session: 01a118c6-60a6-7373-bad2-7f763da2c3c2
Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
The TUI moves from tui/ to packages/tui, with Hunk's packages/hunk/src
flattened to src. xtask, the release archives and their smoke test, NOTICE
and the diffr-ts fixture path follow. Release archives keep mirroring the
repository's license paths, as NOTICE cites them. No code changes.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Theme parsing then runs anywhere, not only under Bun, so the code can be
shared with frontends that do not run on Bun.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
packages/viewer holds what does not depend on OpenTUI, one folder per
domain: protocol (diffr's wire format, events and store), document
(regions, rows, the file tree and selection), viewport (row geometry),
terminal (text measurement and sanitizing) and theme (the palette, Helix
theme loading and the themes). It type-checks against ES2023 plus the few
web APIs every host has, so nothing Bun-, Node- or DOM-only creeps in.

The viewer is plain TypeScript and tests with vitest. Bun and Node both
run it from source, so one package manager must own its dependencies: the
viewer and the TUI join the pnpm workspace, and Bun only runs and compiles
the TUI. xtask installs the TUI with pnpm before compiling it. Code moves
unchanged apart from imports; the row and span types join rows.ts, and the
regions tests' file builders join fixture.ts.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
planCell lays one side of a code row out as coloured runs, with the cells a
click folds or opens and the scope the pointer focuses. CodeRowView draws
the runs and routes OpenTUI's mouse through those targets. No behaviour
change.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…ted)

On a collapsed fold's label line, the pointer over an enclosing scope's rail
arms that scope, but a click opened the label's fold instead. A click now
picks the same target as the pointer.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Viewer holds open files and folds, the reader's position, layout mode,
wrap, theme, pan, the scope under the pointer, and the fold and navigation
keys. It lays out the diff column at a given size and notifies on change,
so App reads it through useSyncExternalStore. App keeps its file tree,
menus, selection and breakdown. No behaviour change.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
It maps a fold's tint to a palette colour, as captureColor maps a syntax
capture, so the theme no longer depends on the document.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: ab10ae26-42f2-4ecd-86d0-79f5a9edecf0
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…ted)

They slice and wrap spans by their text and merge runs whose other fields
all match, so they no longer import the document's span type. That removes
the last import from a lower folder of @diffr/viewer to a higher one.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…g (AI-assisted)

visibleRows used only the start and end of Hunk's window, whose zero-height
rows and spacer sizes never occur in diffr: every measured row is at least
one line tall. A binary search for the first row in view and a walk to the
bottom replace it.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…isted)

wrapText, wrapTextByWidth, cellRangeToCharRange, fitText, padText,
measureWrappedSpansLineCount, formatTerminalPath, RenderSpan.transformFg,
and the sanitizer's options for keeping newlines and ANSI colours: only
sanitizeTerminalLine's behaviour is used. Their tests go with them.
rows.ts also stops re-exporting sourceLines, which callers import from
regions.ts.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
counts.ts redeclared the wire's snapshot union as its own Snapshot, which
also shadowed the store's unrelated Snapshot. The protocol now exports it as
ComparisonEnd, and the store and the comparison label use it.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
The copy names the file and lines the selection lives on.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Agent-Session: ea85fcc1-e10f-453d-afea-1d46506e7e1e
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Marks last as long as the comparison is open; a hint sits beside each box.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11a13-5682-7023-a529-1836d6939a48
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…eys (AI-assisted)

Search shows one hue for matches and another for the current one. Cmd-P
opens the picker where the terminal passes Cmd through. ]c and [c go to the
next and previous change, as in vim. Typed characters from kitty terminals
act as keys.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…ocus (AI-assisted)

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…sisted)

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
A selection can span the old and new sides, as GitLab positions a comment.
Agents receive it as a standard Git patch numbered from the diff's own
order, so folds never change it, with the comparison's base and head.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a11ab9-c68e-7c52-916b-e1b0123e302f
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
…assisted)

Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: cbc75bbf-5438-46f4-b8ed-41beb0e20f0c
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
One job installs the pnpm workspace once, then typechecks and tests
@diffr/viewer and the TUI.

Agent-Session: fb96350b-c19e-48f0-b364-31e9ad2b72f4
Agent-Session: a7af3b88-3bb7-48a2-ae20-7626a83628a1
Agent-Session: 01a1196d-bad4-7f61-bafc-f63d12217676
Agent-Session: 7d7ac281-cc4b-4437-9e1e-3872651e8bcf
Agent-Session: 01a11aca-5007-7b80-ad35-c0a0f746a05d
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: 01a11cf1-8ad4-7910-b120-b0d622692a95
Agent-Session: 01a11ded-d896-7251-bf8f-c71bf07fd283
Agent-Session: 01a11e03-81df-7d63-bfa5-953f535444d2
Agent-Session: 01a11e0a-d042-71d2-9bc9-ac7b652487d1
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Claude-Session: https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Agent-Session: 01a11ecf-aa86-7a81-a5e6-c5dfe72d48eb
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Whiteboard CI typechecked and tested every workspace package, so the
TUI's `bun test` failed on a runner without Bun, and the Claude Code
mod's tests failed to typecheck without the Claude Code types it
generates locally. Diffr Test already checks diffr's packages.

Claude-Session: https://claude.ai/code/session_01HTHD79i4QdthzVCkDZ26gd
Agent-Session: ec5d2c35-3045-44f3-9c41-ba4378797c8e
Agent-Session: b5985b3c-e5d2-4bb4-b6ac-7fec168d8328
Agent-Session: 01a11f51-383b-7f63-a323-39195d82ab2e
Agent-Session: cac10624-9b5a-46ba-b261-1e0359c62796
Agent-Session: 8881e3f3-a916-4405-9706-bb6e3a2f193d
@sidkmenon
sidkmenon merged commit 3c81ed6 into main Oct 9, 2026
11 checks passed
@sidkmenon
sidkmenon deleted the sm/diffr-tui branch October 9, 2026 08:35
sidkmenon added a commit that referenced this pull request Oct 9, 2026
One command installs diffr and its Claude Code plugin:

```sh
curl -fsSL https://install.dev.fast/diffr | sh
```

Commit by commit:
- **`DIFFR_CONFIG_DIR`**: a host such as Whiteboard can keep its own
diffr config file. Without the variable, diffr reads the same file as
before.
- **`--help` guides**: `diffr --help` ends with a guide for agents
(`docs/agents.md`), and `diffr config --help` with the plugins guide
(`docs/plugin.md`). `-h` stays short. An agent runs `diffr --help` in
its shell, so what it reads always matches the installed diffr.
- **`diffr config init`**: in a terminal, asks which agents get the
diffr plugin and which provider writes summaries, and asks for a key
only when neither the config nor the environment has one. Without a
terminal, `config init --json` prints the questions and a schema, and
`config init --json ANSWERS` applies them; the key never goes through
`--json`. Claude Code is the only agent: `claude plugin install
diffr@devfast` after adding or updating the devfast marketplace, the
same marketplace Whiteboard's plugin uses. Installed agents are recorded
in `$XDG_STATE_HOME/diffr/install.json`, outside the synced config
directory.
- **`install.sh`**: finds the newest `diffr/X.Y.Z` release (or
`DIFFR_VERSION`), checks the archive against the release's SHA256SUMS,
installs diffr and diffr-tui in `DIFFR_INSTALL_DIR` (`~/.local/bin` by
default), then runs `diffr config init` on a first install.
- **Install module**: what `config init` installs moves to `install.rs`,
so `upgrade` and `uninstall` use it without the prompts. No behavior
change.
- **`diffr upgrade` and `diffr uninstall`**: work the way diffr was
installed, read from where the binary is (Homebrew, Cargo, inside
`node_modules`: refuse, otherwise install.sh). After an upgrade, each
recorded agent updates its plugin. `uninstall` keeps `~/.config/diffr`,
which Whiteboard shares.
- **Docs**: the plugins guide for agents, the man page's commands, files
and environment, and a README that leads with the install script.
- **Summaries**: `config init` asks yes or no for pseudocode summaries,
then the provider, then its key.
- **PATH and failures**: install.sh offers to put diffr on PATH and
points install failures at the issue tracker.
- **Steps**: setup shows each step as it runs.

## Before the install route goes live
- Release `diffr/0.1.18` after this and #1019 merge. install.sh installs
the newest `diffr/*` release; 0.1.17 has no `config init` or `upgrade`,
and the plugin needs 0.1.18.
- Then merge Fix-Fast/dev#1119, which points `install.dev.fast/diffr` at
`diffr/install.sh` on main.

## Testing
- `cargo fmt --check` and `cargo test`, including the CLI tests for
`DIFFR_CONFIG_DIR`, the `--help` guides, `config init --json`, `upgrade`
and `uninstall`.

Replaces #1039, #1040, #1054, #1056, #1057 and #1063.

Stack: #1065 → #1066 → #1019 → #1023 → #1024 → #1025 → #1026

https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
sidkmenon added a commit that referenced this pull request Oct 9, 2026
Implement a [claude code
mod](https://code.claude.com/docs/en/plugins/mods/overview) for diffr.
You or your claude can launch a beautiful, interactive diff view right
from claude code!

No switching tabs!

<img width="1511" height="832" alt="Screenshot 2026-10-08 at 12 51
37 AM"
src="https://github.com/user-attachments/assets/4b9b41b0-11d1-43d0-919b-213362f043af"
/>

Note from clanker:

```text
/diffr opens diffr's diff viewer in a Claude Code pane, drawn with @diffr/viewer from the PR below. The mod is in diffr/packages/claude-code, and the devfast marketplace (the one Whiteboard's plugin is in) lists it as diffr@devfast.

Commit by commit:
1: The /diffr mod: the command, the model's open tool, the pane, and selections added to the chat as chips whose code goes with the prompt. After Enter adds a selection, the pane closes and opens again without the keyboard, so the person types in the prompt; Claude Code has no call that moves focus there.
2: The built plugin, and its marketplace entry.
3: CI tests the mod and fails when the committed bundle differs from a fresh build.
4: /diffr and the open tool check that diffr is at least 0.1.18, the first release with `config init` and `upgrade`, and name the command that fixes it.
5: The open tool points the model at `diffr --help`, and the README lists /diffr.
```

## Testing
- `vitest run src` in `diffr/packages/claude-code` (9 pass) and `claude
plugin test plugin` (8 pass), from a fresh `pnpm install
--frozen-lockfile`. `pnpm build` reproduces the committed bundle.
- Installed in a live Claude Code session: /diffr opens the pane, Enter
adds a selection to the chat and hands the keyboard back.

Replaces #1061.

Stack: #1065 → #1066 → #1019 → #1023 → #1024 → #1025 → #1026

https://claude.ai/code/session_01HNbERuYJjvvAjBN19zEDbo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants