Skip to content

hydration: readShallow via ownKeys, store adapters ride the trace chunk, SC-only installs moved off hydrate() - #3904

Open
ryansolid wants to merge 7 commits into
nextfrom
size/hydration-pass
Open

ryansolid wants to merge 7 commits into
nextfrom
size/hydration-pass

Conversation

@ryansolid

@ryansolid ryansolid commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

The hydration pass of documentation/plans/hydration-split-measured.md, on next @ d231b99 (the frames tier stack merged): three static moves, each its own commit with its own changeset, each measured on an edited dist copy before it was written and again as built. No new async moment on any sync path; no ruling touched. Two of the document's candidates are not here — (g), the live-takeover install move, turned out not to be a static move, and (f) waits on ruling 92 — see Skipped.

Summary

item what moved measured (dist edit) → built, page: compiled base SC (min / br) elsewhere
(e) readShallow → Reflect.ownKeys readShallow read a proxy's keys through sourceKeys(value, SOURCE_PROXY), whose runtime kind argument kept @solidjs/signals' merge/omit view walkers (collectKeys, mergeKeysOf, hiddenByAny, …, 1,076 B min) in every compiled page with a dynamic class/style, reached by nothing. By definition that call is Reflect.ownKeys(value) — verified on source: sourceKeys → leafKeys(leafOf(s, kind), kind); leafOf is the identity for every kind but SOURCE_MEMO; leafKeys answers SOURCE_PROXY with Reflect.ownKeys (store/utils.ts 62–64, 165–182). −1,165 / −344 → −1,165 / −344 compiled live −1,165 / −375; compiled hydrating +11 / −9 (keeps the walkers through spread)
(a) store hydration adapters ride the trace chunk The adapters (createShadowDraft, quietAnswer, hydrateStoreFromAsyncIterable, wrapStoreFn, hydrateStoreLikeFn, hydrateStoreLike) move to client/store-hydration.ts; enableHydration() still installs the slot (unchanged — a page with no eager store wrapper shakes it); solid-js/internal/container-trace bundles its own copy (rollup resolves the copy's ./hydration.js import to the external solid-js) instead of reading the eager slot through core.withStoreHydration, which pinned every adapter into the entry chunk of an SC page that never creates a client store (§3.1: Rolldown keeps a module eager whenever an eager module imports it). The second instance is inert by construction — verified on source: the moved functions declare no module-level state (closure state per call only); sharedConfig, onHydrationEnd's callbacks, readSerializedOrCompute's latch set, UNASKED, subFetch are member reads on the one solid-js instance, never named imports (so a server-tier resolution of the trace entry stays inert). Rollup specialises the copy to the materializer's call shape (no ssrSource), so trace.js grows 8.18 → 8.71 KB br, less than the +820 measured. −2,511 / −563 → −2,550 / −575 (withStoreHydration gone too) compiled live −2,539 / −544; hydrating (no stores) 0 / 0; hydrating + stores 0 / −22; compiled hydrating 0 / +88 (layout — the adapters now sit earlier in solid.js)
(g-sc) SC-only machinery installs from installServerComponents() hydrate() → enableHydration() installed, on every hydrating page, what only an integration owning server markup wholesale reaches: sharedConfig.holdBoundary (frames-rulings 3.1), sharedConfig.hydrateWindow, fragment ownership by rendering (_$HY.fa → ownedFragment), the ledger's published answer _$HY.fr; and the DOM runtime's claim walk carried the declared-claim-roots test (ruling 95) and the frame-region exclusion of the root sweep (ruling 97). All of it installs from the frames client now, through two @internal installers (below); enableHydration()'s ledger block keys its once-marker on _$HY.f instead of _$HY.fr. Static install-site move; the frames client calls it where it installs its reveal hook — at installServerComponents() (ahead of hydrate()'s root sweep in every entry) and again at the first document boundary, where the page's _$HY necessarily exists. n/a on SC pages — they pay the installers: +224 / +94 (the row estimated ≈ 140 min of glue; ×3 budget 420) app: compiled hydrating −722 / −200 floor, −680 / −221 real → built −688 / −243 (99,683 → 98,995 / 31,234 → 30,991); hydrating (no stores) −686 / −205; hydrating + stores −686 / −217; compiled CSR −66 / −13 (the claim-roots walk left every @solidjs/web bundle); frames: eager +44 / +1 (one import, one call)

