Repository navigation
frames: A0 correctness pass — S-flush, C3 hold, C5 data scope, C12 report, rulings - #3837
Conversation
…nd address, one response per store (S-flush) A0 (SCs are rendered data): a frame is one async value outward. The mount's covering <Loading> now pends on the bound address's FIRST FLUSH — content or error — through `FrameHost.landing(address)`, read per bound address through a fresh node (frames-rulings 1.5, 1.6 (i)): a switch is a new question on the source, the superseded address's late writes release nothing, an unrevealed boundary stays on its fallback and a revealed one holds. The two hand-rolled shell gates (`boundaryComponent`'s arm/release/settle/setGate and `adoptBoundary`'s twin), `followAddress`'s re-arm and frameless waiter, and `onApply`-as-release go. An unstaged response announces itself to the host at its header (`start`), so the address reads "in flight" from the header to the landing. The store is one response's (1.4 full; 2.1/2.2): a version bump or rebind replaces the store wholesale — root, segments, slot records, the error — so a byte-identical root under a new version still applies as the new version's (C7 c), a held record leaves with the response that carried it (C6 a1, b2), and `argsEquivalent`/`clearStreamRecords` delete; the mount's `#slotArgs` value compare is what preserves occurrence state. The host keeps the latest landed version as `shown` so a mount opened mid-flight seeds the committed value (holds-latest) and the landing fans out as the version's whole set. The occurrence's name decides its class: a called occurrence (`prop#n`) found without its record waits for it — never evaluated argless (C18 ×3 flip; the #2968 poll stays as the document face's re-sync trigger, scoped to called occurrences) — and a bare occurrence is direct-insert. A reveal is an apply (2.3, interim): the document face's reveal cascade syncs the adopting frame, so a direct-insert range the fragment carried mounts (C2 b) and a record drained before its range was shown takes effect at the reveal (C4 d). Pins flipped to `test`: C2 (b), C4 (d), C6 (a1, b2), C7 (c), C17 (a — re-pinned: B's first flush, not its `start`, releases), C17 (c), harness C2 ×1, C18 ×3. Re-pinned: frames-binding-slots "orphan record … waits" (was "still mounts"), frames-hn-client zero-data occurrence is the bare prop. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…adopted occurrence is a pending boundary (C3)
Frames-rulings 3.1 (ruled): hydration-done follows non-SC Solid 2 — done is
what `checkHydrationComplete` says (the root pass over, `_pendingBoundaries`
zero), and the frames client participates through that mechanism, with no
accounting of its own. 3.2, the carrier: ONE registration per frame while a
sync leaves an adopted occurrence waiting to mount (for its record, for a
`{$ref}`'s data), released by the first sync that leaves none or by disposal.
solid-js: `sharedConfig.holdBoundary(id)` (internal) — `initBoundaryResume`'s
registration for a holder with nothing to resume: the count, the owner's `_hp`
mark, the disposal release; returns the release, which checks completion.
One assignment in `enableHydration`.
web/frames: `FrameOptions.hold()` (adopt path; wired by `adoptBoundary` under
the component's owner, only while `isHydrationInProgress()`, keyed `sc:<fid>`
so no fragment's bookkeeping is touched); `#syncSlots` tracks whether a full
sync left an unmounted occurrence waiting and registers/releases at its end;
`dispose` releases.
Harness: the C3 law exempts a done that fired at or after the mount's
disposal — a disposed holder owes no claim (its release is what lets done
fire, as a disposed <Loading>'s is). Verified to hide nothing on `next`
(C3 stays 280/500 there).
Pins flipped to `test`: C3 (a), harness C3 ×1. Campaign (500 cases): C3
280 → 0 (seed 3289), 268 → 0 (seed 91501); only C19 remains.
Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…(C5) Frames-rulings 1.2: a `data` chunk lands in its own response's table or nowhere. The integration rotates the address's table at the header and creates it at first use; the transport restamps every chunk with its response's version; but `createFrameHost.apply` routed `data` straight to the data hook with no version read — so a superseded response's late chunk was the first use and filled the table that was now the current response's (R4). The data path is now under the store's version guard like every other chunk: with the response announced to the store at its header (S-flush), a `data` chunk whose version is below the store's is dropped. Pins flipped to `test`: C5 (a), (b), (e). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…n adopted frame in dev; the client shows the server's outcome (C12 c, client half) Frames-rulings 3.3 under A0's corollary 4 (inward): a server `<Loading>` inside a server component is the SERVER's boundary; its outcome arrives as markup and the client shows whatever the server rendered for it — never a blank it invents, never a client error fallback for a server state. A rejected one has no client twin to surface its `<key>_fr` rejection (`hydratedCreateLoadingBoundary`'s `s === 2` arm runs only for a boundary registered against it), so the adoption that claims its placeholder observes the rejection and names it in dev (0 B prod). The blank the position shows today is the server half's gap: `server.ts`'s error path hands `sink.fragment` a `" "` template (the client twin, when there is one, renders over it; a server component's boundary has none). Pins: C12 (c) re-pinned per 3.3 — (c1) the rejection is reported in dev, the client invents no error state (test); (c2) the position shows the server's rendered outcome, never a blank (test.fails, the server half). Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…n #syncSlots, the guard reads isHydrationInProgress alone
−48 B min on the frames client (43,411 → 43,363); the zombie check now
precedes the {$ref} wait, so a zombie held on a ref is unmounted at once
rather than when the ref resolves.
Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… false) Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ency reds and the size audit's duplicates share (proposed) Draft, marked "proposed — maintainer ruling pending"; no engine changes. The frames/hydration consistency contract (spec/frames-consistency-contract) found thirteen reds across C2, C3, C4, C5, C6, C7, C12 and C17; the SC size audit (size/sc-audit §4) listed the layer's duplicated seams. This document tests the hypothesis that they are the same seams seen from two sides, and drafts the rulings — numbered one-sentence statements in the L2 section's voice, each with the mechanism that carries it today, the state it lives in twice, and the reds it decides — grouped by the three questions the reds cluster on: - Response identity (C5, C6, C17): a response owns what it delivered; a `data` chunk lands in its own response's table or nowhere; a record resolves its refs through its own response's table wherever the frame is bound; a bump drops what the previous version never applied; the shell gate is the bound address's; a switch keeps on screen what was on screen. - Applied state per version (C7, C2, C4): the store is the truth and the applied state is a cache of it keyed by version; a bump re-applies a byte-identical root; a reveal is an apply; applied means shown. - Hydration-done accounting (C3, C12, S1's C3b and `.fails`): done counts every occurrence the document delivered; every frames hold is counted once per frame; a claim owns the fragment's outcome; the client consumes the ids the server consumed. For each: the fix shape as one collapsed carrier (response-owned data cells, records carrying their resolver, one shell gate, one applied record, one hold counter), a byte estimate (− collapse / + carrier) read off the audit's function table, the pins that flip, and what touches public surface or the server half. Two-readings items for the maintainer: 1.4 (narrow/full), 1.6 (per-address gate value vs holds-latest), 3.1 (done counts the page vs the root pass — S1's pin and the contract's contradict). What the rulings do not decide (plain bugs, product questions, the wire) and an order of work gated by the contract's pins and scripts/size. Co-authored-by: Claude via Cursor <noreply@cursor.com>
…19 under seam 3; gate is #3813 Co-authored-by: Cursor <cursoragent@cursor.com>
… data; a frame is one async value outward, its inner boundaries are the server's; 3.1 ruled Co-authored-by: Cursor <cursoragent@cursor.com>
… hold model governs; axioms annotated with the Solid 2 rules they restate Co-authored-by: Cursor <cursoragent@cursor.com>
Per-reading comparison (1.3, 1.4, 1.6, 2.3, 3.1, 3.3, 3.5, 3.6) and per- product-question comparison (C12 (c), settles-once projections, live containers, slot-trails-html) against RSC/Flight+Fizz, Next.js, React Router/Remix, Turbo/htmx, Phoenix LiveView, Qwik, Astro server islands, Marko, Vue and Solid Router. Adds an addressing section (content-keyed vs tree-shaped vs DOM-addressed vs connection-tree) for the maintainer's A3/A4 model, a summary table, and a list of mechanisms the references have that we don't. Primary-source citations; verified vs inferred marked. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…plan onto the rulings branch (from size/frames-a0-reattribution @ 1e0be39) The plans tonight's correctness pass implements (frames-savings-pass.md Phase A: A0–A6), carried with the code per the maintainer's preference. Squashed from the branch's nine commits; content unchanged. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ts, the server-half drafts, the as-landed table What the rulings do NOT decide: the nine readings the 2026-10-06 pass took by default (first flush not start; warm switch-back shows the committed value; the name decides the class on every sync; 2.3's interim with S-flush; 1.2 by the version guard; 3.2 via initBoundaryResume keyed sc:<fid> while in progress; 3.3's dev report; S-adopted documented; the harness's C3 dispose exemption), each with its alternative. The server half: C13's delimiter with the client's one-write status, the plain-response streaming bound (complete.bound), C12 (c)'s error template located in finalizeError/the done closure with the two decisions the server PR must make. Order of work: the as-landed table (#3830–#3833), what each step left undone. Public surface: landing, hold, holdBoundary, the behaviour notes. Co-authored-by: Claude via Cursor <noreply@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
… decisions: the defaults accepted, the C3 hold cost accepted (caps raised), #3827 closed as superseded by S-flush Co-authored-by: Cursor <cursoragent@cursor.com>
…ion)
The C3 hold (frames-rulings 3.1, ruled) costs +59 B min on every hydrating page (sharedConfig.holdBoundary in solid-js) and the pass nets +104 B min on the frames client (S-flush −187, the hold and C5's guard +291). Six scenarios are over their cap past the 20 B minified allowance; the maintainer accepted the cost 2026-10-06 ("pay the cost for correctness"). Each cap is set at the 0.01 KB step at or below measured + 10 B, with its recorded minified, from a local measurement that matches CI on this machine: hydrating (no stores) 17.71 -> 17.73 KB, page base 44.84 -> 44.89 KB, page live 48.51 -> 48.60 KB (floor-caps.json, under the PR's Size-Exception:); hydrating + stores 28.87 -> 28.93 KB, compiled hydrating 30.93 -> 31.03 KB, frames eager 13.78 -> 13.79 KB (scenarios.js). The three pre-existing over-cap-within-allowance scenarios (signals floor, isPending, CSR) are at +0 B minified and are not raised.
Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: e968866 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 |
…3837 Co-authored-by: Cursor <cursoragent@cursor.com>
Size (brotli, eager entry chunk)
|
Coverage Report for CI Build 37514156882Coverage decreased (-0.1%) to 75.927%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
… holes eager) and the measured Phase D floor Co-authored-by: Cursor <cursoragent@cursor.com>
…ark is unconditional; hydrateWindow's hydrating cost accepted - frames-rulings 3.6: "Landed" — the park is unconditional (post-done claims under corollary 4 have no hydration state that says "claim"); S1's `claiming` hint at plan step C3 keys it on the claim again. - frames-rulings 3.2: cost as landed — #3837 + #3840, frames −109 br / hydrating +105 br; the hydrating cost accepted, caps raised under a Size-Exception at the next integration PR. - consistency contract R10 / C19: the park's key described as it landed. - savings-pass plan §3 row A2: landed in two parts, measured vs estimate, further solid-side seams measured on an edited dist copy before they are written.
The frames A0 correctness pass, landed as one PR per the maintainer ("merge as one given all the checks"). It fixes the frames/hydration consistency reds under the A0 principle (server components are rendered data; a frame is one async value outward) and the frames rulings — S-flush (the address is an async source; the store is one response's; the occurrence's name decides its class; a reveal is an apply), the C3 hold (hydration-done counts the frame's holds — ruling 3.1, ruled), C5 (a
datachunk is under the store's version guard), C12 (c)'s client half (a rejected server fragment is reported in dev; the client invents no error state) — and carries the A0 documents with the code: the contract (documentation/server-components/frames-consistency-contract.md), the rulings (documentation/server-components/frames-rulings.md), the prior-art comparison, the A0 axiom inserver-components-principles.md, and the two plans (documentation/plans/frames-a0-reattribution.md,frames-savings-pass.md). Consistency harness (CONSISTENCY_FUZZ=1, 500 cases, seeds 3289 and 91501): only C19 remains (C18 0, C3 0, C2 0 on both seeds). Supersedes the stacked drafts #3830 (S-flush), #3831 (C3), #3832 (C5), #3833 (C12 c), #3834 (docs), and the closed #3827 (C18 — unrepresentable under S-flush; see "Anything else" below). Offnext@49a8dca84.Ruled 2026-10-06 (docs commit
00dbc8663, no code): the maintainer ruled the remaining open points ("other than that lets do your recommendations") — inframes-rulings.md: the status line (1.3, 1.4 full, 1.6 (i), 2.3, 3.3 ruled as recommended), 3.5 closed (superseded by default #3; kept as the diagnosis record), 3.6 ruled (iii) — the consumer parks (C19 ×2 flip with the A2b port,fix/frames-a2b-park-and-window; C11 read as "outside a claim's park"; the contract's R10 misread S1 as evidence for (i)), the slot/html wire order ruled unspecified with the client's tolerance as the pin (1.4 and the wire list), and the two Phase A gate judgment calls accepted (C19 red until A2b; C13 (a, b) / C12 (c2) stand with drafts, server PRs to follow) under "What the rulings do NOT decide". Inframes-savings-pass.md: §6 each decision gains itsRuled 2026-10-06:line (8.0 — holes eager, C1 skipped; assets tiered; the tier wire;complete.bound; S1 at C3; decision 6 closed by this PR'sholdBoundaryform with thehydrateWindowform attempted in A2b;preserveModulesafter the tiers; claims on the router's chunk), §4.1 Measured (the Phase D floor by measurement: B.3 −3,692 onL8, adapters 0, E.b+ −462; end state page base ≈ 27.0 / 27.5 KB br, live ≈ 30.7 / 31.3; the router +12,137 br — the 30 KB target is without it; under 30 KB yes, on B.3), and the one sentence after §3's cumulative gates.Public API changes
All
@experimental(the frames surface) or@internal; nothing wire.FrameHost.landing(id: string): Promise<void> | undefined(@experimental) — the address-source seam: a promise while the address's first response is in flight,undefinedonce the address has a landing to show (or nothing has begun). S-flush.FrameOptions.hold?(): () => void(@experimental, adopt path; wired byadoptBoundary) — called when a sync leaves an adopted occurrence waiting, returns the release. C3.sharedConfig.holdBoundary?(id: string): () => void(@internal,solid-js; assigned byenableHydration(), absent in CSR bundles) —initBoundaryResume's registration for a holder with nothing to resume. C3.FrameOptions.recordsPendingis the document face's re-drain/re-sync trigger ("may the document still deliver records?"), no longer a classifier — a called occurrence (prop#n) found recordless waits on every sync, document or stream face;createFrameHost.registerno longer callsframe.rebase()after a seed (the method stays onFrame); the handler applies a syntheticstartchunk at an unstaged response's header (begin), so the address reads "in flight" from the header to the landing; a version bump or rebind replaces the host and frame stores wholesale (root, segments, slot records, the error — a producer must re-send slot records, as the sink does); theargsEquivalentdedupe arm is removed fromFrameImpl.apply(argsEquivalentandclearStreamRecordsdeleted; the mount's#slotArgscompare is what preserves occurrence state across versions);landingresolves on the root, the stream's error, or itscomplete.datachunk whose version is below the store's is dropped atcreateFrameHost.apply(n < undefinedis false, so a raw host consumer's unversioned data applies as before).console.errorinclaimRegionFragmentswhen a server<Loading>fragment inside an adopted server component rejected on the server (_SOLID_DEV_; 0 B prod). No new diagnostic code.ServerComponentHandlerOptions.onStream,FrameHostOptions.resolve/FrameHost.resolve,FrameHost.preview/Frame.preview,STAGED_DATA, the wire format (FrameChunk, the record keys, the DOM markers, the hydration data keys, the_$SCbootstrap).Size
Head as CI measured it (Size run 37488785237 on
665bda838; identical to the local measurement byte-for-byte — every "minified vs recorded" in the size report is 0 B), base =next@49a8dca84as CI measured it in the same run. Every scenario not listed is 0 B; the three pre-existing over-cap-within-allowance warnings (signals floor, isPending/latest, CSR) are at +0 B minified and are not raised.nextmin / brCaps are set at the 0.01 KB step at or below CI-measured + 10 B (not rounded up past +10), each with its recorded minified; the dated ledger line is above each cap in
scenarios.js. Commitsize: raise frames/hydration caps for C3 hold (maintainer Size-Exception).Size-Exception: maintainer accepted the C3 hold (ruling 3.1) cost for correctness, 2026-10-06 — the hydrating floor, both page caps (floor-caps.json) and the frames-eager / hydrating + stores / compiled hydrating caps (scenarios.js) are raised to CI-measured + 10 B, with their recorded minified.
The growth is the C3 hold:
sharedConfig.holdBoundaryinsolid-js(≈ +59 B min on every hydrating page — onesharedConfigassignment whose arrow wrapsinitBoundaryResumeandcheckHydrationComplete; the unmangleable property name and the two calls are most of it) andFrameOptions.hold+#syncSlots' waiting flag +adoptBoundary's wiring in the frames client (≈ +288 B min). S-flush itself is −187 B min / −128 B br on the frames client (the gate twin, the re-arm/waiter and the dedupe arm deleted); C5 is ≈ +70 B min; C12 (c) is 0 B prod. Expected to come back and more with 1b (preview/stage's client half, ≈ 1,300 B min) — the ratchet lowers the caps then.Decided by default (accepted by maintainer 2026-10-06)
The overnight pass ran unattended under "make the conservative choice, write it down, move on"; the maintainer accepted the defaults as made ("I trust you, nothing super flagged"). Each is written with its alternative in
frames-rulings.md("What the rulings do NOT decide → Decided by default").start(1.5; C17 a re-pinned) — astartis the response announcing itself; revealing an empty<solid-frame>at it is the flash the gate exists to prevent.landing(address)isundefinedonce the address has a landing to show.prop#n) found recordless waits; a barepropis direct-insert by design.#scoping removed an accidental poll that had masked R3; the reveal cascade now applies an empty write at the frame's version ("a reveal is an apply").initBoundaryResume's, keyedsc:<fid>, taken only whileisHydrationInProgress()(C3) — the fragment bookkeeping needs a key no fragment uses; a hold on a page that never hydrated is the frame's business. Cost ≈ +59 B min on hydrating pages — the accepted cost above.console.error, not a diagnostic code (C12 c, re-pinned as c1 green / c2 red) — a structured code is new surface; the server half (the blank template) is drafted, not coded.claimRegionFragments → fr.adopt) documented, not built — ≈ +80 B insolid-json every hydrating page, on top of 6.next.landingresolves oncompletetoo — a response that completed landed what it had, so a completed response without html never holds a gate forever.Smaller readings recorded in the drafts' bodies, not restated in the doc: the hold is one per frame, not per occurrence (3.2's shape); a
datachunk with no version (a raw host consumer) applies as before; the two plan documents were brought as one squashed commit.Not in this PR
FrameImpl#preview/stage's client half /#refArgsUnchanged(≈ 1,300 B min): the pull form (the fill's props memo reads the token and derives from the staged record) is written up inframes-rulings.md's as-landed table and fix(web/frames): the address is an async source — one landing per bound address, one response per store (S-flush) #3830's body; it touches the regions and binding-slot paths and needs its own gate run.R.reveal's readiness/retry deletion — 2.3 landed in its interim form only._$HY.robserved by nothing).claimRegionFragments → fr.adopt; ≈ +80 B solid).hydrateWindow/R.claim's deletion and the 3e port (S1's park; C19 ×2 stay red).frames-rulings.md, "The server half — drafts"): C13's sweep delimiter,complete.bound(the plain-response streaming bound), C12 (c)'s server template (render the nearest server<Errored>'s fallback into the<Loading>'s position). C13 (a, b) and C12 (c2) stay red until the server half.Gate
packages/web: client 124 files / 1142 passed / 1 expected fail; server 154 / 1474 / 3 expected fail / 2 skipped; hydrate 68 (+1 skipped) / 358 passed / 6 expected fail (was 22 onnext: C2 a2, C12 c2, C13 a, C13 b, harness C19 ×2 remain);test-typesclean.packages/solid: 41 files / 821 passed;test-typesclean.frames-address-source(@solidjs/web),frames-hold-is-pending-boundary(solid-js,@solidjs/web),frames-data-response-scoped(@solidjs/web),frames-fragment-rejection-report(@solidjs/web). The docs and the cap change need none.Anything else
recordsPendingterm) is closed as superseded: under S-flush the occurrence name decides the class on every sync, so C18 is unrepresentable and the predicate has nothing left to guard; the C18 ×3 harness pins and the replay pins flip here. Its diagnosis (delivered is not drained) is recorded at 3.5 and in the as-landed table.wip/frames-pass-integration): fix(web/frames): the address is an async source — one landing per bound address, one response per store (S-flush) #3830's commit, fix(web/frames): hydration-done counts the frame's holds — a waiting adopted occurrence is a pending boundary (C3) #3831's two, fix(web/frames): a data chunk of a superseded response lands nowhere (C5) #3832's two and fix(web/frames): report a rejected server <Loading> fragment inside an adopted frame in dev; the client shows the server's outcome (C12 c) #3833's one merged, the seven docs commits of docs(server-components): A0, the frames rulings and prior art, the plans — with the overnight pass's defaults and the server-half drafts #3834 cherry-picked, one docs commit recording the maintainer's 2026-10-06 decisions, and the cap commit.