Skip to content

test(signals): pin #3726 on L2 — derived store sync landing wakes parked readers (supersedes #3732) - #3789

Merged
ryansolid merged 2 commits into
nextfrom
port/3732-derived-sync-landing
Oct 5, 2026
Merged

ryansolid merged 2 commits into
nextfrom
port/3732-derived-sync-landing

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Supersedes #3732 by @brenelz — his three tests are kept verbatim and he is credited with Co-authored-by on the commits. Closes #3726.

Why the fix is subsumed by L2

#3732 patched store/next/projection.ts to retire STATUS_UNINITIALIZED after a synchronous landing so core's #3181 settle walk would release readers parked on the superseded first flight — readers whose store node the landing left unchanged ("length" in store, Object.keys, an unchanged length) had no value notification to fall back on and stayed blank inside a loading boundary. On the hold model the core already holds, with no store-side patch: pullFamily (store.ts) reads a derive with a flight up tracked, so the parked reader is linked to the firewall node itself; the sync re-derive's recompute(fw) has wasUninitialized → valueChanged → insertSubs, which re-runs those readers in the landing's flush (they pull the settled derive untracked and the stale link trims). Under a transaction's hold the same readers re-run as the transaction's work and reveal at land — the case #3732 listed as still open. The PR's three tests pass against untouched next; its source change is dropped (src/store/next/ is gone). This is tests-only: no changeset.

Verified on #3732's base (cce43eb44): the PR's tests are red 2/3 as it reports; every new pin below is red there, and the held-landing ones stay red with the PR's fix applied. All green on L2.

What this pins beyond #3732

  • The issue's own playground sequence: one promise whose setTimeout writes the source (the derive lands synchronously) and resolves the superseded promise in the same tick; Presence: under <Loading>, Source: outside; scheduler-driven (no manual flush()).
  • A verdict reader (isPending(() => "length" in store)) parked on the first flight: suspends while uninitialized (A19 exc. 1), answers false at the landing.
  • The two held-landing cases fix(signals): derived store sync landing wakes parked readers #3732 left open: a landing an action holds re-runs the parked readers as the transaction's work and reveals them at its commit (nothing published before; an untracked read throws NotReadyError until then, A25/A29) — with and without a <Loading> over them (fallback until the commit, content after).
  • GabbeV's board shape from the issue thread (fetched from the playground): async source behind a memo → derived createStore(fn, []) → createOptimisticStore → keyed <For>, remounted under a <Loading> that has already revealed, with a count outside the view. It is the held-landing variant, not a distinct bug. On L2 the revealed boundary holds the view switch (A29: a boundary already showing content holds like any reader) and the lane reveals with its rows as one frame — the count and the list never disagree. Pinned as the playground itself (web spec) and a signals reduction.
  • A @solidjs/web spec rendering both playgrounds as JSX (<Loading>, <For>, <Show>, hidden).

