Repository navigation
fix(signals): optimistic store guesses survive re-asks, answer latest() and stay per-action (#4329) - #4330
Conversation
…ile its action is pending (#4329) A derive that writes a keyed list through its draft (`d.length = 0; d.push(...rows)`, or `d.issues = rows`) replaced the guessed row with a fresh object, so the guess, which lives on the old row's target, left the screen before its action settled. The draft's new object at a guessed row's slot (by key in a keyed list) is now adopted by that row's target, as a reconcile landing already does: the guess stays until the commit (D-34) and guesses on other rows stay independent (D-33). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
🦋 Changeset detectedLatest commit: 66deb9b The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 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 |
Size (brotli, eager entry chunk)
|
|
The size It's waiting on a maintainer call: raise the cap with a Generated by Claude Code |
Coverage Report for CI Build 40595552182Coverage remained the same at 78.727%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will degrade performance by 13.23%2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
…ctions independent (#4329) - The draft path's pairing reuses reconcile: the container above a guessed row reconciles the new object at its slot, so both landing paths share one keyed pairing and one leaf diff. - reconcile judges a key under a guess against the guess too, so a truth equal to the covered value still answers it: latest() reads the truth, the screen keeps the guess until the commit (D-34). - supersede no longer joins the landing's tick to the parent hold; the parent takes the node, and passes deriving from the held truth join on their own. One re-ask answering two actions' guesses no longer ties them together (D-33). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2
|
CodSpeed flags 3 benches: None of the three runs code this PR changes. Every change is on an optimistic path: the I'm re-running CodSpeed once to confirm. Generated by Claude Code |
|
On the re-run, CodSpeed still flags
The bench makes no store and no guess, so none of this PR's changed lines run in it. I'm treating it as runner noise and not changing the code. If a maintainer wants, the regression can be acknowledged in CodSpeed. Generated by Claude Code |
… lingering - ref/SOLID_NEXT and the crate's solidjs-compiler rev move to ac28157a (rc.19 plus solidjs/solid#4330). All ref suites pass with zero divergences. - eval builds against that checkout's packages. Round 3's four #4329 failures pass on it, as its README now records. - Round 4 overlap tests: when two adds or removes on one list are out, a declined one may stay until the other settles (one change, H-11; Ryan 2026-10-28: desired). The one still out must not go. - .rx references for close and comments use the plain Solid pattern: a whole-list guess, then refresh. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_0117BoWKEv4MghW8Ns9LeUUX
Summary
Before: with
createOptimisticStore, a per-key guess made inside a pending action (setStore(d => { d[1].assignee = "ann" }), thenyield save.p) disappeared when a re-ask of the store (refresh(store)) landed before the action settled: the screen went from1:null 3:annto1:null 3:nullwhilesavewas still pending. That happened when the derive writes the list through its draft (d.length = 0; d.push(...rows)ord.issues = rows). Two related gaps showed up on both landing paths (draft writes and returned rows):latest(() => store[1].assignee)kept returning the guess when the truth equalled the value the guess covered, and one re-ask landing different truths on two rows, each with its own pending action, tied the two actions together, so a row kept its guess after its own action settled.After: the screen keeps the guess until its action commits (RULES-DERIVED D-34), as
createOptimisticalready does.latest()answers the truth while the screen keeps the guess. Guesses from different actions stay independent (D-33): each row reverts or commits when its own action settles, and rows with no guess show their new truth at the landing.How
store/store.tsnotifyWrites: when the derive's draft writes an optimistic family, a new optimistic hookpairruns before the nodes are notified.store/optimistic.tspairRows: for each guessed row, it finds the container under the written target and the new object at its slot (by key in a keyed list) and runsreconcileStateon it, so the draft path shares reconcile's keyed descent and leaf diff instead of a second routine.store/reconcile.tsapplyAdopt: a key under a guess is judged against the guess too (one line behind the existingoptHookscheck), so a truth equal to the covered value still answers it.core/lanes.tssupersede: dropsjoinFuture(parent). The parent still holds the corrected node, and a pass that derives from the held truth joins the parent on its own (joinPass). The removed join made a whole landing join each guess's hold, which tied two rows' actions together. This also applies tocreateOptimisticsignals; every S-33/S-34 and spec-async pin passes.No new public exports, no new effects. The hooks are installed only by optimistic.ts.
How did you test this change?
packages/signals/tests/optimistic-key-guess-reask-4329.test.ts, 17 tests, run on both derive forms (draft writes and returned rows) where it applies: the issue repro (the row keeps its proxy), the committed value aftersavesettles with nonullframe, two rows with one or both actions pending, the nestedd.issuesshapes,latest()equal to the truth while the screen keeps the guess, independent commits for two actions answered by one landing, a no-guess row showing its truth beside a corrected guess, andcreateOptimisticparity. Againstnext'spackages/signals/src, 14 of the 17 fail; all 17 pass on this branch.packages/signalspnpm test: 355 files, 5646 passed (2 expected fail, 2 skipped).packages/solidpnpm test: 52 files, 897 passed.tsc -p tsconfig.build.jsonis clean. Store benches (write-floor, projection-root-write, reconcile-dbmon, reconcile-tree), 3 rounds alternating base and head: within run-to-run noise.Size (vs
next@ d2323b0, brotli / minified)The store scenario's cap goes 30.96 → 31.08 KB (recorded minified 95,147 → 95,378) with a ledger line in
scripts/size/scenarios.js.Size-Exception: +123 B brotli / +284 B minified on
app: hydrating + every store primitive familyfor the #4329 fix (correctness; only apps usingcreateOptimisticStorepay), accepted by the maintainer 2026-10-27.Fixes #4329
🤖 Generated with Claude Code
https://claude.ai/code/session_015hPxMkC3jg3Uivh65gvaf2