Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c1625d9
stage 2 replayed onto the absorbed monorepo — patch channel current a…
ryansolid Aug 27, 2026
8221d32
test(web): driver/classic equivalence matrix + fix(store): slot emiss…
ryansolid Aug 25, 2026
b6575a3
feat(patch-lists): projection family lists drivable — chained patch r…
ryansolid Aug 25, 2026
fc61cb3
feat(patch-lists): optimistic family lists drivable — family channel …
ryansolid Aug 25, 2026
727f306
docs(design): §17 — family channel complete, equivalence matrix recor…
ryansolid Aug 25, 2026
ce04cbe
test(server): restore next's hydrationRecordKeys-based assertions dro…
ryansolid Aug 26, 2026
010da13
test(harness): restore hydration-records.ts deleted by the stage-2 re…
ryansolid Aug 26, 2026
578c8a3
test(web): equivalence matrix helper accepts projection mode (type-only)
ryansolid Aug 26, 2026
9744177
docs(design): §18 — published-pairing impact sweep + A/B; portal-swar…
ryansolid Aug 26, 2026
8b7a196
perf(patch-channel): pay-for-use — driver and emitters shake out of n…
ryansolid Aug 27, 2026
6299227
docs(design): §19 — pay-for-use restructure record
ryansolid Aug 27, 2026
d793a59
fix(patch-channel): re-audit hardening — lifecycle, transitions, demo…
ryansolid Aug 27, 2026
c0dd857
docs(design): §20 — re-audit hardening record
ryansolid Aug 27, 2026
9cd07d9
fix: restore next work reverted by the stage-2 replay (pre-merge sweep)
ryansolid Aug 27, 2026
5277307
chore: pre-merge sweep — changeset audit + dormancy comment
ryansolid Aug 27, 2026
1e65b5f
perf(patch-channel): two-tier hook arming — flip-preview size scenari…
ryansolid Aug 27, 2026
c0caf84
chore: changeset for two-tier hook arming
ryansolid Aug 27, 2026
681c915
perf(store): reconcile walk emission guards short-circuit on hooks bi…
ryansolid Aug 27, 2026
0a557ee
fix(patch-channel): second re-audit — adoption seams, exception safet…
ryansolid Aug 27, 2026
ca63d0e
chore: changeset for the second re-audit batch
ryansolid Aug 28, 2026
d599581
fix(patch-channel): third re-audit — coalescing correctness, window s…
ryansolid Aug 28, 2026
053b0bf
docs(design): §21 — re-audit rounds 2-3 record
ryansolid Aug 28, 2026
3924c15
fix(store): shallow slot alignment is SameValueZero (self-sweep finding)
ryansolid Aug 28, 2026
d3e8010
docs: retract the reverted-stash retention note — transitions never a…
ryansolid Aug 28, 2026
adab543
perf(store): adoption-seam accessor demotion is dev-only — prod never…
ryansolid Aug 28, 2026
adf10e9
fix(patch-channel): fifth re-audit — adopted-flag no-ops, merge stamp…
ryansolid Aug 28, 2026
5c8026a
Merge remote-tracking branch 'origin/next' into stage2-channel
ryansolid Aug 28, 2026
4135eff
size: ratchet app floors for the next-merge sum (insert seam + useHea…
ryansolid Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/audit-driver-family-decline-multi-slot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@solidjs/signals": patch
"solid-js": patch
"@solidjs/web": patch
---

External-audit fixes on the patch-list driver surface: family (projection/optimistic) arrays now decline the driver — their structural changes emit no row/slot ops and the proxy identity is stable, so an engaged list would freeze on optimistic or projection structure (classic mapArray handles them correctly, including on identity-swap handoff). Shallow slot-patch registration is now multi-consumer — two driven lists over one shallow array previously overwrote each other's channel. Adds `storeHasFamily` (with server stub) and regression tests for both.
5 changes: 5 additions & 0 deletions .changeset/fix-array-target-dictionary-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/signals": patch
---

Group the write-side patch-channel fields (`wk`, `p`, `ro`, `sp`) into one lazily-allocated `pc` extension on store targets and delete the dead prototype binding registry (`b`). Array proxy targets carry their fields as named properties on a real array, and V8 normalizes arrays to dictionary properties as the named count grows — at 24 fields every trap read had become a hash lookup (~15% uibench, tree-heavy scenarios worst). The target is capped at 20 named fields with the shape rule documented; future patch-channel state goes inside `pc`.
5 changes: 5 additions & 0 deletions .changeset/fix-slot-emission-append-race.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/signals": patch
---

Fix shallow slot-patch emission racing row creation: appended positions past a fully-aligned prefix (vacuously aligned when the previous list was empty) emitted slot value-ticks for rows that do not exist yet — the slot queue applies before the row ops that create them, crashing the list driver on clear-then-refill and pure appends. Slots now dispatch only for indices with a previous slot; appends are structure-only. Found by the driver/classic equivalence matrix.
5 changes: 5 additions & 0 deletions .changeset/jfb-driver-and-write-bound.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/web": patch
---

The list driver's identity matching unwraps store proxies on both sides — draft-authored permutations store row proxies verbatim, and matching them against raw records rebuilt every surviving row (caught by the JFB keyed-reorder identity gate).
6 changes: 6 additions & 0 deletions .changeset/optimistic-lists-drivable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Optimistic family arrays are drivable by the patch-mode list driver, completing the family channel: structural optimism (push/splice/reorder/replace in optimistic drafts) emits identity-diffed row ops at lane timing from the override channel — visible in flight, bypassing the transition stash like optimistic record patches — and reverts emit an identity RESYNC the driver resolves against the live post-revert view. The driver binds optimistic lists from the optimistic view (classic reads the same view through the proxy), and the identity-swap matcher is shared between swaps and resyncs. Equivalence matrix extended with async optimistic scenarios (mounted → in-flight → settled, revert and land, element-level and parent-key structural writes).
6 changes: 6 additions & 0 deletions .changeset/patch-channel-pay-for-use.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Patch channel is pay-for-use: the list driver and `patchDriver` moved out of the always-retained web runtime into `patch-driver.ts`, arming the insert seam lazily from `rowProof`/`patchDriver` (which only compiled patch-mode output imports); the store's emitters ride hooks installed at first registration (`patch-hooks.ts`) instead of static imports. Apps without patch-mode output retain only a ~100 B insert hook; the store write-path seams cost ~490 B on the store floor. Before this, every client app carried the full driver (~2.4 KB brotli).
19 changes: 19 additions & 0 deletions .changeset/patch-channel-pr-a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@solidjs/signals": patch
---

Stage 2 (PR-A): the patch channel. Compiled per-record patch consumers
(`registerPatch`, undocumented compiler-contract export) dispatched by store
visibility transitions at all four sites: adoption walk and setter notify
(plain stores, with ancestor bubbling for targeted nested writes), fold
commit (projections — held folds hold their patches), and the override
lifecycle (application emits the visible draft; consumption and engine
reverts force-reapply from the live view). Application timing: per-flush
apply queue at render-effect phase; transition-stamped emissions release
when THEIR batch commits (reverted transactions drop by GC); optimistic
emissions drain at lane-effect timing so in-flight visibility works while
actions stash the regular queues. Unpatched stores pay a null check and the
module tree-shakes out of non-store bundles. Gauntlet: effect-phase timing,
reconcile prev pairing, nested-write bubbling, unbind/multi-consumer,
transition hold, optimistic in-flight + DOM revert, projection refetch,
disposed-owner drop.
6 changes: 6 additions & 0 deletions .changeset/patch-channel-reaudit-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Second re-audit hardening of the patch channel: adoption seams demote accessor-bearing adoptees to tracked effects in development, with a loud diagnostic (production emits directly — per-adoption accessor scans cost ~12% of dbmon's tick, and getter-bearing adoptees on patched records are a development-caught shape); setter-returned root replacements and chained-store swaps emit their patches and row ops at fold commit; the list driver's ops application builds every new row before any destructive step (a throwing row factory leaves DOM and bookkeeping atomically unchanged); patch errors route to the nearest computed ancestor so `Errored.reset()` can recompute it (reset also skips non-computed sources), and unhandled patch errors halt like unhandled effect errors; key equality is SameValueZero and occurrence-aware everywhere keys compare — NaN keys stay retained and duplicate keys adopt per occurrence on both channels; same-batch duplicate patch emissions coalesce (one application per batch, effect parity).
6 changes: 6 additions & 0 deletions .changeset/patch-channel-reaudit-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Third re-audit hardening of the patch channel: same-batch coalescing updates the queued entry in place (latest `next` wins — adoption replaces the captured object, so dropping later emissions applied stale state) and the drain clears the channel stamps (no batch retention on quiet records); the adoption remainder window builds from the misalignment point so prefix-consumed rows are never re-offered to duplicate keys; optimistic tentative matching gains SameValueZero + occurrence-aware parity with the plain channel; a failed row-ops application forces an identity resync on the next update (the store committed the failed topology while DOM kept the old one — positional ops would mis-index) and suppresses slot ticks until the baseline is restored; a throwing row factory also severs its own partial registrations.
6 changes: 6 additions & 0 deletions .changeset/patch-channel-reaudit-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Fifth-round hardening of the patch channel: no-op adoptions (A→B→A in one batch) clear the adopted flag so later setter row ops never freeze a driven list; transition merges retarget the moved entries' coalescing stamps (post-merge emissions coalesce instead of double-applying at commit); multi-consumer patch dispatch snapshots the registration list (a callback unbinding a sibling no longer skips consumers); the list driver's initial construction severs partial registrations on throw like update-time builds (one failed initial render no longer elevates patchCount globally); a failed apply actively resyncs from the next slot tick instead of waiting for a structural update; and identity swaps register the new subject's channels before applying so a throwing swap stays recoverable.
8 changes: 8 additions & 0 deletions .changeset/patch-channel-reaudit-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
"@solidjs/babel-plugin": patch
"@solidjs/compiler": patch
---

Patch-channel contract hardening from the stage-2 re-audit: ordinary `patchDriver` registrations unbind with their owner (entries no longer leak past unmount); merged transitions move their held-patch stash so no patch strands; the optimistic drain shares the normal drain's per-entry error isolation and boundary routing; accessor-bearing records are excluded at admission (scan-before-trust) and records that acquire accessors demote their patches to tracked effect fallbacks; writable projection arrays emit setter row ops at their fold-commit visibility moment; row-ops/slot registrations resolve chained backings to the ultimate owner; duplicate keys match occurrence-aware instead of first-wins; the production dev-token typo (`_DX_DEV_`) is fixed; `patchDriver: true` normalizes identically in Babel and the native loader, the option is typed in `TransformOptions`, and a `dom-patch` parity tier ratchets patch-mode output across both compilers (currently byte-identical on all fixtures).
10 changes: 10 additions & 0 deletions .changeset/patch-channel-row-ops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@solidjs/signals": patch
---

Stage 2 (PR-B): row ops. The keyed adoption walk emits structural list ops
(`registerRowOps`: prefix, sources, removed) through the same apply queue as
record patches — aligned value ticks emit nothing; consumers apply minimal
DOM moves via one LIS over data ops instead of re-deriving moves from DOM
node arrays. Measured on dbmon: sort 10.7 → 4.5ms, remount 25.7 → 9.3ms
(octane 4.0/8.5), while ticks stay ahead (3.0/0.9 vs 3.2/1.3).
11 changes: 11 additions & 0 deletions .changeset/patch-fallback-semantics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Patch-channel semantics completion: a throwing patch now routes through its
registering owner's queue chain to the enclosing error boundary (render-
effect parity; sibling isolation preserved, unhandled errors still rethrow),
and the dual-driver effect fallback splits phases with the same compiled
body — a next===prev read pass tracks in compute, the force apply writes in
the effect phase where transitions and batching expect DOM writes
7 changes: 7 additions & 0 deletions .changeset/patch-held-on-transition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@solidjs/signals": patch
---

Patch-channel held emissions stash directly on their transition object
instead of a WeakMap — the every-flush commit-hook check becomes one
property read, and reverted transitions drop their stash with the object
12 changes: 12 additions & 0 deletions .changeset/patch-list-hydration-claim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@solidjs/web": minor
---

Patch-mode list hydration: claim + register only. The list driver claims each
server row positionally through the row's own `_hk` key (a row-scoped
explicit-id owner makes the compiled template's getNextElement resolve it),
and patchDriver skips the initial force-apply while hydrating — server HTML
stays the truth until the first transition. All driver-side `each` reads and
the probe are id-isolated (throwaway/private explicit-id owners), so lazily
minted prop-getter memos can no longer shift the ambient hydration id chain
on either the engage or decline path.
6 changes: 6 additions & 0 deletions .changeset/patch-list-identity-ruling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"solid-js": patch
"@solidjs/web": patch
---

Patch-mode lists now implement the identity semantics the view declares instead of the reconcile key's. Deep lists are unaffected (adoption preserves proxy identity, so key ops and reference semantics coincide). Shallow reference-keyed lists rebuild rows whose records were replaced — matching classic `mapArray` exactly, where the driver previously patched them in place (a default-on compiler mode must never change observable DOM identity). `For` forwards its `keyed` prop on the list metadata; explicit `keyed={fn}` lists decline the driver until the accessor-row binding contract lands.
15 changes: 15 additions & 0 deletions .changeset/patch-mode-list-driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@solidjs/signals": minor
"solid-js": minor
"@solidjs/web": minor
---

Patch-mode list driver: keyed `<For>` over a store array is offered to the
runtime's row-ops driver (create/bind at op-apply, LIS moves, node removal —
no mapArray, no per-row owners, no DOM-side reconcile). `For` carries `$ll`
metadata on a lazy classic accessor so unaware renderers and declined lists
(non-store subject, impure rows proven by a bind-time owner probe, fallback
or index usage) fall through to today's mapArray path unchanged. Array
identity swaps keep keyed semantics by raw-identity matching. Adds
`ownerIsBlank` (signals) for the purity probe and `driveList` (web, rxcore
seam) for the runtime.
6 changes: 6 additions & 0 deletions .changeset/patch-two-tier-arming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solidjs/signals": patch
"@solidjs/web": patch
---

Patch-channel arming is two-tier so the default-on cost stays proportional: `patchDriver` no longer retains the list driver (only `rowProof` — the compiled marker of a patch-mode list — arms the insert seam), and the store emitters split into value hooks (armed by `registerPatch`) and row hooks (armed by list registrations), so non-list patch templates never retain row binding, LIS, or reconcile's diff builders. Flip-preview size scenarios pin both tiers.
5 changes: 5 additions & 0 deletions .changeset/per-row-patch-unbinds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/web": patch
---

Patch-mode lists retain per-row unbind handles: a record the app keeps beyond its row's life no longer holds a live patch registration updating detached DOM — registrations are severed on row removal, contract-leave handoffs, and list disposal. Dev builds also warn when a stamped row's build attaches computations or cleanups to the shared list owner (owned work in handler/attribute value position is unsupported in patch-mode rows).
7 changes: 7 additions & 0 deletions .changeset/projection-lists-drivable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@solidjs/signals": patch
"solid-js": patch
"@solidjs/web": patch
---

Projection (non-optimistic) family arrays are drivable by the patch-mode list driver: their recomputes walk reconcile, whose row/slot emissions were never family-gated and ride the transition-stamped apply queue. The blanket family decline narrows to optimistic families only (`storeHasOptimisticFamily`), whose user writes ride node overrides and emit no structural ops. Fixes chained-backing patch registration: a projection wrapper's backing is another store's proxy, so `registerPatch`/`patchableRaw` now resolve through the chain to the ultimate owner target — patches registered on wrapped projection rows previously never fired (value transitions fold on the source). Equivalence matrix extended with 13 projection scenarios including recompute-driven structure and retention topology.
5 changes: 5 additions & 0 deletions .changeset/reconcile-walk-guard-hoist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/signals": patch
---

The reconcile walk's patch-emission guards short-circuit on the installed-hooks binding before touching target fields, so stores without any patch consumer pay no per-record loads in the adoption walk (CodSpeed caught −7.7% on the 12k-path listened-paths bench).
7 changes: 7 additions & 0 deletions .changeset/row-proof-admission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@solidjs/web": patch
"solid-js": patch
"@solidjs/signals": patch
---

Patch-mode list admission moves entirely to compile time: driveList engages only for row functions carrying the compiler's `rowProof` stamp (exported from @solidjs/web), and the runtime purity probe is deleted — no speculative execution of user row code, no probeMark/probeGate seams, no ownerIsBlank, no tentative empty-list engagement with late decline. Unstamped rows take the classic mapArray path before any DOM work; `lateClassic` remains only for engaged lists whose subject later leaves the contract (identity swap to a derived array, shallow/deep kind switch).
12 changes: 12 additions & 0 deletions .changeset/setter-row-ops-tentative-lists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@solidjs/signals": minor
"@solidjs/web": minor
---

Close two list-driver coverage gaps found by the JFB store scenario: setter-
channel structural mutation (push/splice/index assignment/permutation) now
emits identity-keyed row ops at the fold — a driven list stays DOM-correct
for stores mutated without reconcile — and empty-initial lists engage
TENTATIVELY, deferring the purity probe to the first created row, with a
late decline handing the region to the classic mapArray path through the
runtime's re-entry thunk
12 changes: 12 additions & 0 deletions .changeset/shallow-compiled-slot-channel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@solidjs/signals": minor
"@solidjs/web": minor
---

Shallow store lists through the compiled driver: slot patches graduate from
prototype to channel semantics (key-aligned value-replaced slots only —
structure rides row ops — queued at effect phase under the registration
owner), and the list driver collects a shallow row's compiled bodies at bind
(rows are raw; nothing to register on) and dispatches them from the array's
slot channel, rebasing indices with structural ops. Adds storeIsShallow;
kind-changing subject swaps (shallow <-> deep) hand off to classic.
5 changes: 5 additions & 0 deletions .changeset/shallow-slot-alignment-samevaluezero.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/signals": patch
---

The shallow branch's slot-alignment prefix compares keys with SameValueZero: strict equality broke alignment on NaN keys, suppressing the slot's value ticks while the ops builder retained the row — a permanently stale DOM row (found by a full-surface self-sweep of every key-comparison site).
Loading
Loading