gallery: the mock app, cmux NEXT's whole window on one URL, with a perf harness
What
The gallery's mock app: cmux NEXT's whole window on one URL, webviews/src/gallery/app.html. It shows the sidebar, the tab strips and each pane's real web surface (agent pane, transcript, composer, menus, Session Info, New Tab, Home) on the in-page mock acpmux host and gallery fixtures. The native chrome around them is approximated.
Leo (2026-11-04): "I want the mock web view of the app to be legit like 'oh we can see the whole unified piece and it doesn't lag the f*** out of my computer/browser when I open it' and stuff".
Every condition is a URL key, and adding an axis is one row in app/axes.ts (the table is in ENTRIES.md, "The mock app"):
/gallery/app.html?fixture=agent-pane.turn-error-card/disk-full&density=compact&theme=Catppuccin%20Mocha
/gallery/app.html?layout=two&fixture2=app.window/new-tab&colorScheme=light
/gallery/app.html?windows=pro14,air13,1280x800&script=streaming
/gallery/app.html?script=tools&cpu=4&slot=custom-element
- Fixture, layout, window size, several sizes (
fixture,fixture2,layout,window,windows,zoom,sidebar,chrome). - Density and scale, theme and customization (
density,scale,interfaceScale,fontFamily,fontSize,theme,colorScheme,accent,background,locale,reducedMotion,highContrast). These are the stage's existing env keys, passed to every pane. - Dynamic scripts (
script=streaming|tools|error,loop=0, fromframe/scripts.ts). A variant's chat comes alive on a loop through the pane's realreceive, and the script pauses while the page is hidden. - Simulated CPU contention (
cpu=N). The page gets 1/N of the main thread. The harness below uses CDP throttling instead. - Extension slot (
slot=custom-element). A registry.js-style renderer that draws a shadow-DOM custom element, registered through the pane's realcmuxAcpmuxRegistry. - Ready signal for the UI judge:
data-mock-app-ready="1"on<html>, plus a{type: "cmux-mock-app", status: "ready"}message to the parent andwebkit.messageHandlers.cmuxPreview.
Changes that make it open fast:
- Per-entry loading. A new
virtual:cmux-gallery/entry-indexmodule maps each entry id to its file, so a stage frame loads only its own entry and no longer evaluates every*.gallery.ts. If the index is stale, the frame falls back to the full store. Saving a gallery file updates the index without a full reload. - The mock app itself is plain DOM with no framework. Windows that are offscreen mount only when they scroll in, and a resize re-lays the panes out in place without reloading.
scripts/gallery-matrix/app-perf.ts (with app-perf-budgets.json) measures each scenario in Chromium: first frame, ready, long tasks after ready, idle CPU (CDP TaskDuration over a 5 s window), idle rAF and timers per second, heap, DOM nodes, and CLS after first paint. It prints ::warning for a budget miss and exits 0 unless --strict is passed.
This is not a CI gate. The brief asked for "a CI budget that warns", but the repo CLAUDE.md forbids new CI gates and budgets (Lawrence, 2026-10-23). So the budget runs when a lane runs it (the command is in ENTRIES.md, "Open-and-idle cost"), and nothing in .github/ changes.
Overlap with #35270
#35270 (a lane whose session has ended) also adds gallery/app.html: one window over the app.window scenes in React, with model and browser tests. This PR covers more ground: any agent-pane fixture, the full axis table, scripts, CPU contention, the extension slot, the per-entry index that speeds up the whole gallery, the perf harness, and the chrome=native mode the view shell uses. The two touch the same paths, so only one should land. My take is to land this one and port #35270's browser test onto it; a coordinator decides.
Performance
Fleet Chromium, headless, CPU 1x, 2 s settle, then a 5 s idle window. "Before" is the base (4ce339304e67), measured with the same harness. The budgets are a first frame under 500 ms, no long task over 50 ms after ready, about 0% idle CPU and CLS 0.
| Scenario | Before: first frame | After: first frame | Before: idle CPU | After: idle CPU | Before: long task max after ready | After | Before: CLS | After |
|---|---|---|---|---|---|---|---|---|
| Mock app, whole window (new) | n/a | 73 ms | n/a | 0.1% | n/a | 0 | n/a | 0 |
| Mock app, two panes, compact, light (new) | n/a | 93 ms | n/a | 0.1% | n/a | 0 | n/a | 0.0078 |
| Mock app, three window sizes (new) | n/a | 77 ms | n/a | 0.1% | n/a | 0 | n/a | 0.0001 |
Mock app, script=streaming (animating on purpose) (new) | n/a | 69 ms | n/a | 6.8% | n/a | 0 | n/a | 0.0001 |
| Gallery landing (one entry) | 366 ms | 274 ms | 0.1% | 0.1% | 125 ms | 109 ms | 0 | 0 |
app.window scene | 113 ms | 106 ms | 0.1% | 0.1% | 197 ms | 242 ms | 0.01 | 0.0102 |
app.window, every variant | 86 ms | 110 ms | 5.7% | 3.3% | 160 ms | 164 ms | 0.016 | 0.0151 |
| Whole gallery (browse, 24 live previews) | 2132 ms | 318 ms | 12.8% | 9.1% | 120 ms | 126 ms | 0.0001 | 0.4966 |
The mock app meets every budget at 1x: first frame under 100 ms, no long task after ready, 0 rAF and 0 timers while idle. The streaming row is expected to use CPU, because it animates.
Still over budget, all in the older gallery views, for a follow-up:
- Browse idle CPU. This comes from the harness's own loops: the
shift.tsbox tracker rAF, the play runner'swaitForrAF poll, and themarkReadyWhenStilltimer inframe/main.ts. - Browse CLS. It rose because the previews now paint sooner and then settle.
- The hidden "next" iframe preload in
Stage.tsx.
At CPU 4x on the base, the browse view used 67.8% idle CPU with a 626 ms longest task, and the variants view used 93.7% idle CPU. I have not run the 4x pass on this branch yet.
Testing
bun run checkinwebviews/passed on the fleet (fast checks) at this head, aftervp fmt.- I ran the perf harness on the fleet at the base and at this head (the table above).
- I have not run it in a real WKWebView; a capture mini does that in the native shell PR.
Observed / Not run
- Observed: the harness numbers above; the mock app reaches ready in all four scenarios; and
vp fmt, lint and typecheck are clean. - Not run: the 4x pass at head, and a manual pass in desktop Chrome on a person's machine.
Changelog
none
๐ค Generated with Claude Code
Agent session: agent session
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith ยท Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Adds the gallery's mock app at mock-app.html: cmux NEXT's whole window โ sidebar, tab strips, traffic lights, and each pane's real stage surface โ shown on one URL. Every condition is a URL query key in mock-app/axes.ts: fixtures and layouts, window sizes, theme and density, dynamic chat scripts, CPU contention, and an extension slot. Scripts, CPU contention, and extension slots run on the main pane only, since the panes share one main thread. Stage frames now load only their own entry through a new virtual entry-index module, which keys each file by its exported entry's id; keying by the first id literal found mis-mapped 108 of 428 files (nested picker or scenario ids sit above the entry's own), making the whole registry load.
The mock app moved from app.html so it sits beside #35270's page of the same name, which landed; the view shell, dev entries, and static build input follow the new path. The dev server's page matcher now routes /gallery/mock-app.html so it no longer falls through to the default page, and a route test covers it serving beside app.html. No changelog.
Performance
- At 1x CPU the mock app meets every budget: first frame under 100 ms, no long task after ready, ~0% idle CPU, no layout shift; the
script=streamingrow animates on purpose and is the expected exception. - The per-entry loading cut the whole-gallery browse view's first frame from 2132 ms to 318 ms.
- Browse idle CPU and CLS remain over budget because of the older views' own loops (CLS rose since previews now paint sooner), left for a follow-up.
scripts/gallery-matrix/app-perf.tsmeasures each scenario against budgets inapp-perf-budgets.jsonand prints warnings on misses; it is lane-run, not a CI gate (CLAUDE.md).