Minor Changes
- f0c3692: Point-of-pain discovery for diagnostics:
DEV.diagnostics.setConsoleFooter(fn)registers a footer printed once per diagnostic code after that code's first console report. solid-js registers a footer in dev pointing at its shipped repair skill (node_modules/solid-js/skills/reactivity-diagnostics/SKILL.md), so anyone — human or agent — hitting a diagnostic warning learns where the prescribed fix lives without prior knowledge of the skill system. - 8d249c7: 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).Forcarries$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 anddriveList(web, rxcore
seam) for the runtime. - 8d249c7: 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 - 8d249c7: 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.
Patch Changes
-
8d249c7: 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. -
8d249c7: Group the write-side patch-channel fields (
wk,p,ro,sp) into one lazily-allocatedpcextension 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 insidepc. -
505c73d: Fix
isPendingmemos reading inconsistently during an action-held transition (#3078)Two defects stacked in the report:
- Untracked top-level reads of subscriber-less auto-dispose memos were destructive:
read()tore the node down inline (unobserved) and the next read revived it with a full recompute in the ambient transition/lane context, so consecutive reads could answerfalse → true → falsewith no write in between. Reads now queue the node for a re-validating dormancy sweep at the top of the next flush — reads are idempotent within a tick, the leak protection is preserved (reclamation within one microtask; the enqueue armsschedule()), and a same-tick dirtying is reclaimed instead of recomputed, matching the old compute counts across flushes. - The fresh-read pairing rule (#2831/#3028) suppressed the pending verdict for any compute that saw a staged plain write while an action still held the transition, making
createMemo(() => isPending(x))reportfalsewhile a directisPending(x)probe reportedtruefor the whole action window. A plain signal's staged write is an input to a computation still in flight, not a landed answer awaiting reveal:heldAwaitingAsyncnow treats an unsettled action as "still computing" for input-staged (non-computed) nodes, while a computed's landed async answer keeps the pairing rule even inside an open action.
- Untracked top-level reads of subscriber-less auto-dispose memos were destructive:
-
de9e3cb: Fix projection stores leaking unsettled writes to effect-phase readers (#3082)
A derived (projection) store's pending backing is authoritative-elect and served to context-free readers, but that clause also caught CHILDREN_FORBIDDEN execution scopes (
onSettled/createTrackedEffectcallbacks), sosetStore(...)followed by a read inside the callback returned the staged draft while a signal write in the same scope correctly read committed. Projection reads in those scopes now get committed visibility, restoring the #3006 contract (a callback never observes its own unsettled write) and store/signal parity. -
0e37f90: Fix projection transition isolation and
latest()parity (#3074, #3075). A projection recompute deriving from transition-held sources committed its output through the eager adoption channel, so untracked readers saw the uncommitted value afterflush()while reads of the source signal correctly stayed committed. Adoption under a live hold now stages a held view: committed-visibility readers keep the pre-hold backing until the transition commits (speculative readers — drafts, owner-context computeds,latest()— see the adopted backing). Andlatest()now works through projections: store traps never reach coreread()without an observer, so the get trap pulls the projection computed up to date under the latest window and serves the in-flight derivation — signal/memo parity. -
8d249c7: 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.
-
b96d7ce: Fix store folds silently stranding while any transition is in flight (#3089). The fold queue armed a drain only when it was empty, assuming every drain clears it — but a held re-queue or an incomplete-transition flush (which skips
commitPendingNodesentirely) leaves entries behind afterscheduledis consumed, and every later fold was then queued without ever scheduling a drain: the committed backing froze at stale state (a derived store's seed) while its nodes committed, and readers on different rails saw torn state —length0,Object.keys["0"], the element intact.queueFoldnow always arms the scheduler, and folds carry a write-time transition stamp so drafts written under a still-running transition — including unobserved keys, which have no pending node for the drain's held check to see — defer to that transition's settle instead of landing in whichever flush drains next. -
8d249c7: 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).
-
8d249c7: Patch channel is pay-for-use: the list driver and
patchDrivermoved out of the always-retained web runtime intopatch-driver.ts, arming the insert seam lazily fromrowProof/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). -
8d249c7: 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. -
8d249c7: 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). -
8d249c7: Third re-audit hardening of the patch channel: same-batch coalescing updates the queued entry in place (latest
nextwins — 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. -
8d249c7: 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.
-
8d249c7: Patch-channel contract hardening from the stage-2 re-audit: ordinary
patchDriverregistrations 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: truenormalizes identically in Babel and the native loader, the option is typed inTransformOptions, and adom-patchparity tier ratchets patch-mode output across both compilers (currently byte-identical on all fixtures). -
8d249c7: 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). -
8d249c7: 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 -
8d249c7: 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 -
8d249c7: Patch-channel arming is two-tier so the default-on cost stays proportional:
patchDriverno longer retains the list driver (onlyrowProof— the compiled marker of a patch-mode list — arms the insert seam), and the store emitters split into value hooks (armed byregisterPatch) 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. -
8d249c7: 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, soregisterPatch/patchableRawnow 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. -
8d249c7: 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).
-
8d249c7: Patch-mode list admission moves entirely to compile time: driveList engages only for row functions carrying the compiler's
rowProofstamp (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;lateClassicremains only for engaged lists whose subject later leaves the contract (identity swap to a derived array, shallow/deep kind switch). -
8d249c7: 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).
-
ba6c0b6: Effects dispatch status through one shared notifier instead of storing it per node:
effect()stored the module-levelnotifyEffectStatuson every effect throughext(), allocating the full 19-fieldNodeExtensionat every effect creation — +127 B/node and +23% effect creation time, shipped unnoticed with the stage-3 cold-field split. Status walks now resolve the notifier viastatusNotifierOf(an own_xchannel — boundaries — wins; effect nodes fall back to the shared one), which preserves the walks' display-consumer membership semantics exactly. Effect nodes drop to 488 B (below even the pre-stage-3 528) and creation recovers to ~1.07 ms/10k from 1.26.