Files: packages/signals/tests/store/derived-presence-async-3726.test.ts (8 tests), packages/web/test/derived-presence-async-3726.spec.tsx (2 tests). RULES-INDEX.md regenerated (A19/A25/A29 rows gain the test's citations).

Tests

Public API changes

None.

ryansolid and others added 2 commits October 4, 2026 21:06
…ked readers

Port of #3732 (brenelz) onto the hold model. The PR patched
`store/next/projection.ts` to retire STATUS_UNINITIALIZED after a
synchronous landing so core's #3181 settle walk would release readers
parked on the superseded first flight: readers whose store node the
landing left unchanged (`"length" in store`, `Object.keys`, an unchanged
`length`) had no value notification to fall back on and stayed blank
inside a loading boundary while a sibling read of the source updated.

On L2 the core already holds, with no store-side patch: a reader of a
derive with a flight up is parked on the derive itself (`pullFamily`
links it, as a reader of a memo with a flight), and the derive's first
commit (uninitialized → a value) is a value change for every subscriber
(`recompute`: `wasUninitialized` → `insertSubs`) — the walk's
uninitialized exemption is moot. The PR's three tests pass unchanged;
its superseded source change is dropped (`src/store/next/` is gone).

Added: the report's sequence verbatim (the source write and the
superseded promise's resolution in one timer callback, under a loading
boundary, microtask-driven) in signals and as a `<Loading>` web spec; a
verdict probe parked on the first flight; and the case the PR listed as
open — a landing a transaction holds: the parked readers re-run as the
transaction's work and reveal at its commit, under a boundary and out
(red on the PR's base with and without its fix; green on L2).

Tests only — no changeset. RULES-INDEX
regenerated (A19/A25/A29 gain the test's citations).

Co-authored-by: Brenley Dueck <brenleydueck@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The #3726 comment's playground, fetched: an async card source behind a
memo (`createMemo(() => ready() ?? pending)`), a derived
`createStore(() => source(), [])`, `createOptimisticStore` over it and a
keyed `<For>`, remounted under a `<Loading>` that has already revealed;
a count outside the view. Before, Activity → Board left "All cards: 1"
beside an empty lane: the remounted readers were parked on the derive's
first flight under the boundary's hold, and the held synchronous landing
never woke them — the case PR #3732 listed as open (red on its base with
and without its fix). Same root-cause family as the report, not a
distinct bug.

On L2 the revealed boundary holds the view switch (A29: a boundary
already showing content holds like any reader) and the lane reveals
with its rows as one frame — the count and the list never disagree.
Pinned as the playground itself (web spec) and a signals reduction.

The report's own playground, fetched too: the `Source:` hole is outside
the `<Loading>` and the promise is created once — the first web test
and the signals "report's playground" test now follow it exactly.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2e24ac3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

Size (brotli, eager entry chunk)

scenario head vs base cap lazy chunks (not counted)
signals: core floor (createSignal/Memo/Effect/Root/flush) 7.32 KB 0 B 7.33 KB ✅
signals: + createStore 14.52 KB 0 B 14.53 KB ✅
signals: + isPending/latest 9.44 KB 0 B 9.45 KB ✅
app: render + one signal (the simple-app floor) 9.80 KB 0 B 9.81 KB ✅
app: hydrating (no stores) with Show/For/Loading/Errored/lazy 17.63 KB 0 B 17.64 KB ✅ lazy-page.js 0.04 KB
app: hydrating + every store primitive family 28.75 KB 0 B 28.78 KB ✅ lazy-page.js 0.04 KB
app: CSR with Show/For/Loading/Errored/lazy 12.81 KB 0 B 12.82 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier (same app on the observe artifacts) 14.38 KB 0 B 14.39 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier + attribution engine enabled 28.60 KB 0 B 28.61 KB ✅ lazy-page.js 0.04 KB
frames: eager client consumer (frames client + transport, lazy codec) 13.00 KB 0 B 13.00 KB ✅
page: base server components (hydrating + dynamic + frames + sf reference) 44.03 KB 0 B 44.03 KB ✅ decode.js 6.07 KB, lazy-page.js 0.04 KB
page: live server components (base + live/GET + action + isPending/latest) 47.65 KB 0 B 47.67 KB ✅ decode.js 6.07 KB, lazy-page.js 0.04 KB
server: floor (getRequestEvent + isServer) 1.33 KB 0 B 1.34 KB ✅
server: renderToString (the server-render floor) 20.41 KB 0 B 20.42 KB ✅

Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. Caps 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).

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 37262214084

Coverage remained the same at 75.991%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1195
Covered Lines: 962
Line Coverage: 80.5%
Relevant Branches: 925
Covered Branches: 649
Branch Coverage: 70.16%
Branches in Coverage %: Yes
Coverage Strength: 27.68 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Oct 5, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 8.68%

⚠️ 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) 26.9 ms 29.4 ms -8.68%

Tip

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


Comparing port/3732-derived-sync-landing (2e24ac3) with next (1a3f87f)

Open in CodSpeed

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