frames: refetched content lands at the transition's commit - #3759
Conversation
🦋 Changeset detectedLatest commit: 4f7f415 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Size (brotli, eager entry chunk)
Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. Caps in |
Coverage Report for CI Build 37268989085Coverage remained the same at 75.991%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will improve performance by 19.62%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | memo + sync render effect only (reference) |
32.1 ms | 26.8 ms | +19.62% |
Tip
Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.
Comparing fix/frames-refetch-commit (4f7f415) with next (bde4299)
…rted onto L2) A refetch or single-flight region for a call a boundary is showing is staged in the transport and the call resolves to a content token; the mount lands it in the two halves of the render effect that follows its address accessor. Under the hold model the two halves ride core's seams unchanged: the compute half is the delivering transaction's pass (its `preview` writes — the staged slot args — are held with it and commit in the frame whose landing dissolves the lane's guesses), the effect half is its `land` (the commit replays the staged chunks). Both signals pins (`compute-write-joins-transition`, `settle-folds-queued-writes`) and the optimistic-hold specs were green on untouched L2; the morph-in-transition specs were red (3/3) and are closed by the staging. Composes with e133516 (the switch gate re-arms in the pass): the rebind — display — moves to the effect half so a switch's content waits for the commit too (`frames-morph-in-transition` switch case), while the gate settles on the new address's first write through a frameless waiter registered on the host, so a second switch mid-flight still binds (`call-driven-lifecycle`). No core seam touched. Size caps not raised: frames 12,997 -> 13,770 B (cap 13.00 KB), page base 44,017 -> 44,765, page live 47,656 -> 48,447 — reported for the maintainer's exception. Signals 4898/0/2, web 1138/0 + server 1411/2 skipped + hydrate 275/0. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…e base 44.03 -> 44.78, page live 47.67 -> 48.43 Refetched content lands at the transition's commit (the staging, the content token, the two halves of the mount's follow effect, the staged data tables, and on L2 the switch's rebind in the effect half with the frameless host waiter). Measured against `next` @ 1a3f87f (after #3776): frames 12,997 -> 13,770 B (+773; +2,262 B minified, frames client +2,255), page base 44,029 -> 44,762 B (+733; +2,265 B minified), page live 47,654 -> 48,418 B (+764; +2,265 B minified). Caps set at head + 10 B. Ledger notes in scenarios.js re-applied from the PR's 1b347c7 against these numbers. Size-Exception: frames: eager client consumer 12,997 -> 13,770 B (cap 13.00 -> 13.78 KB); page: base server components 44,029 -> 44,762 B (cap 44.03 -> 44.78 KB); page: live server components 47,654 -> 48,418 B (cap 47.67 -> 48.43 KB) — #3759's staging so refetched content lands at the transition's commit; accepted by the maintainer (2026-10-04). Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
6f09b62 to
52dd93b
Compare
…rted onto L2) A refetch or single-flight region for a call a boundary is showing is staged in the transport and the call resolves to a content token; the mount lands it in the two halves of the render effect that follows its address accessor. Under the hold model the two halves ride core's seams unchanged: the compute half is the delivering transaction's pass (its `preview` writes — the staged slot args — are held with it and commit in the frame whose landing dissolves the lane's guesses), the effect half is its `land` (the commit replays the staged chunks). Both signals pins (`compute-write-joins-transition`, `settle-folds-queued-writes`) and the optimistic-hold specs were green on untouched L2; the morph-in-transition specs were red (3/3) and are closed by the staging. Composes with e133516 (the switch gate re-arms in the pass): the rebind — display — moves to the effect half so a switch's content waits for the commit too (`frames-morph-in-transition` switch case), while the gate settles on the new address's first write through a frameless waiter registered on the host, so a second switch mid-flight still binds (`call-driven-lifecycle`). No core seam touched. Size caps not raised: frames 12,997 -> 13,770 B (cap 13.00 KB), page base 44,017 -> 44,765, page live 47,656 -> 48,447 — reported for the maintainer's exception. Signals 4898/0/2, web 1138/0 + server 1411/2 skipped + hydrate 275/0. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…e base 44.05 -> 44.78, page live 47.67 -> 48.45 Refetched content lands at the transition's commit (the staging, the content token, the two halves of the mount's follow effect, the staged data tables, and on L2 the switch's rebind in the effect half with the frameless host waiter). Measured against `next` @ bde4299 (after #3790/#3791): frames 12,997 -> 13,770 B (+773; +2,262 B minified, frames client +2,255), page base 44,048 -> 44,762 B (+714; +2,265 B minified), page live 47,670 -> 48,436 B (+766; +2,265 B minified). Caps set at head + 10 B. Ledger notes in scenarios.js re-applied from the PR's 1b347c7 against these numbers, after #3743's page-base note. Size-Exception: frames: eager client consumer 12,997 -> 13,770 B (cap 13.00 -> 13.78 KB); page: base server components 44,048 -> 44,762 B (cap 44.05 -> 44.78 KB); page: live server components 47,670 -> 48,436 B (cap 47.67 -> 48.45 KB) — #3759's staging so refetched content lands at the transition's commit; accepted by the maintainer (2026-10-04). Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
52dd93b to
4f7f415
Compare
Ported onto L2 (2026-10-04)
This PR was written against the pre-L2 scheduler and has been rebased and re-landed on the L2 hold model (
next@ bde4299, after #3788–#3791). Authorship is kept on the feature commit; the port is one commit plus a size commit.Subsumed by L2 (tests ported verbatim, green on untouched L2):
compute-write-joins-transition.test.ts(a compute-half write under a held transaction joins it and is read before the optimistic intent ends) andsettle-folds-queued-writes.test.ts. On L2 "a staging read is the frame's" and a write queued before a landing joins the transaction; the pins stay as regression tests.frames-optimistic-hold.spec.tsxcase, including the four new ones (literal args,{$ref}args on the shared host, a fill inside a nested region, a refetch that renames the nested region).Ported (red on untouched L2):
frames-morph-in-transition.spec.tsx(3/3). The staging design lands as described above, in L2 vocabulary: a response for a showing address is staged under the address, the call resolves to a content token,dynamicdelivers it as an address, and the mount's follow effect lands it in two halves —stagedContent.previewfrom the compute half (the pass of the transaction that delivered the content, so the staged slot args are held with that transaction and commit in the frame whose landing dissolves its optimistic guesses, never a flush behind it) andstagedContent.commitfrom the effect half (atland). The preview half was verified to be load-bearing on L2: disabling it returns the one-flush optimistic gap (server=false / derived=false) in all three multi-flight specs. Staged data tables (stageTables) on the shared host and root-routed single-flight regions are as in the original.Switch adaptation and ruling. The L2 carve (e133516) had moved the switch's re-arm and rebind into the compute half so a second switch mid-flight could not deadlock, and called the rebind "plumbing, not display". This PR's switch test needs the rebind at the commit. Maintainer ruling (2026-10-04): the switch is display — one reveal. e133516's wording is superseded (stated in the comment at
followAddress). Resolution: the rebind runs in the effect half at the commit; the re-arm stays in the pass; a frameless waiter{ apply: settle }is registered on the host under the new address so the switch gate settles on the new address's first content without the rebind — a second switch mid-flight still binds to the live call (call-driven-lifecyclestays green). Consequence:host.get(newAddress)is truthy while a switch pends, so a refetch of the new address that arrives in that window is staged rather than written through.Public API changes
No new public exports, props, options or parameters beyond those the original description lists. On L2 they are:
frames-live.spec.tsx's supersession test mounts the second caller's result).ServerComponentHandlerOptions.componentreceives can deliver a content token (address + "\0" + version) for a refetch of the address it shows; the bundled mounts strip it withcontentAddress.host.get(newAddress)is truthy while the switch pends.@internalsurface:Frame.preview?(records, resolve?),FrameHost.preview?(chunk, resolve?); newframe-transport.tsexportscontentAddress,stagedContent,STAGED_DATA(a handler option symbol, not on the options type).ownedWrite: true(staged args are written from a compute half). Both mount gate signals already carriedownedWriteon L2 (e133516, the re-arm in the pass); the port adds nothing there..changeset/frames-refetch-lands-at-commit.md(@solidjs/web, patch).Tests
Measured on the rebased branch (
next@ bde4299), clean build with the native compiler rebuilt:packages/webvitest: 1139 passed, 1 expected fail (1140) (1138 before test(signals): pin #3726 on L2 — derived store sync landing wakes parked readers (supersedes #3732) #3789 addedderived-presence-async-3726.spec.tsx).packages/signalsvitest: 4919 passed, 3 expected fail, 2 skipped (4924).packages/webtsc:tsconfig.test.json,tsconfig.test.augment.json,tsconfig.json(--noEmit) all clean.packages/signals/scripts/rules-index.mjs --checkcurrent.scripts/sizegate: every scenario within its cap;check-floor-caps.mjs origin/nextpasses with the exception line below and fails without it.New/changed specs:
frames-morph-in-transition.spec.tsx(3, new),frames-optimistic-hold.spec.tsx(6, four new),frames-live.spec.tsx(supersession test updated),compute-write-joins-transition.test.ts(2, new),settle-folds-queued-writes.test.ts(2, new).Size
Re-measured after the rebase against
next@ bde4299 (decimal KB; caps at head + 10 B, rounded up to two decimals). The frames client is the whole cost: +2,255 B minified inweb/frames, +7 B in the server-functions client slice; the pages' remaining ~210 B minified is attribution drift.next@ bde4299The L2 port is 21 B minified smaller than the original PR's frames client (the follow effect is shared between the cold mount and adoption, and
awaitContentis inlined); the frameless waiter is included. Dropping either half was weighed and rejected (see the ledger notes inscripts/size/scenarios.js). Accepted by the maintainer (2026-10-04).Size-Exception: frames: eager client consumer 12,997 -> 13,770 B (cap 13.00 -> 13.78 KB); page: base server components 44,048 -> 44,762 B (cap 44.05 -> 44.78 KB); page: live server components 47,670 -> 48,436 B (cap 47.67 -> 48.45 KB) — #3759's staging so refetched content lands at the transition's commit; accepted by the maintainer (2026-10-04).
Original description
Written pre-L2 against
next@ 9338c00. Its Size table, Size-Exception line and test counts are superseded by the sections above; it is preserved verbatim for the design rationale.Summary
A refetch (or a single-flight region) for a server-component call a boundary is showing used to morph the boundary as soon as its body arrived. That tore against the transition that drove it:
isPendingand optimistic writes over the old slot args (§9.2.2) were still held while the new markup was already on screen, or the optimistic intent ended one flush before the new args landed (aserver=false / derived=falseframe).The cache is now the commit point. Such a response is staged:
dynamictreats addresses as opaque, so the token reaches the mount like an address switch, inside the transition that read the call.stagedContent.preview): the staged slot args are pushed into the live fills under that transition, so a fill deriving optimistic intent over a server arg never reads the old arg once the intent ends. Occurrences whose args add or rename a region are structural and wait.stagedContent.commit): the markup, the store, mounts, re-calls and region changes land with everything else the transition holds.datachunks decode into staged tables installed at commit, so{$ref}args resolve against the new response while the content still showing keeps its own tables.Cold mounts and switches to an address nothing shows are unchanged: they write through with header-time resolution, because the shell gate is their hold.
Public API and behaviour changes
frames-live.spec.tsx's supersession test now mounts the second caller's result.)componentreceives (ServerComponentHandlerOptions.component) can now deliver a content token (address + "\^@" + version) for a refetch of the address it shows. The bundled mounts strip it withcontentAddress; a custom component that compares or keys on the raw address would see the token.@internal) surface:Frame.preview?(records, resolve?)andFrameHost.preview?(chunk, resolve?).frame-transport.tsexportscontentAddress,stagedContentandSTAGED_DATA(a handler option symbol, not part of the options type).ownedWrite: true. Staged args are written from a compute half, and a commit from one mount's render can release another mount's gate.Size
Size-Exception: refetched content lands at the transition's commit; the staging and the two-phase landing are the feature (+715 to +749 B brotli, ~+2.3 KB minified, all in the frames client).
next@ 9338c00Re-measured after merging
next@ 9338c00 (first measured against 98d35b9 at +749 / +825 / +765 B, page base 46.20 → 47.01 KB). The base page's cap is now lower than first requested (#3741 and #3749 moved the base under the PR); the frames and live page caps are unchanged.Trimmed before accepting. Dropping either half was weighed and rejected: without the preview the one-flush optimistic gap returns; without staged tables the shown content reads the new response's refs before the commit. Ledger notes are in
scripts/size/scenarios.js.Tests
packages/signals/tests/compute-write-joins-transition.test.ts: a compute-half write under a held transaction joins it and is read before the optimistic intent ends (pins the core behaviour the preview relies on).packages/signals/tests/settle-folds-queued-writes.test.ts.packages/web/test/frames-optimistic-hold.spec.tsx: multi-flight with literal args,{$ref}args on the shared host, a fill inside a nested region, and a refetch that renames the nested region (it reproduced a lost rename in the preview, fixed here).packages/web/test/frames-morph-in-transition.spec.tsx: the morph waits for the transition.packages/web/test/frames-live.spec.tsx: supersession test updated (behaviour change above).Verified locally:
packages/webvitest: 1130 passed, 1 expected fail. The twolowercase-on-attribute.spec.tsxfailures come from fix!: lowercase on* names are attributes, not event handlers #3753 and a stale local native compiler binary; they pass withJSX_COMPILER=babel.packages/signalsvitest: 4836 passed, 3 expected fail, 2 skipped.packages/webtsc configs.scripts/sizegate, andcheck-floor-caps.mjswith the exception line.After merging
next@ 9338c00 (clean build, native compiler rebuilt):packages/webvitest: client 1137 passed + 1 expected fail, server 1400 passed + 2 skipped, hydrate 275 passed (frames specs: 92 passed).packages/signalsvitest: 4836 passed, 3 expected fail, 2 skipped.packages/solidvitest: 819 passed.pnpm types,packages/webandpackages/solidtest-types.scripts/sizegate, andcheck-floor-caps.mjsagainstorigin/nextwith the exception line.