test(signals): pin #3726 on L2 — derived store sync landing wakes parked readers (supersedes #3732) - #3789
Conversation
…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>
|
Size (brotli, eager entry chunk)
Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. Caps in |
Coverage Report for CI Build 37262214084Coverage remained the same at 75.991%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will degrade performance by 8.68%
|
| 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)
Supersedes #3732 by @brenelz — his three tests are kept verbatim and he is credited with
Co-authored-byon the commits. Closes #3726.Why the fix is subsumed by L2
#3732 patched
store/next/projection.tsto retireSTATUS_UNINITIALIZEDafter 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 unchangedlength) 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'srecompute(fw)haswasUninitialized → 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 atland— the case #3732 listed as still open. The PR's three tests pass against untouchednext; 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
setTimeoutwrites the source (the derive lands synchronously) and resolves the superseded promise in the same tick;Presence:under<Loading>,Source:outside; scheduler-driven (no manualflush()).isPending(() => "length" in store)) parked on the first flight: suspends while uninitialized (A19 exc. 1), answersfalseat the landing.NotReadyErroruntil then, A25/A29) — with and without a<Loading>over them (fallback until the commit, content after).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.@solidjs/webspec 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.mdregenerated (A19/A25/A29 rows gain the test's citations).Tests
@solidjs/signals: 4894 passed / 0 failed / 2 skipped → 4902 / 0 / 2 (+8).rules-index --checkgreen.@solidjs/web(default config): 1132 / 0 → 1134 / 0 (+2). Server 1411/0/2 and hydrate 275/0 unchanged.next@1a3f87fd1(signals: benchmarks measure the built package; the fold queue's pre-batch record (CodSpeed follow-up to #3774) #3776), no conflicts; signals dist rebuilt before the web run.Public API changes
None.