Repository navigation
fix: optimistic release without torn frames (sink slot records, keyed guess fold) - #4210
Conversation
Each arg binding of a slot occurrence re-sends the whole record as it sweeps, so the first copy carried the other args' previous values and went out alone, ahead of the holes the same commit moved. The chat's outbox dropped a pending message before the thread showed its row. Inside a sweep the record now joins the sweep's unit like hole and attr records (C13): the pass's last re-emission stands and leaves with them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
🦋 Changeset detectedLatest commit: 3cd7d4e The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 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)
|
Coverage Report for CI Build 39704959958Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage remained the same at 77.662%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will degrade performance by 24.38%2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
… voiding it Two overlapping actions pushing onto one keyed optimistic store: when the stream echoed the first write while the second was open, the correction dissolved the lane. For that window the store read torn (a hole at the first row's index, `length` from the guess, a raw target served for a row), and at the commit the confirmed row remounted twice, which destroyed an open editor and its draft in the board example. A keyed arrangement guess is rows by key, so a current truth that differs now folds into the hold beneath it (D-34, amended): the guess stays the lane's value and the screen's until the hold commits, and rows match by key there. Deriving passes outside the lane read the hold's world (each guess's truth, else what it covered) and join the hold, as for any held truth (#3164); `latest()` shows it; `until`/`resolve` keep their postures. Keys born under a folded guess are held with it. Lane work in a frame that already has a transaction reads that frame's stagings as the screen, as a verdict reader does, so a list pass never pairs the truth's length with the guess's rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
The fold of a correction under a keyed arrangement guess moves into store/optimistic.ts behind GlobalQueue hooks (`fold`, `foldedRead`, `foldedHold`), so the lane engine and plain stores carry only the calls. The hooks and `Transaction.folded` are not `_`-prefixed: a new mangled name shifts every later one in the dist and costs the floors bytes. The lane staged-screen read is gated on the lane engine, so the core floor is unchanged. Caps raised with ledger entries, pending the maintainer's acceptance: + createStore (+35 B minified), + isPending/latest (+142 B), hydrating + every store primitive family (+694 B: it carries the fold itself), and CSR (+0 B minified; brotli layout noise from the shifted mangled names on a scenario next already had at the allowance). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
Generated by Claude Code |
…ntil's posture - read(): a lane pass reads the frame's stagings as the screen only over a folded lane, gated on the lane engine (floors +0 B minified). - One store hook for the re-base and the fold (_laneRebase), and a folded lane carries the store's reader (Transaction.folded): fewer unmangled names in core. - D-34: a keyed guess the screen never showed is void at a correction, as any guess's; the fold applies to a shown one only. - laneRead: the fold's arm stays before the authoritative one so an until outside the lane joins the hold (D-31); pinned by a new test. - Size caps re-recorded on the golfed numbers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
|
CodSpeed on f143b9c flags "props chain depth 1: build + consume" at −51%. This PR doesn't change anything that benchmark runs: it only calls Earlier flags on this PR show the same pattern:
I don't have the means to re-run the CodSpeed check from here. Generated by Claude Code |
… raises Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
…-f38ibp # Conflicts: # scripts/size/scenarios.js
Raises the live server components frozen floor (Size-Exception in the PR). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
The keyed fold's +44 B was left above the recorded minified, which leaves next 44 B past it and only 20 B of allowance for later PRs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018LY7F9czoRqdMEvk2QwPr1
…ved-keys set fold's guard was re-deriving "keyed container with a LaneView of rows" on every landing; the setter already knows, and now always gives a keyed array's view a removed-keys set (empty or not). -59 B minified where createOptimisticStore is used. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9
Requested by Ryan · project thread
Summary
This PR fixes two places where releasing an optimistic value could show a frame that is torn or can't be reconciled.
1.
@solidjs/web: a sweep's slot re-emissions join the sweep's unitA live server component could send a torn write. Each arg binding of a slot occurrence re-sends the whole slot record as it sweeps.
frame-sink.tsslot()calledputdirectly, so the first copy went out in its own message. That copy carried the other args' previous values, and it went out before the message carrying the holes from the same commit.In the chat example, one commit went out as
slot:composer { seen: <new>, busy: false }on its own, thenslot:composer { …, busy: true }+hole:lh:2. The client applies each message in its own flush, so the pending bubble left the outbox before the thread showed the server's row. In Chromium the two landed 0.4 to 7 ms apart, sometimes in separate tasks.Inside a sweep,
slot()now writes into the sweep's unit (swept.r), ashole()andattrrecords already do (C13: one sweep, one frame). Outside a sweep, nothing changes.2.
@solidjs/signals: a shown keyed arrangement guess folds a correction instead of voiding itTwo overlapping actions push onto one keyed optimistic store, and the stream echoes the first write while the second is open. The correction dissolved the lane. For that window the store read torn: a hole at the first row's index,
lengthtaken from the guess, and a rawStoreTargetserved for a row. At the commit, the confirmed row remounted twice. In the board example this destroyed an open title editor and its draft.A keyed arrangement guess is rows by key. So when a correction differs from a guess the screen has shown, it now folds into the hold beneath the guess. RULES-DERIVED D-34 is amended to say so, and F-11 still holds: the screen keeps what it showed until the hold commits.
latest()shows that world.untilandresolvekeep their D-31 postures. InlaneRead, the fold's arm comes before the authoritative arm on purpose. It serves anuntiloutside the lane the same truth_truthwould, and also joins it to the hold, which is its posture over a foreign hold. With the arms reversed, three pinned D-31 posture tests fail, and a new test inoptimistic-keyed-fold.test.tspins the order.undefined. The check is gated on the lane engine, so the floors don't carry it.The fold lives in
store/optimistic.ts. The lane engine reaches it through the existing_laneRebasehook, which now takes anolderflag and handles both the re-base and the fold. A keyed arrangement guess is recognized by its view's removed-keys set, which the setter now always gives a keyed array's view. A folded lane carries the store's reader of it asTransaction.folded. That property is deliberately not_-prefixed, because a new mangled name shifts every later one in the dist.No existing test changed.
Size
Size-Exception: the maintainer accepted the keyed-fold size cost (2026-10-22). After merging
next@ db8241c (#4213 re-recorded the floors), the live server components frozen floor carries the lane engine's fold hooks (+110 B, as+ isPending/latest).The other floors are +0 B minified. Following review, the fold was trimmed from the first version:
+ isPending/latestwent from +142 to +110 B, and the all-stores app from +694 to +569 B. Every cap below was re-measured overnext@ db8241c, has a ledger entry, and was accepted by the maintainer:floor-caps.json)CSR is +0 B minified and compiled CSR is +44 B; both stay under next's caps. All numbers were measured locally on Linux; earlier rounds matched CI exactly.
How did you test this change?
test/server/frame-sweep-ops.spec.tsx: one commit produces exactly one batch,[slot, hole], withbusy: true. It fails onnext.tests/optimistic-keyed-fold.test.tshas 9 cases:untilover a folded lane, which delivers at the commit (D-31);next.server-functions-adapter-request"rebuild failure" test, which fails only on Node 22.22 here.nextfor both. CodSpeed also warns that base and head ran on different runner types.<li>went#5 → #7 → #8and focus ended on<body>. Now it keeps its identity and the editor keeps focus and its draft.🤖 Generated with Claude Code
https://claude.ai/code/session_015SnZadCVdDSeDzxBUhcMU9