Repository navigation
Agent pane: one DOM insertion no longer restyles the whole pane (pane-level :has() rules to state attributes) - #28559
Conversation
…lta, rail wheel step and menu open An opt-in Chromium trace (PANE_HAS_MEASURE=1) of UpdateLayoutTree element counts, with a per-rule :has() ablation (PANE_HAS_ABLATION=1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…and cost each :has() rule alone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…s and computed-style digests Opt-in (PANE_HAS_PARITY=1): run at the commit before the change and at the change; both must match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…ed, and the trace says why a node restyles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
… costly :has() rules; add parity and grid-mark tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…er a pane with the costly :has() rules out Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
Found 1 test failure on Blacksmith runners: Failure
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
Five :has() rules put a :has() before a combinator whose last compound has no class or attribute (`*`, `:not(...)`, `span:not(...)`). Chromium then restyles everything under the elements it tracks for :has() whenever any node is inserted in the pane: about 850 elements (4-5 ms of style) per streamed row, rail tick or menu row. With the five gone, the same insertion restyles 43 (pane-has-restyle.browser.test.ts, Chromium trace). - Composer: ComposerBox marks Claude's compact grid on the box and Composer on its form (data-composer-grid), in place of :has(> .acpmux-chrome-area > .acpmux-composer-inner) on the box, the form and the dock. The asks stack's radius keys off a sibling :has on the composer. - PR links: TurnFooter and ReplyActions mark their action row (data-pr-dock) exactly when the dock draws its links (useHasPullRequestDock), in place of :has(> .acpmux-pr-dock). - Code block: the :has(.acpmux-pr-dock) copy of the label's truncation rule is gone; the label already truncates with the same declarations (conversation.css). - desktop.css: everything inside any label takes the label's cursor (`label *` inherit) in place of `label:has(checkbox, radio) *`; a locked choice's arrow reaches its contents by inheritance in place of a !important `label:has(...) :not(...)` rule. clickable-cursor.test.ts gains selectable-text label fixtures, which pass before and after. Tests: prDockAttribute.test.tsx (red on base: rows unmarked), composer-grid-attribute .browser.test.ts (red on base: no mark; checks it stands exactly where the old :has matched). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
… parity run keeps each state's computed styles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
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)
Gallery before/after for
States
Pick other states with a Passes: CI passes on CI passes on Written by web and size checks: green Shipped in:
|
|
Review (codex gpt-6-astra high, next-review): SHIP at 0d351ec No blocking findings. |
…-restyle # Conflicts: # webviews/src/pages/shared/desktop.css
|
Review (the owner lane's independent review, at its land request): SHIP at 5af41b3 Recorded by lane-gh-broker from this PR's owner lane, which asked to land this exact head after its independent review. next-autoland lands it once every red is the base's; a BLOCK at this head, a HOLD line or a hold label still stops it. |
|
Review (codex gpt-6.1-sol high fast, router-review): SHIP at 5af41b3 No correctness findings in the diff. Visual verdict (drain-ship, landing lane): SHIP at 5af41b3. Before and after match pixel for pixel (context menu dark, PR dock pills light), as a restyle-only perf change should; the menu is compact and fits. |
…-restyle # Conflicts: # webviews/src/agent-session/acpmux/ComposerBox.tsx
|
Review (codex gpt-6-astra, independent review box): SHIP at 46691e2 Carried from the review box's reply, where "SHIP at …" was glued to the end of a paragraph. Range reviewed: 6b2795d..46691e2 (base merge of the reviewed head 5af41b3). Follow-ups:
|
#28559 moved the box's radius rule to state attributes; the dock takes it with a child-scoped :has(> .acpmux-composer[data-composer-grid]). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-f71664ebfb6cf5206479
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Leo (rulebook): "when it comes to the left scroll rail thing, i already said we want it ultra fast ultra responsive, but the same goes for EVERYTHING IN THE WEB UI".
What was wrong
Any node inserted in the agent pane made Chromium restyle the whole pane. That covers a streamed reply's rows and tokens, a session rail tick, and a menu row. It cost 678 elements on the normal chat page and 2,186 on the 3,000-turn page, 4–9 ms of style per insertion. The draft rail investigation (#28540) found the effect. This PR measures it per rule and removes it.
The cause is one selector shape: a
:has()before a combinator whose last compound has no class or attribute (… :has(…) > *,… :has(…) > :not(…),… :has(…) > span:not(…),label:has(…) *). With such a rule anywhere in the page, Chromium invalidates whole subtrees under the elements it tracks for:has()on every insertion.:has()on the subject itself, or followed by a compound with a class (.x:has(…) > .y), is cheap. The opt-in Chromium trace below measures each of the page's 143:has()rules. With all 143 out, an insertion restyles 2 elements. With only the five below out, it restyles 43. Each of the five alone brings it back to about 870–900.Changes (five rules)
.acpmux-composer-box:has(> .acpmux-chrome-area > .acpmux-composer-inner) > .acpmux-composer-field-menu > *(and the 11 sibling rules on the same:has, on the box, the form and the dock)data-composer-gridthatComposerBoxsets on the box andComposersets on its form, for Claude's look (look !== "codex", the condition that already adds.acpmux-composer-inner). The asks stack's radius uses a sibling:has(~ .acpmux-composer[data-composer-grid])… .cv-turn-actions…:has(> .acpmux-pr-dock) > :not(.acpmux-pr-dock), … .cv-reply-actions:has(> .acpmux-pr-dock) > :not(…)and the matchingopacity: 1ruledata-pr-dockon the action row, set byTurnFooterandReplyActionsthroughuseHasPullRequestDock(), the sameprs?.lengthcondition under whichPullRequestDockdraws.cv-codeblock__header:has(.acpmux-pr-dock) > span:not(.cv-codeblock__actions):where(…, label[for], label:has(checkbox, radio)) * { cursor: inherit }:where(…, label) *: everything inside any label takes the label's cursorlabel:not([for]):has(disabled):not(:has(enabled)) :not(clickables) { cursor: default !important }The two desktop.css rewrites cascade to the same cursors in every fixture of
clickable-cursor.test.ts. That test gains four selectable-text label fixtures (a choice, a locked choice, a field label and its text field), which pass before and after. One edge differs: a descendant of a label that has its own non-clickable surface cursor rule now keeps that rule. Before,!important defaultoverrode it for a locked label, andautooverrode it for other labels inside.selectable. No current fixture or source hits that case.Specificity: the composer rules drop from
:has(…)to one attribute. I scanned every rule that sets the same properties on the same elements, and none sits in the range that drop would change. The style dumps below check this per state.Overlap: #28533 (bottom stack) is not touched. Its new
:has()rules are the cheap shapes (subject, or a class after the combinator). bottomStack.css's owncomposer-inner:hasrules are cheap too and stay. #27616 edits composerLocation.css lines 257–360 (the access menu). This PR edits lines 1280–1395.No new observer, timer or listener.
Performance
Same harness, same CI job and same runner for both arms:
test/pane-has-restyle.browser.test.ts(PANE_HAS_MEASURE=1), a Chromium trace ofUpdateLayoutTree.elementCountand duration. The job ran after, before, after, before by applying and reverting this PR'swebviews/srcdiff. The table gives the median of the two runs per arm. Pages:flows.html?mock&recents=demo(normal) andflows.html?mock&turns=3000&resident(3,000 turns).Visual parity
test/pane-has-parity.browser.test.ts(PANE_HAS_PARITY=1) captured 17 gallery states, each in light and dark: the composer (idle, focused, right-click menu open, long draft, image attached, queue, Codex harness), Claude's and Codex's looks, the three draft scrollbar placements, and the PR links (inline, hovered turn, one-first, no footer, code tail). All 34 stills are byte-identical between the arms in both rounds.Viewed after image: the PR links beside the turn's footer show
#131 atlas-web,#130 atlas-weband+2, with the footer's own buttons hidden until hover; in the composer the right-click menu sits over the prompt and the prompt keeps its grid (+ at left, mic and Send at right). Each is byte-identical to its before still.Tests
src/agent-session/acpmux/summary/prDockAttribute.test.tsx: a turn footer and a reply's actions carrydata-pr-dockexactly when the dock draws links. Red on the base half of the job, green on this PR.test/composer-grid-attribute.browser.test.ts: in Claude's look and Codex's, the mark stands exactly where the old:has()matched, and the box is still a grid. Red on base (Claude states), green here.test/clickable-cursor.test.ts: new label fixtures, green before and after.test/pane-has-restyle.browser.test.tsandtest/pane-has-parity.browser.test.ts: opt-in measurements, skipped unless their env var is set (no CI gate).Observed / Not run
bun run checkpassed on the head (in the same job).:has()shapes. The repo rules say no new CI gates, so the rule is documented in desktop.css's comment instead. Also not run: a native capture. Nothing native changed, and the webview stills above are pixel-identical.Changelog
Fixed: the agent pane no longer restyles the whole pane on every streamed row, rail tick or menu row.
Agent session: agent session
🤖 Generated with Claude Code