perf(chat-open): a chat shows its newest messages sooner
HOLD: the fix on this branch (scroll-anchor read beside the connect) shows no gain in the bench; not landing until a change with a measured gain replaces it.
Leo-ask: ask-78e06958ab
Leo: "and i also want things to load faster"
What the harness says (bench/chat-open.bench.ts, Linux CI, production vite build, median of 5)
A cold chat open (every chat open in the app is a cold page: AgentPaneSpareReadiness.adoptsExistingChats = false, HQ#5914):
| stage | 300 turns | 1,500 turns |
|---|---|---|
| DOMContentLoaded | 11 ms | 13 ms |
| socket opened | 156 ms | 157 ms |
| initialize arrived | 204 ms | 206 ms |
| attach answered | 205 ms | 207 ms |
| newest message painted | 330 ms | 327 ms |
- Chat length does not matter: the attach page is 400 events / 73 KiB either way.
- Before the socket: script evaluation and the pane's first render (the bench's dev entry also loads every locale and its chunks one after another, so it overstates this part).
- Socket open to initialize: 71-75 ms with an instant daemon. The page is busy rendering the handshake's chrome (composer, model picker, header) before it handles
open. - Attach to newest message: ~125 ms of render. The first frame draws 16 rows, 84 rail entries, 711 elements; the profile spreads it over the placement effect's first forced layout,
drawnHeightOf, the rail and the header's overflow measure, with no single hotspot. - The client already sends status, watch and the early attach in one burst after
initialize. Sending the attach beforeinitializeanswers is not safe: acpmux takes the person proof, auth and compression frominitialize. - Chat switch (chat-switch.bench.ts, 1,500 turns): 73-83 ms with an instant daemon, 67-108 ms at 40 ms per answer.
The change tried here, and its result
The open read the chat's kept scroll anchor from the host (chat.scrollAnchor.get) before it started the connect. The branch reads it beside the connect and holds the client's frames until it settles. A regression test proves the order (the attach goes out before the host answers, and the chat still opens at its kept place: 1 fail on base, 3 pass with the change). But the newest-message time did not move: the anchor read already overlapped the first render.
| host + daemon latency | before (ms) | after (ms) |
|---|---|---|
| 0 | 477, 607 | 529, 670 |
| 20 | 610, 534 | 530, 484 |
| 40 | 553, 495 | 609, 517 |
The bench tooling added here (stage rows, LATENCY, HOST_LATENCY, TRACE=1, profile windows) stays useful for the next attempt.
Next lever
The boot is paid on every chat open because a tab opening an existing chat never adopts the warm spare page (HQ#5914). Fixing that adoption removes the whole boot stage (about 150-200 ms of the 330 ms here) from every open.
Agent session: agent session
š¤ Generated with Claude Code