Repository navigation
cmux-next: diff viewer paints big diffs fast and gets the cmux NEXT look - #23154
Conversation
…cmux NEXT chrome Measured on huge-blue (headless Chromium, production build of the virtualization harness, from the patch being ready): - 2,000 files / 10 MB: first row 1,180-1,330 ms -> 390-510 ms - one 1,000,000-line file: first row 6,190-6,740 ms -> 1,195-1,260 ms What was slow and what changed: - The patch splitter appended every 256 KB chunk to its buffer and searched it, flattening the whole rope each time: quadratic inside one big file (4.4 s of copying). Text with no file boundary now waits outside the buffer. - The highlight pool was created with the first files; it now mounts with the viewer, so its workers start while the patch streams. - Rows drew only after every worker compiled Shiki's WASM; the pool now gets a JavaScript-engine instance at once and draws plain rows until its colors arrive. - Between batches the parse yields every 12 ms, so the first files' rows and worker replies are not held behind a 100 ms parse task. Look: word changes are on by default (word-alt marks), a hairline rim and an accent open state on toolbar chips, a translucent toolbar, the files panel on one lift with an accent selection, and a five-block change bar per file. A Chromium perf test (diff-paint-perf.test.ts) holds the first row and the first highlight to a budget; a unit test covers the splitter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
|
Owned by projects-ba on cosmic-gray · session |
|
Owned by projects-ba on cosmic-gray · session |
|
Bead |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Unclear: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: web / Web status; NEW: green on the base 0284c98; do not merge until green; see lane-rules
Agents: Gallery before/after for
States
Pick other states with a |
…t row mid-stream The mixed fixture also asserts the first row draws before 60% of the stream, which holds on any machine speed and fails on the old path (78-86%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
+6 -6 drew two green blocks and three red; ties now drop a block from both sides and leave a neutral one, as GitHub's bar does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
The settings exports regenerated by the PR autofix (its push job leaves them in the pr-autofix-patch artifact), applied with scripts/cmux-next/pr-autofix-apply.sh: diff.wordDiffs now defaults to true. The defaults snapshot stays with the base autofix (bot-only). Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39566105569 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
The settings exports take the base's copies (GitHub merges them by line and called the pull request conflicting); the PR autofix regenerates them with this branch's diff.wordDiffs default on top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
…highlighter once replaced Review findings: a MessageChannel per parse yield stayed alive (a started, connected port is never collected), hundreds per big patch; and the pool overwrites the plain-rows Shiki instance without disposing it. The yield now shares one channel with a FIFO of waiters, and the plain highlighter is disposed on the pool's first stats broadcast after it is replaced or cleared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
…40faca scripts/cmux-next/regenerate-settings-exports.sh on cmux16 (the PR autofix's Mac job did not regenerate them on the last two heads, and its push job is skipped). The export run's SettingsSchemaExportTests reports a base gap (notifications.attention.flash keys have no agent policy) and still writes the exports; defaults_guard.py check-stamp passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
The settings exports take the base's copies; the next commit regenerates them on this merge with diff.wordDiffs on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
scripts/cmux-next/regenerate-settings-exports.sh on cmux16 at this branch's merge with feat-cmux-next cca5809. Three base test files that do not compile there (PageEnginePolicyTests, ChiefAppAcpmuxTests, BrowserProfileNewTabMenuTests; the exports do not read them) were left out of that scratch build only. defaults_guard.py check-stamp passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
The settings exports take the base's copies (#23239 regenerated them); the next commit regenerates them on this merge with diff.wordDiffs on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
…ations of two base tests belong to the base) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
The settings exports the PR autofix regenerated on its Mac job (its push job is skipped; artifact pr-autofix-patch), applied with scripts/cmux-next/pr-autofix-apply.sh: diff.wordDiffs defaults to true. The defaults snapshot stays with the base autofix (bot-only). Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39590411725 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review (independent subagent, claude-opus-5-5): SHIP at 19ecab0 No blocking findings. Checked: streaming parse gives the same files as a whole-patch parse at every chunk size; cancellation drops batches from stale streams; plain rows before workers start can't use the wrong language and leak no worker; word diffs are capped on huge diffs (lines over 1000 chars, plain diffs over 1000 lines); the toolbar blur doesn't change how menus stack; the settings export sha matches. cmux16, base → head: mixed first row ≈330 → ≈93 ms, huge ≈585 → ≈220 ms. The perf test fails on base, as intended. Non-blocking:
Reds at this head are base: |
Default changesThis pull request changes 1 default at
Needs a call. It passes only with an approving review on this commit from @lawrencecchen or @austinywang (the call owners in Merge receipt: merged as Regenerate the snapshot with |
…3480) #23154 added diff-paint-perf with a probe that read layout in a requestAnimationFrame callback; #23374's frame-read rule (check:browser-lanes) landed meanwhile, so webviews lint went red on feat-cmux-next for every PR. The probe now reads in window.cmuxAfterPaint (test/support/paintedFrames.ts) and marks each milestone at the painted frame's time. Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ghlighter uses the pool's themes (#23465) * test: diff viewer word changes default per host, plain-rows highlighter themes from the pool (red) The classic viewer (embedded config) should start with word changes off and cmux NEXT (page host, transport `page`) with them on, NEXT's defaults matching the exported settings schema. The plain-rows highlighter should load the pool's own themes and not be handed to a pool shut down while it was built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray * cmux-next diff viewer: word changes default on in cmux NEXT only; plain-rows highlighter takes the pool's themes #23154 turned word changes on in the shared webview default, which also changed the classic viewer. The default now follows the host: on cmux NEXT's page host (config transport `page`) the display defaults are the `diff.*` settings' (DiffViewerSettingsSchema.swift; diff-option-defaults.test.ts holds them to the exported schema), word changes on; the classic viewer and the dev server keep word changes off. The virtualization harness seeds word changes on so diff-paint-perf keeps measuring cmux NEXT's default. drawPlainBeforeWorkers loaded its themes from the viewer options captured at first mount; it now reads the pool's (getDiffRenderOptions().theme), the ones the pool draws plain rows with. A pool terminated while the instance was built no longer gets it (it is disposed; outcome `stopped`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray * test: diff-option-defaults installs the DOM globals @pierre/diffs reads at import state.ts reaches @pierre/diffs, whose web components module reads customElements when it loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15d24-cf0a-7373-a03a-75491e4d724a&host=cosmic-gray --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Shipped in:
|
The diff viewer (the chat header's Changes page and every other
diff-page.htmlhost) now draws its first rows of a big diff 2.5-5x sooner. It gets the cmux NEXT chrome: a glass toolbar, accent-tinted open menus, a lifted file list and a per-file change bar. Word-level highlights are now on by default.What was slow
I measured from the moment the host's patch was ready, in headless Chromium on huge-blue. The page was
test/browser/diff-virtualization.html, which runs the samemountDiffSurfacepath as the app. I took a CPU profile for each fixture.createStreamingPatchFileSplitterappended every 256 KB chunk to its search buffer, and then searched it for the nextdiff --git. That flattened the whole V8 rope again on each chunk. On a 60 MB generated bundle,nextGitBoundaryIndexalone took 4.4 s before anything drew.WorkerPoolContextProvidermounted only with the first files, and each worker then compiles Shiki's WASM (300-450 ms). Until every worker is up,@pierre/diffsdraws file headers with no rows.What changed
diff-stream.ts: text that cannot contain a file boundary is held aside. It joins the search buffer only when adiff --gitshows up (checked across the chunk seam) or atfinish(). Each file is now flattened once. Parsing yields to the event loop every 12 ms through a MessageChannel post.scheduler.yield()measured worse, because its continuation runs ahead of the worker replies.App.tsx/useRenderDiff.ts: the worker pool and the theme registration start as soon as the page knows a diff is coming, not when the first files arrive. The workers boot while the patch streams.WorkerRenderOptionsSync.tsx(drawPlainBeforeWorkers): while the workers boot, the pool gets a Shiki highlighter on the JavaScript regex engine with no grammars. That highlighter draws plain rows in the right theme with no WASM. Once the workers are up they highlight as before. A changed@pierre/diffsthat no longer has the field this reads reportsunsupported, and the perf test fails on that.Restyle, using existing tokens only (
--cmux-accent,--surface-blur,--cmux-hairline, fg/bg mixes; no new literal colors):--surface-blurover a 3% fg lift).--text-detail.Word diffs default to on (
diff.wordDiffsinDiffViewerSettingsSchema.swiftand the webview default). They render asword-alt. No Debug toggle: the setting already exists in Settings and in the toolbar's options menu (Word diffs), so turning it off is one click. The settings exports (settings-schema.json, its source stamp,docs/mdm) were regenerated withscripts/cmux-next/regenerate-settings-exports.shon cmux16, because the PR autofix's Mac job did not regenerate them on recent heads and its push job is skipped. The defaults snapshot is left to the base autofix (bot-only).Review fixes (from an independent review; it also fuzzed the new splitter against the old one, 50,000 cases, no difference):
MessageChannelfor the page, with a queue of waiters. A channel per yield stayed alive (a started, connected port is never collected), hundreds per big patch.Numbers
All numbers are in ms from the patch being ready, on huge-blue, headless Chromium.
Production build (
vite buildof the harness, best of 3):mixed: 2,000 files, every fifth line changed, then a 1,500-line rewrite (~10 MB)huge: one 1,000,000-line bundle (~60 MB) ahead of 50 filesThe new perf test itself (dev server, best of 2): base
7a4b464b375vs this branch.mixedhugeAt
940faca2aad(with the review fixes, word diffs on), the same test on huge-blue:mixedfirst row 398 ms, first highlight 962 ms, complete 1,505 ms (first row at 26% of the stream);hugefirst row 1,216 ms, first highlight 1,471 ms.Streaming the whole of
mixedtakes slightly longer (about 1.6-1.8 s against 1.3-1.9 s), because parsing now yields so the rows can paint.Perf test and budget
webviews/test/diff-paint-perf.test.tsis new, usesrequireBrowserLane, and is Chromium only. The webviews runner picks it up from the git index; there is no registry for webviews tests. A probe script marks, each frame, the first diff row inside the viewer, the first row with Shiki token colors, and the end of the stream. The test runs each fixture twice, keeps the best run, and asserts:mixedrow < 1,500 and highlight < 3,300;hugerow < 3,500 and highlight < 4,500. They are about 3x this branch's dev-server numbers, which leaves room for a slower CI runner. Base misses thehugeones by 2,400 ms or more.mixed, that the first row is drawn before 60% of the stream. This check does not depend on machine speed (base 78-86%, this branch 21-34%).drawn), so a@pierre/diffsupgrade cannot silently undo it.Base fails the test and this branch passes it, on huge-blue.
webviews/test/diff-stat-blocks.test.tsis also new and covers the header bar.webviews/test/diff-stream-splitter.test.tsis also new. It checks that the splitter returns the same files for every chunking, including adiff --gitmarker split across chunks. It also checks that a big file comes back once and whole, the no-git-header fallback, andformat-patchcommit headers.Gallery
There are new
pages.diffvariants:word-diff: unified, a config file with changes inside linesword-diff-splitbig-review: 120 files at 760 px, for the file list and the change barscollapsed-files: a play step that clicks a header to collapse itThe existing variants show the new chrome. To compare: https://cmux-lawrences-mac-mini.tail137216.ts.net:18796/compare/cmux-diffviewer-fast/?entry=pages.diff
No capture: the only native-app change is one default value (
diff.wordDiffsinDiffViewerSettingsSchema.swift); everything visible is the webview diff page, shown by the gallery stills below.Gallery stills from the gallery dev server on huge-blue (Chromium, 1000 px). Base
small-split, then this branch:Word changes inside lines, dark and light (an even +6 -6 split draws two green blocks, two red, one neutral):
120 files: the file list on its own surface, and the change bar in every header:
A header clicked to collapse its file:
Changelog
Observed / Not run
Observed:
diff-paint-perf.test.ts,diff-stat-blocks.test.tsanddiff-stream-splitter.test.tspass at940faca2aad(numbers above).diff-paint-perf.test.tsfails on base7a4b464b375.diff-virtualization.test.tspasses at940faca2aad(10/10, Chromium and WebKit). Run in onebun testprocess right after the perf file it timed out in setup (a cold dev-server warm-up); alone it passes.regenerate-settings-exports.shfor the committed exports;defaults_guard.py check-stamppasses.diff-stream-splitter.test.tsanddiff-stat-blocks.test.ts,tsc --noEmit, lint andprepush-gate.py.Not run:
Agent session: cosmic-gray
🤖 Generated with Claude Code