Phase A's +110 br on the plain hydrating pages comes back with interest: app: hydrating (no stores) 17,894 → 17,689 br, app: compiled hydrating 31,155 → 30,991 against next.

Public API changes

All @internal (stripped from the declarations; internal-surface.spec pins the main entries, export-parity.spec the server mirror). Nothing user-facing: no public export, prop, option, default or diagnostic changes.

  • (a) solid-js client entry: removed withStoreHydration (its only consumer was the trace entry; the server entry's inert stub is removed too). Added, runtime exports the trace entry's adapter copy reads back from the one instance: readSerializedOrCompute, subFetch, readHydratedValue, wrapFirstYield, adoptedAnswerStream, withHydrationGate, onHydrationEnd, noHydrationId, markTopLevelSnapshotScope, hasLoadingWindow, isAsyncIterable, syncThenable, UNASKED — mirrored on the server entry as inert stubs (the shapes they have with no hydration in progress) for export parity. A new source module (client/store-hydration.ts) and a resolve plugin in packages/solid/rollup.config.js scoped to it; no new dist entry.
  • (g-sc) solid-js: added enableServerComponentHydration() (runtime export on the main entry, typed on solid-js/internal; a no-op on the server entry) — installs sharedConfig.holdBoundary, sharedConfig.hydrateWindow, the fragment-ownership slot fragmentPolicy reads, and _$HY.fr. @solidjs/web: added installServerComponentHydration() — calls solid's installer and fills the DOM runtime's claim-walk slot (the claimRoots test, the frame exclusion as a per-sweep containment closure); the frames client imports this one. sharedConfig.holdBoundary / hydrateWindow / _$HY.fr are now absent on a page without server components (their doc comments say so); the SharedConfig doc comments in client/hydration.ts updated.
  • (e) no signature or documented-behaviour change.

Pins

  • (e): hydration/style-adoption (Reactive style bindings overwrite server-rendered styles during hydration #3180), hydration/class (Class handles primitive values inconsistently between static, dynamic, and array forms #3189), web/style-class-tracked.
  • (a): rulings 51 / 58 / 60 — solid/client-hydration (store-family adoption, latched divergence, live takeover), solid/hybrid-store-handoff, solid/container-trace, solid/internal-surface (the 13 helpers reach no declaration), solid/server/export-parity; web/lifecycle-matrix/container-args, web/frames-container-lazy-{codec,document}, hydration/container-trace-hold-* (5), hydration/hybrid-store-handoff-3574, hydration/buffered-projection-repeat, hydration/welcome-status-lazy, contract C3 (b) (consistency/c03-hydration-done-counts-holds), C11.
  • (g-sc): rulings 15 (SC clause) / 80 / 95–97 / 101, frames-rulings 3.1–3.3 — consistency/c01-claim-window-roots, C3 (a), C12, C14, adopted-swap-post-done, boundary-arrival, web/frames-adopted-region-fragments (5), web/frames-late-boundary-client (5), hydration/adopted-claim-args-address, hydration/truncated-stream (installs the SC half itself to probe the ledger through _$HY.fr), solid/hydrate-window (installs the SC half itself), the generic hydration arm GH1–GH6.
  • Suites: web client 136 files / 1,272 passed (1 expected fail), web server 161 / 1,524 (3 expected fail, 2 skipped), web hydrate (hydration + consistency) 93 / 463 (16 expected fail, 2 skipped — the counts next has), solid 42 / 833; test-types and typecheck on both; the dist specs; scripts/size gate tests 12 pass.
  • Consistency harness, 500 cases × seeds 3289 / 91501: SC arm 0 / 0 findings; generic arm with CONSISTENCY_IGNORE=C1,C9,C19,E 0 / 0.
  • Artifacts: welcome-status-streamed.json / write-before-resume.json moved on every suite run (the nondeterministic recorder) and were reverted; nothing re-recorded.

Size

Local (macOS, Node 26). Head = this branch's fresh build (pnpm install --frozen-lockfile, compiler rebuilt, turbo --force, scripts/size npm ci); next = d231b9911, built fresh in a second worktree (the one carrying #3879's harness-only commit; its packages are next's to the byte) and measured with this head's harness (SIZE_PACKAGES_ROOT). Minified Δ is the firm number; brotli moves ±50–90 B with layout.

scenario next @ d231b99 (min / br) head (min / br) Δ min / Δ br cap
page: compiled base SC 109,461 / 35,146 105,970 / 34,321 −3,491 / −825 35.13 → 34.34 KB
page: compiled live SC 122,933 / 40,651 119,469 / 39,774 −3,464 / −877 40.66 → 39.79
page: base SC 105,413 / 33,910 103,087 / 33,387 −2,326 / −523 33.92 → 33.40 (floor)
page: live SC 117,456 / 37,610 115,130 / 37,073 −2,326 / −537 37.59 → 37.09 (floor)
page: base + router 144,238 / 45,996 141,912 / 45,503 −2,326 / −493 46.02 → 45.52
page: live + router 148,683 / 47,326 146,357 / 46,773 −2,326 / −553 47.29 → 46.79
app: compiled hydrating 99,672 / 31,155 98,995 / 30,991 −677 / −164 31.17 → 31.01
app: hydrating (no stores) 52,992 / 17,894 52,306 / 17,689 −686 / −205 17.91 → 17.70 (floor)
app: hydrating + every store 92,318 / 29,169 91,632 / 28,930 −686 / −239 29.19 → 28.94
app: compiled CSR 79,140 / 25,224 79,085 / 25,165 −55 / −59 25.24 → 25.18
app: compiled floor 28,418 / 10,121 28,353 / 10,107 −65 / −14 10.13 → 10.12
app: CSR 36,770 / 12,921 36,704 / 12,928 −66 / +7 12.96 → 12.94
app: CSR, observe tier 41,299 / 14,525 41,233 / 14,553 −66 / +28 14.53 → 14.57 (raised under the Size-Exception below: 23 B over the old cap on CI, brotli layout at −66 min; recorded minified lowered 41,284 → 41,233)
app: CSR, observe + attribution 86,926 / 28,854 86,860 / 28,823 −66 / −31 28.89 → 28.84
app: render + one signal 27,887 / 9,917 27,822 / 9,915 −65 / −2 9.93 (recorded minified 27,872 → 27,822)
frames: eager 33,418 / 11,112 33,462 / 11,113 +44 / +1 11.13 (unchanged; under)
signals ×3, server ×2 — — 0 / 0 —
page: hackernews (#3879's scenario, this head's packages) 145,471 / 47,849 143,186 / 47,368 −2,285 / −481 (on #3879: 47.86)

Caps ratcheted by ratchet.mjs (lower only, measured + 10 B at the 0.01 KB step, dated ledger lines): the three hydrating caps above, the six SC pages, and the four CSR/compiled lines whose isHydrating(node) lost the claim-roots walk. CI confirmed (Linux, Node 24; Size run on this head): every scenario reads the local bytes exactly — minified and brotli — so every lowered cap stands at CI measured + 10 B. One cap raised: app: CSR, observe tier 14.53 → 14.57 KB (CI measured 14,553 B + 10 B at the 0.01 KB step, commit 692b6582b), accepted by the maintainer as brotli layout (0 B minified); it is not a frozen floor, so check-floor-caps origin/next still reports no floor cap raised. Four scenarios sit above their recorded minified and are left alone as the ratchet does: the two signals floors (+15 B, next's own since the caps were seeded), frames: eager (+44, this PR's install call), renderToString (+4).

Size-Exception: maintainer accepted a 23 B brotli layout overage on CSR observe + attribution (0 B minified) and the (g-sc) SC-page cost, 2026-10-07

Skipped

  • (g) live-takeover install moved into the sf client's live module — not a static move. The document's g-live cut (−953 / −342) removes the takeover's gate machinery (nodeGate / liveGates / openScopes, armLiveTakeover, takeOver, openLiveScope / releaseLiveScope, and the arm in the latched branch of readSerializedOrCompute). That machinery is shared with ruling 55 — a dependency write while a node is latched arms the gate so exactly the diverged nodes re-run at scope release; no live() involved (solid/client-hydration › "latched divergence — mid-stream dependency changes commit at hydration end", 3 specs; hybrid-store-handoff's non-iterable shapes, 6). Moving it behind live turns those red on every non-live page. What is genuinely live-only — the LIVE_LOCAL adoption arm, the trace-detect arm, the LIVE_RESUME_FROM stamp in takeOver, the three registered symbols — measures −348 / −143 as a cut and −144 / −47 as a slot the sf client's live() would fill: glue 204 B min, over the document's 60 × 3 = 180 budget (rule 2), for ≈ 50 B br — and the sf client has no solid-js import today (its build's externals are seroval only), so the slot is a new package edge. Not built; the numbers are in the document's landed column.
  • (f) prod prose dev-gated — needs ruling 92 (§7 Q8 of the solid/web audit). Not built.
  • (b) lazy resume half — measured floor −360 br, honest net ≈ 0 to −0.3 KB after glue, for a new chunk, a server modulepreload and GH1–GH4 across a chunk boundary. Not built, as the document recommends.

#3879

size/hackernews-scenario re-based onto next @ d231b99 (--onto, its one commit), built fresh, cap re-set to local measured + 10 B (54.57 → 47.86 KB, capMinified 176,581 → 145,471), pushed with --force-with-lease, base changed to next, body re-written for the re-base. HN still mounts with dynamic(); its move to dynamicComponent is the example's own change (the demos pass), not the scenario's. On this branch's packages the scenario reads 143,186 / 47,368 (−2,285 / −481).

Title

The planned title named the live-takeover install move; it is not in this PR (see Skipped — not a static move), so the title does not claim it.

Docs

documentation/plans/hydration-split-measured.md cherry-picked from audit/hydration-split with a landed column on the §3 table, a landed table in §4 and a landed paragraph up top; one sentence in frames-savings-pass.md §4.

ryansolid and others added 6 commits October 7, 2026 18:38
`readShallow` called `sourceKeys(value, SOURCE_PROXY)` for a proxy-backed
class/style object. By definition that is `Reflect.ownKeys(value)` (leafOf
is the identity for a non-memo kind; leafKeys answers SOURCE_PROXY with one
ownKeys trap), but the kind is a runtime argument the bundler cannot fold,
so the merge/omit view walkers of @solidjs/signals (collectKeys,
mergeKeysOf, hiddenByAny, isHidden, addKey, leafOf, viewSource — 1,076 B
minified) rode along on every compiled page with a dynamic class or style,
reached by nothing. Read the keys directly.

Measured (scripts/size, local): page: compiled base SC 109,461 → 108,296
min / 35,146 → 34,802 br; compiled live SC −1,165 / −375; app: compiled
hydrating +11 / −9 (keeps the walkers through spread()).

hydration-split-measured.md candidate (e).
…wn copy

The store-family hydration adapters (createShadowDraft, quietAnswer,
hydrateStoreFromAsyncIterable, wrapStoreFn, hydrateStoreLikeFn,
hydrateStoreLike) move out of client/hydration.ts into
client/store-hydration.ts. enableHydration() still installs
hydrateStoreLike into the generic store slot (unchanged: a page with no
eager store wrapper shakes the install and the adapters with it), and
solid-js/internal/container-trace — the frames traces tier's
materializer, a lazy chunk — now bundles ITS OWN COPY of the module
(rollup: the adapter's ./hydration.js import resolves to the external
solid-js in that build) instead of reading the eager slot through
core.withStoreHydration. Reading the slot from the lazy chunk pinned
every adapter into the entry chunk of a server-component page that never
creates a client store: Rolldown assigns a module to the entry whenever
an eager module imports it, dead write or not (hydration-split-measured.md
§3.1). The second instance is inert by construction: the moved functions
declare no module-level state (verified on source — closure state per
call only), and every shared piece (sharedConfig, onHydrationEnd's
callbacks, readSerializedOrCompute's latch set, UNASKED, subFetch) is a
member read on the one solid-js instance, never a named import, so a
server-tier resolution of the trace entry stays inert.

Public surface (@internal, stripped from declarations): withStoreHydration
removed from the client and server entries (sole consumer gone); 13
runtime exports added to the client entry for the copy to read —
readSerializedOrCompute, subFetch, readHydratedValue, wrapFirstYield,
adoptedAnswerStream, withHydrationGate, onHydrationEnd, noHydrationId,
markTopLevelSnapshotScope, hasLoadingWindow, isAsyncIterable,
syncThenable, UNASKED — mirrored on the server entry as inert stubs (the
shapes they have with no hydration in progress) for export parity
(test/server/export-parity.spec). internal-surface.spec pins that none
reaches the declarations.

Measured (scripts/size, local): page: compiled base SC 108,296 → 105,746
min / 34,802 → 34,227 br (dist-edit prediction −2,511 / −563); compiled
live SC −2,539 / −544; app: hydrating (no stores) 0 / 0; hydrating +
stores 0 / −22; compiled hydrating 0 / +88 (layout — the adapters now
sit earlier in solid.js); lazy trace.js 8.18 → 8.71 KB br (the copy is
specialised by rollup to the materializer's call shape).

Pins: solid/container-trace, client-hydration (rulings 51/58/60),
hybrid-store-handoff, lifecycle-matrix/container-args, the
container-trace-hold specs, consistency C3(b), hybrid-store-handoff-3574,
buffered-projection-repeat, frames-container-lazy-{codec,document}.

Co-authored-by: Cursor <cursoragent@cursor.com>
… installServerComponents()

hydrate() → enableHydration() installed, on every hydrating page, what only
an integration that owns server markup wholesale reaches: the hold a client
takes on adopted markup (sharedConfig.holdBoundary, frames-rulings 3.1),
the claim window an adopted occurrence re-enters hydration through
(sharedConfig.hydrateWindow), fragment ownership by rendering (the _$HY.fa
term of the reveal policy — ownedFragment) and the ledger's published answer
(_$HY.fr = { pending, subscribe }); and the DOM runtime's claim walk carried
the declared-claim-roots connectivity test (isHydrating(node), ruling 95)
and the frame-region exclusion of the root sweep (gatherHydratable, ruling
97). All of it is SC-only (rulings 15's SC clause, 80, 95–97, 101), so it
installs from the frames client now:

- solid-js: enableServerComponentHydration() (@internal; runtime export on
  the main entry, typed on solid-js/internal, a no-op on the server entry)
  installs holdBoundary, hydrateWindow, the ownedFragment slot fragmentPolicy
  reads, and _$HY.fr. enableHydration()'s ledger block keys its once-marker
  on _$HY.f (the policy owner) instead of _$HY.fr, which it no longer
  publishes.
- @solidjs/web: installServerComponentHydration() (@internal) calls solid's
  installer and fills the claim-walk slot (the claimRoots test, the frame
  exclusion as a per-sweep containment closure) — one call for the
  integration, the shape hydrate() has (solid's half, then the DOM
  runtime's).
- frames client: installRevealHook() calls it first — once at
  installServerComponents() (ahead of the page's hydrate() and its root
  sweep, which the entry call precedes) and again at the first document
  boundary, where the page's _$HY necessarily exists (the ledger's answer
  needs it; a test's bootstrap may follow the entry call).

Static install-site move, no new async moment; a page without server
components never reaches any of it. Tests: hydrate-window.spec installs the
SC half itself; truncated-stream.spec (which probes the ledger through
_$HY.fr) installs it too; internal-surface pins the new seam on
solid-js/internal and off the main declarations.

Measured (scripts/size, local): app: compiled hydrating 99,683 → 98,995 min
/ 31,234 → 30,991 br; hydrating (no stores) −686 / −205; hydrating + stores
−686 / −217; compiled CSR −66 / −13 (the claimRoots walk was retained
there too). SC pages pay the installers' glue: page: compiled base SC +224
/ +94, base SC +236 / +86, compiled live +240 / +42; frames: eager +44 / +1.
Dist-edit prediction on the plain app: floor −722 / −200, real shape
−680 / −221.

Pins: consistency C1, C3 (a), C12, C14, adopted-swap-post-done,
boundary-arrival, frames-adopted-region-fragments,
frames-late-boundary-client, adopted-claim-args-address, truncated-stream,
solid/hydrate-window, the generic hydration arm GH1–GH6.

hydration-split-measured.md candidate (g-sc).
ratchet.mjs over this head's local measurement (macOS, Node 26; CI to
confirm): the hydrating caps come down — app: hydrating (no stores)
17.91 → 17.70 KB, hydrating + stores 29.19 → 28.94, compiled hydrating
31.17 → 31.01 — and the server-component pages' — compiled base SC
35.13 → 34.34, compiled live 40.66 → 39.79, base SC 33.92 → 33.40, live SC
37.59 → 37.09, base + router 46.02 → 45.52, live + router 47.29 → 46.79 —
plus compiled CSR 25.24 → 25.18, compiled floor 10.13 → 10.12, CSR
12.96 → 12.94, CSR observe + attribution 28.89 → 28.84 (the claim-roots
walk left every @solidjs/web bundle). Recorded minified lowered where it
fell (CSR observe tier, render + one signal). No cap raised; the four
scenarios above their recorded minified (the signals floors +15 B from
next's own fixes, frames: eager +44 B from the install call, renderToString
+4 B) are left alone, as the ratchet does.
Measurement-and-design audit, no source changes: how much of the
solid-js + @solidjs/web hydrating runtime could leave the eager entry
of page: compiled base SC under the no-async-in-sync-paths rule.

- Function-level attribution of the hydrating runtime by concern on the
  five compiled/hydrating scenarios (19,672 B min / ~5.9 KB br on the
  SC page, 17% of the page), plus the 1,076 B of store-view walkers
  readShallow retains by static reference.
- Every tier candidate measured as a cut on edited dist copies, with
  its already-async cover named; candidate (a) measured in its real
  shape (adapters in their own module riding the trace tier's chunk),
  glue included: -2,505 / -653. The readShallow line: -1,165 / -396.
  Live takeover install move: -953 / -342. Prod prose: -356 / -83.
- Finding: Rolldown assigns a module to the entry chunk whenever an
  eager module imports it statically, even with sideEffects:false and
  a dead write; the install-slot pattern pins a module eager by
  construction, so (a) needs the trace entry to carry its own copy.
- Honest total: compiled base SC 34,869 -> 33,464 B br (-4.0%);
  compiled hydrating 31,075 -> 30,458 (-617, the Phase A bytes back).
  The lazy resume half is ~0 to -0.3 KB after glue: not worth building.
- Recommendation: hydration first (readShallow today; (a) and the live
  install this week, ~3.5 days for ~1.4 KB with no semantic change),
  the frames-client rewrite after the binding-slots ruling.

Co-authored-by: Claude <noreply@anthropic.com>
hydration-split-measured.md (cherry-picked from audit/hydration-split):
a 'landed' column on the §3 candidate table and a landed table in §4 —
(e), (a), (g-sc) as built against the predictions; (g) not a static move
(the gate machinery is ruling 55's too; the live-only residue −348 / −143
as a cut, glue over budget as a slot); (g-sc)'s glue is paid by every SC
page (+224 / +94 on compiled base SC); (f) and (b) not built. One sentence
in frames-savings-pass.md §4: the pass returned the Phase A bytes to the
plain hydrating pages.
@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 692b658

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
solid-js Patch
@solidjs/web Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
test-integration Patch
@solidjs/universal Patch
todos-server-example Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/signals Patch

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

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Size (brotli, eager entry chunk)

scenario head vs base minified vs base minified vs recorded cap lazy chunks (not counted)
signals: core floor (createSignal/Memo/Effect/Root/flush) 7.43 KB 0 B 0 B +15 B 7.45 KB ✅
signals: + createStore 14.69 KB 0 B 0 B 0 B 14.70 KB ✅
signals: + isPending/latest 9.63 KB 0 B 0 B +15 B 9.65 KB ✅
app: render + one signal (the simple-app floor) 9.91 KB −2 B (−0.0%) −65 B 0 B 9.93 KB ✅
app: hydrating (no stores) with Show/For/Loading/Errored/lazy 17.69 KB −205 B (−1.1%) −686 B 0 B 17.70 KB ✅ lazy-page.js 0.04 KB
app: hydrating + every store primitive family 28.93 KB −239 B (−0.8%) −686 B 0 B 28.94 KB ✅ lazy-page.js 0.04 KB
app: CSR with Show/For/Loading/Errored/lazy 12.93 KB +7 B (+0.1%) −66 B 0 B 12.94 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier (same app on the observe artifacts) 14.55 KB +28 B (+0.2%) −66 B 0 B 14.57 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier + attribution engine enabled 28.82 KB −31 B (−0.1%) −66 B 0 B 28.84 KB ✅ lazy-page.js 0.04 KB
app: compiled floor (one template, one text hole, one delegated click) 10.11 KB −14 B (−0.1%) −65 B 0 B 10.12 KB ✅
app: compiled CSR (JSX todo app: spread/merge/omit, events, class/style, keyed For, Show, Loading + lazy, store) 25.16 KB −59 B (−0.2%) −55 B 0 B 25.18 KB ✅ stats.js 0.18 KB
app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable) 30.99 KB −164 B (−0.5%) −677 B 0 B 31.01 KB ✅ stats.js 0.20 KB
frames: eager client consumer (frames client + transport, lazy codec) 11.11 KB +1 B (+0.0%) +44 B +44 B 11.13 KB ✅
page: base server components (hydrating + dynamic + frames + sf reference) 33.39 KB −523 B (−1.5%) −2326 B 0 B 33.40 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.24 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, trace.js 8.73 KB, wire.js 0.93 KB
page: live server components (base + live/GET + action + isPending/latest) 37.07 KB −537 B (−1.4%) −2326 B 0 B 37.09 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.24 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, trace.js 8.74 KB, wire.js 0.93 KB
page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread) 34.32 KB −825 B (−2.3%) −3491 B 0 B 34.34 KB ✅ assets.js 0.78 KB, bind.js 1.82 KB, decode.js 6.24 KB, regions.js 0.80 KB, sc-comments.js 0.20 KB, trace.js 8.71 KB, wire.js 0.93 KB
page: compiled live server components (the compiled base page + live/GET + action + isPending/latest) 39.77 KB −877 B (−2.2%) −3464 B 0 B 39.79 KB ✅ eager (counted): web.js 21.16 KB; assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.24 KB, regions.js 0.79 KB, sc-comments.js 0.19 KB, trace.js 8.71 KB, wire.js 0.93 KB
page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate) 45.50 KB −493 B (−1.1%) −2326 B 0 B 45.52 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.24 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, server.js 1.02 KB, trace.js 8.73 KB, wire.js 0.93 KB
page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate) 46.77 KB −553 B (−1.2%) −2326 B 0 B 46.79 KB ✅ assets.js 0.78 KB, bind.js 1.84 KB, decode.js 6.24 KB, lazy-page.js 0.04 KB, regions.js 0.81 KB, server.js 1.02 KB, trace.js 8.75 KB, wire.js 0.94 KB
server: floor (getRequestEvent + isServer) 1.33 KB 0 B 0 B 0 B 1.34 KB ✅
server: renderToString (the server-render floor) 20.40 KB 0 B 0 B +4 B 20.42 KB ✅

Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. A scenario fails only when it is over its brotli cap and its minified size is more than 20 B over the minified recorded with the cap; over the cap within that allowance is brotli layout noise and passes with a warning. Caps and their recorded minified in scripts/size/scenarios.js; the floor and page caps in floor-caps.json are frozen (lower only, or Size-Exception: in the PR body). npm run ratchet lowers caps per RC; it never raises one (scripts/size/README.md).

@coveralls

coveralls commented Oct 8, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 37739432195

Coverage decreased (-0.02%) to 76.414%

Details

  • Coverage decreased (-0.02%) from the base build.
  • Patch coverage: 39 uncovered changes across 2 files (130 of 169 lines covered, 76.92%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
packages/solid/src/client/store-hydration.ts 154 121 78.57%
packages/solid/src/client/hydration.ts 13 7 53.85%
Total (4 files) 169 130 76.92%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1231
Covered Lines: 999
Line Coverage: 81.15%
Relevant Branches: 961
Covered Branches: 676
Branch Coverage: 70.34%
Branches in Coverage %: Yes
Coverage Strength: 28.3 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 13.59%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 187 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ memo + sync render effect only (reference) 28 ms 32.4 ms -13.59%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing size/hydration-pass (692b658) with next (8d23a5a)

Open in CodSpeed

…, brotli layout)

Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid marked this pull request as ready for review October 8, 2026 07:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants