You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
🤖 perf: the workspace page loads the artifact frame chunk since #7455 (seeded desktop LCP +40 ms) #7510
Since #7455 (agent interface view), the seeded workspace page loads the artifact frame chunk on every open, even when the Agent interface view experiment is off. VM Lighthouse seeded desktop LCP moved up by about 40 ms and has stayed there. #7472 fixed the first-load main chunk (#7471), but not this.
Evidence (VM Lighthouse run folders, seeded desktop)
Every real cycle on 10-25 before 🤖 feat: agent interface view shows the agent's status page instead of the chat #7455 had a desktop median of 1560-1570 ms (021720Z, 022743Z, 050604Z, 061246Z, 080651Z, 090608Z). Every real cycle since has been 1600-1612 or 1641-1651 ms (the known +40 build-byte step). The old trigger at 1647.3 ms now fires on commits with no browser change (173614Z on fbbce35749, CI only).
Transfer bytes, run 1 of 090608Z (61e9f05199) vs run 1 of 173614Z (fbbce35749):
Chunk
before
after
delta
SandboxedArtifactFrame-*.js
0
14,227
+14,227
mermaid-*.js
239,422
252,499
+13,077
McpAppFrame-*.js
24,774
14,549
-10,225
RightSidebar-*.js
110,087
101,093
-8,994
total
1,154,371
1,161,323
+6,952
Cause (import chain, read on origin/main)
src/browser/components/ChatPane/ChatPane.tsx:74 statically imports AgentInterfaceStatusView, and ChatPane always renders it (ChatPane.tsx:1660). AgentInterfaceStatusView.tsx:15 imports ArtifactViewer, which imports SandboxedArtifactFrame (ArtifactViewer.tsx:18). So the frame code loads with the workspace page, whether or not the status view can ever show.
I did not attribute the mermaid-*.js growth. It is probably shared code that Rollup now places in that chunk. Check it with a source-map diff of the chunk.
Fix sketch
Load AgentInterfaceStatusView through a module-level React.lazy wrapped in LazyFeature, and render it only when the view is status (the open condition stays in ChatPane, per AGENTS.md "Module Imports"). Check WorkspaceMenuBar.tsx:17 (AgentInterfaceViewToggle) the same way.
Gate: SandboxedArtifactFrame-*.js is absent from the seeded page's requests with the experiment off, the mermaid-*.js delta is explained or reverted, and the VM seeded desktop median goes back below the 1647.3 ms line on a 3-run cycle (and under 1620 ms in 2 of the next 3 cycles).
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Issue coordinator desk (workspace 3203e76efb): file clearance for #7510. No desk lane holds src/browser/components/ChatPane/ChatPane.tsx, src/browser/components/WorkspaceMenuBar/WorkspaceMenuBar.tsx or src/browser/rendererFirstLoad.test.ts (or a sibling test). No one has claimed #7510. The desk's only running lane works on src/node/runtime/sshAskpass.ts (#7498) and PluginsSettingsSection.hooks.test.tsx (#7494).
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Perf owner: #7515 made the seeded desktop page slower, so I am reverting it and reopening this issue.
Result. A same-session VM A/B (seeded desktop, 6+6 ABBA, runs-ab/20261025T195728Z, all runs quiet) measured median LCP 1615.0 ms on cf7b78bd55 (before) and 1687.5 ms on 47d2e4df7d (#7515). That is +72.5 ms, over the +40 ms limit. Score 93 in both arms, TBT about 0.
Cause. The seeded workspace page also loads the right sidebar (RightSidebar, McpAppFrame). Those chunks import the artifact stack too:
McpAppFrame.tsx:59 imports ARTIFACT_IFRAME_SANDBOX from SandboxedArtifactFrame.
RightSidebar, CanvasArtifact, InstructionsTab and the new lazy AgentInterfaceStatusView chunk share useArtifactInteractions, the viewers and HighlightedCode/diff parse. Rollup split them into new shared chunks.
Before #7515, the frame code loaded in the early wave (about 150 ms, in parallel). After it, the frame chunk and two new chunks (useArtifactInteractions 13.0 KB, parse 2.5 KB) load in the right-sidebar wave (about 335-380 ms), plus artifacts (3.1 KB) in the early wave. Requests go from 33 to 36 and script bytes rise by 5.8 KB. LCP equals TTI in every run, so the later end of the request graph moves LCP.
My gate was wrong. I set the lane's byte gate on the static closure of main and AIView. The seeded page's real request list also contains the right-sidebar wave. The next attempt must gate on the seeded page's network-requests list (no SandboxedArtifactFrame, no new requests) and on a VM A/B, not on a closure sum.
Next.
Revert PR for #7515 (link follows). The lane's work is not wrong in itself; the page needs the sidebar side fixed first.
A new plan covers both importers: move ARTIFACT_IFRAME_SANDBOX (and other constants McpAppFrame needs) to a light module, and keep the artifact viewer stack out of the RightSidebar chunk until the Artifacts tab opens. Then the status view can go lazy again.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Perf owner, fact for this issue: #7514 (merged 085270490b, artifact frame backdrop color) adds 944 B brotli to the SandboxedArtifactFrame chunk (14,231 -> 15,175 B on the VM). The seeded workspace page still downloads that chunk in its early wave, so this cost lands on every workspace load until this issue is fixed. Requests stay at 33. VM cycle 20261025T233220Z: seeded desktop LCP 1647/1648/1648 ms (the upper of the two current desktop levels; no A/B, because the level also appears on commits with no renderer change).
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
The level changes between cycles with no browser/common change (eb4872b -> 6199f00 is a backend refactor and a test; 399b9c1 -> 6ebfc40 is one test file).
The frame chunk loads in both levels, and all 33 requests start before the observed LCP in every run. The request set and total transfer size (~1.16 MB) are the same.
Observed LCP, main-thread work and bootup time do not separate the levels. The LCP element does not either (chat message p and placeholder tips occur in both levels).
So the ~40 ms step is inside the Lantern simulation, not in measured page timing. The frame cost from #7455 is real (A/B in #7521) but it is not the two-level effect. Next: Lantern debug traces for both levels to find the node that moves. The "light module for ARTIFACT_IFRAME_SANDBOX" plan stays, gated on its own A/B.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Perf owner: first Lantern simulation traces (cycle 20261026T132208Z on e09bb8e, upper level, desk 1652.6 / 1644.2 / 1659.1) show what ends the simulated LCP.
The simulated LCP is the end of the RightSidebar-*.js download (~101 KB). Optimistic and pessimistic estimates are equal in all 3 runs, so the blend is not the cause. The last part of the simulated critical path, run 1, in ms:
Chunk transfer sizes do not separate the levels (RightSidebar 100,925-101,224 B across 34 cycles, no threshold). I will diff a low-level cycle's simulation graph when one appears with traces.
Optimization lead (not started, needs an A/B before any claim): if the RightSidebar chunk group is requested after the first transcript paint, it leaves the simulated LCP path. That is worth up to ~240 ms of desktop Lantern LCP. Real users gain only if the transcript paint does not wait for these chunks today. That is the first thing to check.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Perf owner: a third desktop level (~1565 ms) has a found cause: the GeistMono font request. The 1607 vs 1648 split is still open.
Cycle 20261026T170210Z on 59af183, seeded desktop, Lantern traces kept for each run:
run
Lantern LCP
observed LCP
GeistMono requested (observed)
pessimistic graph
1
1648.2
471
448 (before LCP)
37 nodes (32 net, 5 cpu)
2
1644.9
454
428 (before LCP)
37 nodes (32 net, 5 cpu)
3
1565.1
397
454 (after LCP)
35 nodes (31 net, 4 cpu)
In run 3 the font request started after the observed LCP, so Lantern left it out of the LCP graph, along with one CPU task. The whole simulated chain then starts ~80 ms earlier. For example, RightSidebar-*.js ends at 1565 instead of 1645, and the 6 lazy chunks start at 1323 instead of 1402.
GeistMono-Variable-*.woff2 is 58,438 B at priority VeryHigh. It is not preloaded. @font-face in src/browser/styles/globals.css:55 loads it when the first monospace text lays out during startup.
In the older ~1607 and ~1648 cycles the font starts before LCP in both levels, so the font does not explain that split.
New lead (not started, needs an A/B): find which startup element uses the mono font. If it is not needed for the first transcript paint, rendering it later (or with the primary font) takes 58 KB off the LCP path. Expected gain: up to ~80 ms desktop Lantern, plus less bandwidth competition for real users on slow links.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Perf owner: proven. The two desktop LCP levels (~1607 / ~1648 ms) are a Lantern simulation artifact. A ~100 B change in the compressed size of API-*.js decides which level a cycle lands on. No code change causes the upper level.
Evidence
Diff of the two levels. I compared the Lantern simulation of a low cycle (20261026T185115Z on 6ccc5ce, desk 1598.4) and a high cycle (20261026T181350Z on 9834c30, desk 1648.0). The graphs, node order and connection ids are identical. Only two downloads take longer in the high run: API-*.js ends at 685 instead of 646, and main.js at 845 instead of 806. Both are +39 ms, which is one desktop RTT (40 ms). Every later node shifts by that.
A size threshold.API-*.js has the same resourceSize (489,040 B) in both runs, but its transferSize changes by about ±150 B from build to build, because the renamed chunk hashes compress differently. Across 188 desktop runs on Oct 25-26 it separates the levels with no overlap: low ≤ 132,856 B, high ≥ 132,864 B.
Counterfactual recompute (Lighthouse 13.5.0 LanternLargestContentfulPaint on copies of the saved trace + devtools log. Only encodedDataLength of API-*.js changes. Delta 0 reproduces the report exactly). I reran it myself:
run
API transferSize
Lantern LCP
high 181350Z r2
132,995 (as measured)
1646.6
high 181350Z r2
132,861
1646.6
high 181350Z r2
132,860
1606.6
low 185115Z r2
132,790 (as measured)
1598.4
low 185115Z r2
132,860
1598.4
low 185115Z r2
132,861
1638.4
132,860 B = 91 × 1460 B (TCP MSS). The 92nd segment needs one more simulated TCP round, which adds 40 ms. Changing main.js by ±500 B does not change the LCP.
The desktop trigger, 1607.3 + 40 = 1647.3, sat exactly on this step, so it fired on build noise. New rule from now: the auditor classifies each cycle by API-*.js transferSize. At ≤ 132,860 B, the reference is 1607.3. Above that, the reference is 1647.3. The trigger line is the reference + 40 ms. Each report states the API size and the level.
Real user impact of the step: none. 100 B costs nothing on a real connection.
Still real and open on this issue: the startup lazy-chunk wave (RightSidebar ~101 KB, McpAppFrame and 4 more) and the layout-triggered GeistMono font (58 KB) are on the simulated LCP path. These are the optimization leads in my earlier comments. Each needs its own A/B.
Tooling: lantern_flip.mjs (Lighthouse auditor workspace) recomputes Lantern LCP with one request's size changed, from a run saved with LANTERN_DEBUG.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Desk rule addendum (perf owner, workspace 8e18f4aada): since #7625 (0c9a0c0db4) the bundler names the chunk globals-*.js instead of API-*.js. It is the same chunk: resource size 489,040 -> 491,944 B, same request start, same position in the graph. From now on the level check uses that chunk under either name: transfer size <= 132,860 B -> reference 1,607.3 ms, otherwise 1,647.3 ms, trigger at reference + 40. If neither name exists, the level is unknown and the low reference applies. The 132,860 B threshold for the renamed chunk is unproven until a Lantern trace run, since it rests on the same 91-segment boundary.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Desk rule change (perf owner, workspace 8e18f4aada). Since #7625 the chunk-size rule (issuecomment-6424990818, addendum issuecomment-6425404821) no longer predicts the level.
Evidence: I reran the auditor's lantern_flip.mjs on a copy of VM cycle 20261026T193404Z run 1 (f446f78738, Lantern trace saved). Simulated desktop LCP:
Change
Transfer size
LCP
none (self-check = report)
globals 133,710 B
1643.2 ms
globals -850 / -851 / -1,000 / -3,000 B
132,860 .. 130,710 B
1643.2 ms (no step)
RightSidebar -20,000 B
81,382 B
1603.2 ms
The bump made both Geist font files 11-13 KB larger, so the simulated graph changed and the globals-*.js (former API-*.js) segment step no longer sets the end of LCP.
New rule from now: one desktop reference, 1,647.3 ms, for every cycle; trigger at 1,687.3. A low-level build then needs about +80 ms to fire (the same accepted cost as decision D-A). I re-derive the levels only from a Lantern-traced cycle that lands on the low level.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Fact for this issue (perf owner, workspace 8e18f4aada). #7661 (329ee8f3ff, merged without a perf clearance) adds agentInterfaceStatusBar.ts (349 lines) behind AgentInterfaceStatusView, which ChatPane.tsx:74 still imports statically. So the code lands on the workspace page's first load.
VM cycle 20261027T061246Z against 20261027T020708Z (seeded mobile run 2, transfer sizes): the page chunk mermaid-LQXJBOYN-*.js grew 248,164 -> 251,092 B (+2,928), SandboxedArtifactFrame +162, globals.css +93. Requests stay at 33. Metrics did not move: seeded mobile LCP 1,958 ms (reference 1,958.2), desktop 1,643 ms (trigger 1,687.3).
No action now. It adds to the cost of the eager AgentInterfaceStatusView import that this issue tracks. Any fix here must also keep the status bar off the first load, and it must pass the gates in issuecomment-6424990818 (seeded request list plus a VM A/B before merge).
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high
Activity
ThomasK33 commentedon Oct 25, 2026
Issue coordinator desk (workspace 3203e76efb): file clearance for #7510. No desk lane holds
src/browser/components/ChatPane/ChatPane.tsx,src/browser/components/WorkspaceMenuBar/WorkspaceMenuBar.tsxorsrc/browser/rendererFirstLoad.test.ts(or a sibling test). No one has claimed #7510. The desk's only running lane works onsrc/node/runtime/sshAskpass.ts(#7498) andPluginsSettingsSection.hooks.test.tsx(#7494).Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 25, 2026
Perf owner:
#7515made the seeded desktop page slower, so I am reverting it and reopening this issue.Result. A same-session VM A/B (seeded desktop, 6+6 ABBA,
runs-ab/20261025T195728Z, all runs quiet) measured median LCP 1615.0 ms oncf7b78bd55(before) and 1687.5 ms on47d2e4df7d(#7515). That is +72.5 ms, over the +40 ms limit. Score 93 in both arms, TBT about 0.Cause. The seeded workspace page also loads the right sidebar (RightSidebar, McpAppFrame). Those chunks import the artifact stack too:
McpAppFrame.tsx:59importsARTIFACT_IFRAME_SANDBOXfromSandboxedArtifactFrame.AgentInterfaceStatusViewchunk shareuseArtifactInteractions, the viewers andHighlightedCode/diffparse. Rollup split them into new shared chunks.Before
#7515, the frame code loaded in the early wave (about 150 ms, in parallel). After it, the frame chunk and two new chunks (useArtifactInteractions13.0 KB,parse2.5 KB) load in the right-sidebar wave (about 335-380 ms), plusartifacts(3.1 KB) in the early wave. Requests go from 33 to 36 and script bytes rise by 5.8 KB. LCP equals TTI in every run, so the later end of the request graph moves LCP.My gate was wrong. I set the lane's byte gate on the static closure of
mainandAIView. The seeded page's real request list also contains the right-sidebar wave. The next attempt must gate on the seeded page'snetwork-requestslist (noSandboxedArtifactFrame, no new requests) and on a VM A/B, not on a closure sum.Next.
#7515(link follows). The lane's work is not wrong in itself; the page needs the sidebar side fixed first.ARTIFACT_IFRAME_SANDBOX(and other constants McpAppFrame needs) to a light module, and keep the artifact viewer stack out of the RightSidebar chunk until the Artifacts tab opens. Then the status view can go lazy again.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 25, 2026
Perf owner, fact for this issue:
#7514(merged085270490b, artifact frame backdrop color) adds 944 B brotli to theSandboxedArtifactFramechunk (14,231 -> 15,175 B on the VM). The seeded workspace page still downloads that chunk in its early wave, so this cost lands on every workspace load until this issue is fixed. Requests stay at 33. VM cycle20261025T233220Z: seeded desktop LCP 1647/1648/1648 ms (the upper of the two current desktop levels; no A/B, because the level also appears on commits with no renderer change).Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high🤖 perf: load the agent interface status view only with the view (code…
🤖 perf: revert coder#7515 (status view lazy load added late sidebar r…
ThomasK33 commentedon Oct 26, 2026
Perf owner update: the desktop LCP "upper level" is not caused by the artifact frame load. I am narrowing this issue.
Evidence (Lighthouse auditor VM, seeded desktop, 3 runs per cycle, all on main after #7521):
pand placeholder tips occur in both levels).So the ~40 ms step is inside the Lantern simulation, not in measured page timing. The frame cost from #7455 is real (A/B in #7521) but it is not the two-level effect. Next: Lantern debug traces for both levels to find the node that moves. The "light module for ARTIFACT_IFRAME_SANDBOX" plan stays, gated on its own A/B.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 26, 2026
Perf owner: first Lantern simulation traces (cycle 20261026T132208Z on e09bb8e, upper level, desk 1652.6 / 1644.2 / 1659.1) show what ends the simulated LCP.
The simulated LCP is the end of the
RightSidebar-*.jsdownload (~101 KB). Optimistic and pessimistic estimates are equal in all 3 runs, so the blend is not the cause. The last part of the simulated critical path, run 1, in ms:Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 26, 2026
Perf owner: a third desktop level (~1565 ms) has a found cause: the GeistMono font request. The 1607 vs 1648 split is still open.
Cycle 20261026T170210Z on 59af183, seeded desktop, Lantern traces kept for each run:
RightSidebar-*.jsends at 1565 instead of 1645, and the 6 lazy chunks start at 1323 instead of 1402.GeistMono-Variable-*.woff2is 58,438 B at priority VeryHigh. It is not preloaded.@font-faceinsrc/browser/styles/globals.css:55loads it when the first monospace text lays out during startup.New lead (not started, needs an A/B): find which startup element uses the mono font. If it is not needed for the first transcript paint, rendering it later (or with the primary font) takes 58 KB off the LCP path. Expected gain: up to ~80 ms desktop Lantern, plus less bandwidth competition for real users on slow links.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 26, 2026
Perf owner: proven. The two desktop LCP levels (~1607 / ~1648 ms) are a Lantern simulation artifact. A ~100 B change in the compressed size of
API-*.jsdecides which level a cycle lands on. No code change causes the upper level.Evidence
API-*.jsends at 685 instead of 646, andmain.jsat 845 instead of 806. Both are +39 ms, which is one desktop RTT (40 ms). Every later node shifts by that.API-*.jshas the sameresourceSize(489,040 B) in both runs, but itstransferSizechanges by about ±150 B from build to build, because the renamed chunk hashes compress differently. Across 188 desktop runs on Oct 25-26 it separates the levels with no overlap: low ≤ 132,856 B, high ≥ 132,864 B.LanternLargestContentfulPainton copies of the saved trace + devtools log. OnlyencodedDataLengthofAPI-*.jschanges. Delta 0 reproduces the report exactly). I reran it myself:132,860 B = 91 × 1460 B (TCP MSS). The 92nd segment needs one more simulated TCP round, which adds 40 ms. Changing
main.jsby ±500 B does not change the LCP.Consequences
API-*.jstransferSize. At ≤ 132,860 B, the reference is 1607.3. Above that, the reference is 1647.3. The trigger line is the reference + 40 ms. Each report states the API size and the level.Tooling:
lantern_flip.mjs(Lighthouse auditor workspace) recomputes Lantern LCP with one request's size changed, from a run saved withLANTERN_DEBUG.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 26, 2026
Desk rule addendum (perf owner, workspace 8e18f4aada): since #7625 (
0c9a0c0db4) the bundler names the chunkglobals-*.jsinstead ofAPI-*.js. It is the same chunk: resource size 489,040 -> 491,944 B, same request start, same position in the graph. From now on the level check uses that chunk under either name: transfer size <= 132,860 B -> reference 1,607.3 ms, otherwise 1,647.3 ms, trigger at reference + 40. If neither name exists, the level is unknown and the low reference applies. The 132,860 B threshold for the renamed chunk is unproven until a Lantern trace run, since it rests on the same 91-segment boundary.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 26, 2026
Desk rule change (perf owner, workspace 8e18f4aada). Since #7625 the chunk-size rule (issuecomment-6424990818, addendum issuecomment-6425404821) no longer predicts the level.
Evidence: I reran the auditor's
lantern_flip.mjson a copy of VM cycle 20261026T193404Z run 1 (f446f78738, Lantern trace saved). Simulated desktop LCP:The bump made both Geist font files 11-13 KB larger, so the simulated graph changed and the
globals-*.js(formerAPI-*.js) segment step no longer sets the end of LCP.New rule from now: one desktop reference, 1,647.3 ms, for every cycle; trigger at 1,687.3. A low-level build then needs about +80 ms to fire (the same accepted cost as decision D-A). I re-derive the levels only from a Lantern-traced cycle that lands on the low level.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:highThomasK33 commentedon Oct 27, 2026
Fact for this issue (perf owner, workspace 8e18f4aada). #7661 (
329ee8f3ff, merged without a perf clearance) addsagentInterfaceStatusBar.ts(349 lines) behindAgentInterfaceStatusView, whichChatPane.tsx:74still imports statically. So the code lands on the workspace page's first load.VM cycle 20261027T061246Z against 20261027T020708Z (seeded mobile run 2, transfer sizes): the page chunk
mermaid-LQXJBOYN-*.jsgrew 248,164 -> 251,092 B (+2,928),SandboxedArtifactFrame+162,globals.css+93. Requests stay at 33. Metrics did not move: seeded mobile LCP 1,958 ms (reference 1,958.2), desktop 1,643 ms (trigger 1,687.3).No action now. It adds to the cost of the eager
AgentInterfaceStatusViewimport that this issue tracks. Any fix here must also keep the status bar off the first load, and it must pass the gates in issuecomment-6424990818 (seeded request list plus a VM A/B before merge).Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high