Skip to content

Agent pane: one DOM insertion no longer restyles the whole pane (pane-level :has() rules to state attributes) - #28559

Merged
teamleaderleo merged 11 commits into
feat-cmux-nextfrom
lane/ux-has-restyle
Oct 31, 2026
Merged

teamleaderleo merged 11 commits into
feat-cmux-nextfrom
lane/ux-has-restyle

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

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)

rule where replaced by
.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) composerLocation.css data-composer-grid that ComposerBox sets on the box and Composer sets 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 matching opacity: 1 rule summary/summary.css data-pr-dock on the action row, set by TurnFooter and ReplyActions through useHasPullRequestDock(), the same prs?.length condition under which PullRequestDock draws
.cv-codeblock__header:has(.acpmux-pr-dock) > span:not(.cv-codeblock__actions) summary/summary.css removed: it repeated, declaration for declaration, the label's unconditional rule in conversation.css
:where(…, label[for], label:has(checkbox, radio)) * { cursor: inherit } pages/shared/desktop.css :where(…, label) *: everything inside any label takes the label's cursor
label:not([for]):has(disabled):not(:has(enabled)) :not(clickables) { cursor: default !important } pages/shared/desktop.css the subject rule alone. The locked label's contents inherit its arrow through the label rule above

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 default overrode it for a locked label, and auto overrode 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 own composer-inner :has rules 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 of UpdateLayoutTree.elementCount and duration. The job ran after, before, after, before by applying and reverting this PR's webviews/src diff. The table gives the median of the two runs per arm. Pages: flows.html?mock&recents=demo (normal) and flows.html?mock&turns=3000&resident (3,000 turns).

metric before after delta harness or run link
elements restyled per inserted row node, normal page 678 41 −94% run
style ms per inserted node, normal page 3.6–4.2 0.55–0.72 −83% same
elements restyled per inserted row node, 3,000 turns 2,186 41–46 −98% same
style ms per inserted node, 3,000 turns 8.6–9.0 0.77–1.01 −90% same
elements restyled per streamed delta (12 chars / 16 ms, 2 s), normal 185 45 −76% same
style ms per streamed delta, normal 1.42 0.64 −55% same
largest recalc while streaming (p95 elements), 3,000 turns 386 26 −93% same
style ms per streamed delta, 3,000 turns 1.49 0.69 −54% same
rail wheel step, elements restyled (p50), 3,000 turns 2,615 166 −94% same
rail wheel step style ms p50 / p95, 3,000 turns 9.8 / 10.5 1.4 / 4.1 −86% / −61% same
slash menu open, elements restyled / style ms, normal 1,474 / 7.5 259 / 2.2 −82% / −71% same
slash menu open, elements restyled / style ms, 3,000 turns 4,466 / 17.3 243 / 2.5 −95% / −86% same
streaming frame p95 (headless 60 Hz) 16.8 ms 16.8 ms 0 same; headless Chromium paces frames at 60 Hz and dropped none in either arm, so this metric shows nothing here

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.

state before after
Composer, right-click menu open (dark) before after
PR links beside a turn (light) before after

Viewed after image: the PR links beside the turn's footer show #131 atlas-web, #130 atlas-web and +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 carry data-pr-dock exactly 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.ts and test/pane-has-parity.browser.test.ts: opt-in measurements, skipped unless their env var is set (no CI gate).

Observed / Not run

  • Observed: the runs above. bun run check passed on the head (in the same job).
  • Not run: a lint for new costly :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

…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
@github-actions github-actions Bot added the risk:low Risk tier (docs/ci/risk-tiers.md) label Oct 30, 2026
…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
teamleaderleo and others added 2 commits October 30, 2026 22:42
…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
@blacksmith-sh

blacksmith-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

teamleaderleo and others added 2 commits October 30, 2026 23:33
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
@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:low Risk tier (docs/ci/risk-tiers.md) labels Oct 30, 2026
… 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
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 46691e2: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr28559 -f ref=lane/ux-has-restyle -f platform=macos -f backend_mode=local

Gallery before/after for 46691e2: 16 states rendered, 0 images differ, 2654 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41204790068 -R manaflow-ai/cmux -n gallery-advisory-28559-46691e2413874a28b87b38104875ce5961a7c7c7 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.access-offer/turn-no-credits Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-no-credits Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-low-credits Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-low-credits Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-no-offer Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-no-offer Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.background-activity/duration-columns Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/duration-columns Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/pinned-live-tail-light Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/pinned-live-tail-light Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/pinned-live-tail-dark Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/pinned-live-tail-dark Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/after-latest-message-light Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/after-latest-message-light Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/after-latest-message-dark Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/after-latest-message-dark Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/compact-pill-light Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/compact-pill-light Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/compact-pill-dark Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/compact-pill-dark Apple System Colors unchanged 0% own summary.css
agent-pane.background-activity/side-panel-light Apple System Colors Light unchanged 0% own summary.css
agent-pane.background-activity/side-panel-light Apple System Colors unchanged 0% own summary.css
agent-pane.coderouter-offer/claude-key Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/claude-key Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/deepseek-key Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/deepseek-key Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/sign-in Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/sign-in Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/rate-limited Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/rate-limited Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/on-coderouter Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/on-coderouter Apple System Colors unchanged 0% own TurnRows.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Passes: CI passes on 46691e2413.

CI passes on 46691e2413 (run 41204790149 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Shipped in:

@teamleaderleo
teamleaderleo marked this pull request as ready for review October 31, 2026 00:13
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, next-review): SHIP at 0d351ec

No blocking findings.

@cmux-bot cmux-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmux-bot approves 0d351ec: an independent review passed this exact head (next-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

…-restyle

# Conflicts:
#	webviews/src/pages/shared/desktop.css
@teamleaderleo

teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator Author

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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol high fast, router-review): SHIP at 5af41b3

No correctness findings in the diff.
Follow-ups: none

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
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol high fast, router-review): SHIP at 46691e2

The base merge of 6b2795d (one ComposerBox.tsx conflict, which keeps both data-composer-grid and data-attachment-view) was reviewed as part of the full diff. No correctness findings.
Follow-ups: none

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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:

  • [P2] [follow-up] webviews/test/pane-has-parity.browser.test.ts:111-118 records hashes but never compares them with a baseline; add baseline digest/pixel comparisons.

@teamleaderleo
teamleaderleo merged commit de83900 into feat-cmux-next Oct 31, 2026
169 of 171 checks passed
@teamleaderleo
teamleaderleo deleted the lane/ux-has-restyle branch October 31, 2026 13:57
teamleaderleo added a commit that referenced this pull request Nov 1, 2026
#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

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 46691e24 Deployed Oct 31, 2026 by teamleaderleo via cmux-tui tree preflight #89966
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant