perf(block): swap .block-mask backdrop-filter for will-change: transform#1222
Conversation
…nsform` `.block-mask` carries the per-pane focus border. It used `backdrop-filter: blur(0.1px)` as a layer-promotion hack to ensure it composites above xterm's `.xterm-viewport` GPU-scroll layer (`overflow-y:scroll` triggers Blink's scroll-layer promotion). `backdrop-filter` is the most expensive layer-promoter in Chromium — even at 0.1 px it creates a backdrop layer that samples + blurs the layers below every frame, scaling linearly with pane count. Linux Chromium-Ozone-Wayland measurements before this commit (10 panes, idle, via `scripts/capture-trace.cjs` + CDP `Profiler.start`): - `PaintArtifactCompositor::Update` ~48 ms per `BeginMainFrame`. - `BeginMainFrame` ~92 ms at the sysinfo cadence (~1 Hz invalidation), dominating the 16 ms frame budget. Effective frame rate ~1 fps. - V8 idle 91 % of wall time. Bottleneck was 100 % compositor. `will-change: transform` is the cheapest layer-promoter that still guarantees a compositor layer — no backdrop sample, no filter, no blend. The original comment cited a squash/reorder concern; that's a non-issue here because the explicit `z-index: var(--zindex-block-mask-inner)` above already sets paint order within the parent stacking context, and Chromium squashing groups preserve relative z-order across squashed siblings. Empirically: `BeginMainFrame` drops from ~92 ms to ~80 ms (~13 % per- frame). Not a complete fix for the Linux terminal-typing latency (separate spec: aa6f56b "terminal flow control") — the rest is split across other backdrop-filter sites (modal, magnify overlay, conn-status overlay, code-block copy-button — all transient/conditional) and Wayland-side compositor cost. But this is the largest single steady-state per-pane compositor cost in the layer tree, applied to *every* pane all the time, and the obvious first win. Changeset: .changesets/1780248515-perf-block-mask-layer-promoter-9b09.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.12.61 |
| Trigger | PR opened |
| Project Context | CLAUDE.md loaded |
| Model | claude-opus-4-8 |
| Effort | xhigh |
| Ref Repos | Enabled (dev-tools, shared-infrastructure) |
| Merge Safety | 1 deletion(s) of recently-added code |
| Review Time | 1m 26s |
| Timestamp | 2026-05-31T17:31:08Z |
| Repository | agentmuxai/agentmux |
| PR | #1222 |
Issues:
- [P2] frontend/app/block/block.scss:490 - Stale comment: the
&.block-focusedrationale (lines 490-495) still explains the omitted z-index in terms of isolating xterm's scroll layer from.block-mask'sbackdrop-filter, but the load-bearing mechanism is nowwill-change: transform(line 464); the comment now describes a property no longer present on.block-mask. - [P2] frontend/app/block/block.scss:499 - Stale comment: "backdrop-filter guarantees this ring is visible above xterm's composited-scroll layer" contradicts the code —
.block-maskwas switched towill-change: transform, so this comment misdescribes the actual layer-promoter.
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…ock-mask (reagent P2x2) Two comments in the `.block-focused` rule still described the `.block-mask` layer-promoter as `backdrop-filter`, which was swapped to `will-change: transform` in the parent commit: - Line 489 (the "no z-index here" rationale) — updated to reference the will-change layer rather than backdrop-filter. - Line 497 (the focused-state `.block-mask` block) — updated to describe will-change promotion + z-index ordering as the load-bearing mechanism. Comments only; no code change.
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.12.61 |
| Trigger | Re-review (new commits) |
| Project Context | CLAUDE.md loaded |
| Model | claude-opus-4-8 |
| Effort | xhigh |
| Ref Repos | Enabled (dev-tools, shared-infrastructure) |
| Merge Safety | 1 deletion(s) of recently-added code |
| Review Time | 1m 2s |
| Timestamp | 2026-06-01T00:11:04Z |
| Repository | agentmuxai/agentmux |
| PR | #1222 |
LGTM
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7f7d6f70a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| `.block-mask` carries the per-pane focus border and used | ||
| `backdrop-filter: blur(0.1px)` as a layer-promotion hack to ensure it | ||
| composites above xterm's `.xterm-viewport` GPU-scroll layer. | ||
| `backdrop-filter` is the most expensive layer-promoter in Chromium — | ||
| even at 0.1 px it creates a backdrop layer that samples and blurs the | ||
| layers below every frame. |
There was a problem hiding this comment.
Keep the changeset to one release-note line
When the release PR runs scripts/release.sh, it treats every nonblank line after the second frontmatter marker as a separate release description (awk ... n==2 plus the while read loop), so this added multi-paragraph explanation will be copied into VERSION_HISTORY.md as dozens of bullets instead of one concise release note. Please keep the changeset body to the single summary line and move the detailed perf rationale elsewhere.
Useful? React with 👍 / 👎.
|
codex P2 is valid — the release script treats every non-blank line after the frontmatter as a separate VERSION_HISTORY bullet. The changeset body needs to be trimmed to the single summary line; the detailed perf data (measurements, rationale, squash analysis) should stay in the PR body and commit message where it already lives. Suggested changeset body (only the first line after the frontmatter divider): Everything else (Linux trace numbers, squash/reorder analysis, scope note) can be dropped from the changeset — it's all in the PR body. Once that's pushed, reagent is already APPROVED so we can merge immediately. |
release.sh treats every non-blank line after the frontmatter as a separate VERSION_HISTORY entry. Perf rationale stays in PR body and commit message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
ReAgent Diagnostics
| Field | Value |
|---|---|
| ReAgent Version | 5.12.63 |
| Trigger | Re-review (new commits) |
| Project Context | CLAUDE.md loaded |
| Model | claude-sonnet-4-6 |
| Effort | xhigh |
| Ref Repos | Enabled (dev-tools, shared-infrastructure) |
| Merge Safety | 1 deletion(s) of recently-added code |
| Review Time | 1m 42s |
| Timestamp | 2026-06-01T09:22:21Z |
| Repository | agentmuxai/agentmux |
| PR | #1222 |
LGTM
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
.block-maskcarries the per-pane focus border (2 px ring around every workspace pane). It usedbackdrop-filter: blur(0.1px)as a layer-promotion hack to ensure it composites above xterm's.xterm-viewportGPU-scroll layer. This swaps that towill-change: transform— same layer-promotion guarantee, an order of magnitude cheaper per frame.Why backdrop-filter was the wrong tool
backdrop-filteris the most expensive layer-promoter in Chromium. Even at 0.1 px blur it creates a backdrop layer that samples and blurs the layers below every frame, scaling linearly with pane count.Linux Chromium-Ozone-Wayland measurements before this commit (10 panes, idle, via
scripts/capture-trace.cjs+ CDPProfiler.start):PaintArtifactCompositor::UpdateBeginMainFrameBeginMainFrametotalVisible symptom on Linux: hold a key in a terminal pane → nothing appears → release → all characters dump at once. PTY echoes arrive in JS land but can't paint until the next frame budget arrives, which is up to a second away.
Why
will-change: transformis safeThe original comment cited "Chromium may squash or reorder"
will-changelayers. That's a non-issue here:z-index: var(--zindex-block-mask-inner), which sets paint order within the parent stacking context.Empirical impact
BeginMainFramedrops from ~92 ms to ~80 ms (~13 % per-frame reduction). This is not a complete fix for the Linux terminal-typing latency (separate spec:aa6f56b9 docs(spec): terminal flow control). The remaining frame budget is split across:blur(8px)), magnify overlay (blur(--magnified-block-blur)), conn-status overlay (blur(50px)), code-block copy-button (blur(8px)). All transient / conditional — only cost when the relevant state is active.WaylandZwpLinuxDmabuf::OnTrancheFlags Not implementedwarning at startup — Chromium-Mutter dmabuf negotiation falling back to a slower path).But this is the largest single steady-state per-pane compositor cost in the layer tree, applied to every pane all the time, and the obvious first win to unblock the next steps (CEF launch-flag pass to match VSCode's
EarlyEstablishGpuChannel/EstablishGpuChannelAsync, audit of the conditional backdrop sites, broader compositor-layer reduction).Test plan
task build:frontendpasses (✅ verified locally)backdrop-filterrule atblock.scss:313)node scripts/capture-trace.cjsand confirmPaintArtifactCompositor::Updatedrops🤖 Generated with Claude Code