Skip to content

Draw diffr web's comparison as plain rows on every screen - #1097

Open
milanb17 wants to merge 19 commits into
claude/diffr-web-packed-grammarsfrom
claude/diffr-web-dom-renderer
Open

milanb17 wants to merge 19 commits into
claude/diffr-web-packed-grammarsfrom
claude/diffr-web-dom-renderer

Conversation

@milanb17

@milanb17 milanb17 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Draw diffr web's comparison as plain DOM rows on desktop as well as phones, in split or unified layout, and delete the Monaco diff editors. Stacks on #1095; merge #1093, #1094 and #1095 first. It deletes reviewFilesDiffView.ts, which #1096 edits, so #1096 can close once this merges.

  • Split layout. Each row pairs the lines diffr paired, with a hatched filler where a side has no line. When lines don't wrap, each side scrolls sideways on its own, and a file's segments stay level. With word wrap on, both sides of a row wrap to the same height. A text selection stays on the side it started on.
  • Unified layout. On desktop it shows both sides' line numbers. Phones keep one column of numbers. Removed header lines now carry their base-side fold pill, as head lines already did.
  • Folds. Bands, chevrons (2+ lines), header pills and summary pseudocode all work as before. On desktop only the gutter folds, so clicking code just places the caret that z a/o/c/A/O/C and z j/k act on. On a phone, a tap anywhere on a fold row folds it.
  • Virtualisation. Rows are drawn in blocks of 120, only near the screen. Far from it, a block empties again and holds its measured height. Before a block is drawn, its height is estimated from its row and band counts. Find, next/previous change and reveal draw the blocks they need.
  • Scopes. Every open scope draws a gray indent guide down its body, broken at bands. On desktop the pointer's column picks the scope: over code it is the innermost, and moving left into the indentation steps out one guide at a time. The picked scope lights its chevron, braces and a rail (which stops at bands), shows its unread changes and a viewed box, and a click in its indentation or on the rail folds it (the header then reads "click · fold N lines"). Clicking the number of a line inside a scope lists every scope around it, innermost first, as a menu on desktop or a sheet of 56px rows on a phone. A viewed scope folds, dims its lines, keeps "Viewed" on its outermost header and shows ✓ in its pill. On a phone, pinch in over a scope to fold it, out over a fold to open it; the list sets touch-action: pan-x pan-y, so touch listeners stay passive.
  • Wrapping. Wrapped lines continue under their own indentation, and their number stays beside the first line.
  • Sidebar. FilesSidebar holds the changed-files tree, its summary line and a drag sash. ⌘B toggles it.
  • Deleted: the multi-diff view, its structural folds, syntax decorations, the Monaco widgets for scope rails and viewed controls (redrawn as DOM above), the native-scroll shims, the overflow-widget host, and the CSS for all of these. nativeDiffScroll.ts shrinks to viewport.ts, which holds only the phone media query.

I started from our renderer rather than forking @pierre/diffs, whose line-hunk model and built-in Shiki don't fit diffr's folds. I took the idea of per-side scroll columns from its split layout but copied none of its code.

Evidence

vite v5.3.0...v5.4.0 (217 files):

Before After
Expand pnpm-lock.yaml (25k rows), split, desktop Chrome one 440 ms task, 107k nodes 35 ms
Scroll through it, p95 frame 46 ms 10 ms
Same on a 402×874 touch viewport — 40 ms expand, 11 ms p95
JS bundle (raw / gzip) 2.95 MB / 782 kB 2.11 MB / ~555 kB
iPhone 17 Pro simulator Safari, peak footprint while loading 527–543 MB 450 MB, settles at 248 MB
  • Desktop Chrome: split and unified, wrapped and not, sideways sync, ⌘F highlights in undrawn blocks, [/] across blocks, the fold keys, the sidebar and quick open all checked, with no console errors. Estimated block heights matched measured ones exactly, so the page doesn't jump as blocks fill.
  • Simulator Safari: 60 stacked flings showed 13 blank captures. With blocks turned off (one block per file), the same run showed 14. These are the Safari paint lag in the 82k-px CHANGELOG seen before this change, not undrawn rows.
  • Scopes, desktop Chrome: hovering inside a function draws its rail in each pane segment; ticking its viewed box folds and dims it, the enclosing function then counts as viewed and carries "Viewed" and ✓, and unticking restores everything. Pinch: synthetic two-finger touch events fold and reopen a scope in phone-emulated Chrome and in iPhone 17 Pro simulator Safari (iOS 26.5), where the list's touch-action also reads pan-x pan-y. A real two-finger gesture in the simulator is still untried.
  • Browser typecheck, lint and format pass. diffr-web has no unit tests.

Merge Danger

Door: two-way; revert the two commits to bring the Monaco diff view back.

Blast Radius: diffr web's whole diff view on every screen size.

The phone's DOM renderer now draws the desktop too, in split or unified
layout, beside the changed-files tree, and the Monaco diff editors are gone.

- Split pairs the lines diffr paired, with a hatched filler where a side has
  none. Unwrapped, each side scrolls sideways on its own and a file's segments
  stay level; wrapped, both sides of a row wrap to one height. Selecting text
  keeps to the side it started on.
- Unified on a desktop shows both sides' line numbers.
- Rows are drawn in blocks of 120 only near the screen and emptied again far
  from it, at their measured height. Expanding the 25k-row pnpm-lock.yaml in
  split took one 440 ms task and scrolled at a 46 ms p95 frame; it now takes
  35 ms and scrolls at 10 ms.
- Folds work as before; on a desktop the gutter folds, so clicking code only
  places the caret that z and j/k act on.
- The files tree, its summary and a drag sash move into FilesSidebar.

Deleted: the multi-diff view, its structural folds, syntax decorations and
per-scope viewed controls, native-scroll shims, the overflow-widget host and
the CSS that styled them. The bundle drops from 2.95 MB of JS (782 kB gzip)
to 2.11 MB (about 555 kB).

Agent-Session: 429958d8-68f3-4f68-bd3b-a8371e7feb8e
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
The Monaco view had these and the plain rows did not yet.

- On a desktop, the scope under the pointer lights its chevron, shows a
  rail down its indentation that folds it on click, and shows its changes
  left to read with a viewed box on its header.
- Marking a scope viewed records its lines as viewed, folds it and dims
  them. A viewed scope keeps "Viewed" on its header, the outermost one
  only, and its pill reads ✓. Viewed scopes start folded, as before.
  StructuralViewedState comes back without its Monaco widget hook.
- On a phone, pinching in over a scope folds it and pinching out over a
  fold opens it. The list sets touch-action: pan-x pan-y, so Safari never
  zooms the page under it, and the touch listeners stay passive.
- Wrapped lines continue under their own indentation, and their number and
  chevron stay beside the first line.

Agent-Session: 429958d8-68f3-4f68-bd3b-a8371e7feb8e
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
As the Monaco view did, with the CSS Custom Highlight API that find already uses, so hovering adds no DOM. Brace columns are one-based.

Agent-Session: 429958d8-68f3-4f68-bd3b-a8371e7feb8e
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
…owser

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
…l's fold on the header

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
…hone

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 01a1362c-3e21-7c22-9c5c-8f440a884b30
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 01a1362c-3e21-7c22-9c5c-8f440a884b30
…nd keep file extensions

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 01a1362c-3e21-7c22-9c5c-8f440a884b30
Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 01a1362c-3e21-7c22-9c5c-8f440a884b30
…tion, and list a line's scopes from its number

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 01a1362c-3e21-7c22-9c5c-8f440a884b30
…the gutter

iOS drew the inner list's scroll bar under the sticky file headers. A phone now scrolls the
page: the app header sticks, the file headers stick below it, and the observers that draw and
free rows watch the viewport. A band's chevron sits in the gutter in line with the rows' own,
before a hatched code column reading "⋯ N unchanged lines", as Monaco drew it.

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 429958d8-68f3-4f68-bd3b-a8371e7feb8e
…bel on its diagonal fill

Agent-Session: 5eb4067a-b2f6-4508-a6dd-0471ff2c14d1
Agent-Session: 429958d8-68f3-4f68-bd3b-a8371e7feb8e
Agent-Session: dcdf8861-de36-4127-b7a5-52bfb5448d3d

This branch has not been deployed

No deployments
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.

1 participant