List.replace(key, value)— guaranteed item mutation: Updates the value of an existing item in place, propagating to all subscribers regardless of how they subscribed.byKey(key).set(value)only propagates throughitemSignal → listNodeedges, which are established lazily whenlist.get()is called; effects that subscribed vialist.keys(),list.length, or the iterator never trigger that path and receive no notification.replace()closes this gap by also walkingnode.sinksdirectly — the same structural propagation path used byadd(),remove(), andsort(). Signal identity is preserved: theState<T>returned bybyKey(key)is the same object before and after. No-op if the key does not exist or the value is reference-equal to the current value.
cause-effectskill for consumer projects: New Claude Code skill with self-contained API knowledge inreferences/— no library source access required. Covers three workflows:use-api,debug, andanswer-question.README.mdUtilities section: Documents the previously undocumentedcreateSignal,createMutableSignal,createComputedfactories andisSignal,isMutableSignal,isComputedpredicates exported fromindex.ts.
cause-effect-devskill restructured: Refactored to progressive disclosure pattern with separateworkflows/andreferences/modules. Scoped explicitly to library development; external references toREQUIREMENTS.md,ARCHITECTURE.md, andsrc/are now clearly library-repo-only.- Documentation alignment: Corrected wrong graph node type for
StateinARCHITECTURE.md; added missingFLAG_RELINKandsrc/signal.tstocopilot-instructions.md; updatedREQUIREMENTS.mdstability section to reflect 1.0 release; completed and corrected JSDoc acrossSensor,Memo,Store,List,Collection, and utility types. No runtime behaviour changed. - TypeScript 6 compatibility: Added
erasableSyntaxOnlytotsconfig.json(requires TS ≥5.8); replaced@types/bunwithbun-typesdirectly and added"types": ["bun-types"]totsconfig.jsonto fix module resolution under TypeScript 6. - Package management cleanup: Added
typescripttodevDependencies(was only inpeerDependencies, causing stale version installs); updatedpeerDependenciesrange to>=5.8.0; removedpackage-lock.jsonand gitignored npm/yarn/pnpm lockfiles — Bun is required for development. - Zed editor configuration: Disabled ESLint language server for JS/TS/TSX in
.zed/settings.json— project uses Biome for linting.
- Stricter TypeScript configuration: Enabled
noUncheckedIndexedAccess,exactOptionalPropertyTypes,useUnknownInCatchVariables,noUncheckedSideEffectImports, andnoFallthroughCasesInSwitchintsconfig.json. All internal array and indexed object accesses have been updated to satisfy these checks. Runtime behaviour is unchanged. stopon node types now typed asCleanup | undefined: Thestopproperty inSourceFields(and by extensionStateNode,MemoNode,TaskNode) is now declaredstop?: Cleanup | undefinedrather thanstop?: Cleanup. UnderexactOptionalPropertyTypes, this is required to allow clearing the property by assignment (= undefined) rather than deletion — preserving V8 hidden-class stability on hot-path nodes. Consumers readingstopfrom a node should already be handlingundefinedsince the property is optional, but TypeScript will now surface this requirement explicitly.guardon options types now requires explicit presence: UnderexactOptionalPropertyTypes, passing{ guard: undefined }toSignalOptions,ComputedOptions, orSensorOptionsis now a type error. Omit the property entirely to leave it unset.
unown(fn)— escape hatch for DOM-owned component lifecycles: Runs a callback withactiveOwnerset tonull, preventing anycreateScopeorcreateEffectcalls inside from being registered as children of the current active owner. Use this inconnectedCallback(or any external lifecycle hook) when a component manages its own cleanup independently viadisconnectedCallbackrather than through the reactive ownership tree.
- Scope disposal bug when
connectedCallbackfires inside a re-runnable effect: Previously, callingcreateScopeinside a reactive effect (e.g. a list sync effect) registered the scope'sdisposeon that effect's cleanup list. When the effect re-ran — for example, because aMutationObserverfired — it calledrunCleanup, disposing all child scopes including those belonging to already-connected custom elements. This silently removed event listeners and reactive subscriptions from components that were still live in the DOM. Wrapping theconnectedCallbackbody inunown(() => createScope(...))detaches the scope from the effect's ownership, so effect re-runs no longer dispose it.
- Watched
invalidate()now respectsequalsat every graph level: Previously, callinginvalidate()from a Memo or Taskwatchedcallback propagatedFLAG_DIRTYdirectly to effect sinks, causing unconditional re-runs even when the recomputed value was unchanged. Nowinvalidate()delegates topropagate(node), which marks the node itselfFLAG_DIRTYand propagatesFLAG_CHECKto downstream sinks. During flush, effects verify their sources viarefresh()— if the memo'sequalsfunction determines the value is unchanged, the effect is cleaned without running. This eliminates unnecessary effect executions for watched memos with custom equality or stable return values.
propagate()supportsFLAG_CHECKfor effect nodes: The effect branch ofpropagate()now respects thenewFlagparameter instead of unconditionally settingFLAG_DIRTY. Effects are enqueued only on first notification; subsequent propagations escalate the flag (e.g.,CHECK→DIRTY) without re-enqueuing.flush()processesFLAG_CHECKeffects: The flush loop now callsrefresh()on effects with eitherFLAG_DIRTYorFLAG_CHECK, enabling the check-sources-first path for effects.- Task
invalidate()aborts eagerly: Task watched callbacks now abort in-flight computations immediately duringpropagate()rather than deferring torecomputeTask(), consistent with the normal dependency-change path.
- Slot signal (
createSlot,isSlot): A stable reactive source that delegates reads and writes to a swappable backing signal. Designed for integration layers (e.g. custom element systems) where a property position must switch its backing signal — from a local writableStateto a parent-controlledMemo— without breaking existing subscribers. The slot object doubles as a property descriptor forObject.defineProperty().replace(nextSignal)swaps the backing signal and invalidates downstream subscribers;current()returns the currently delegated signal. Options mirror State: optionalguardandequals.
match()now preserves tuple types: Theokhandler correctly receives per-position types (e.g.,[number, string]) instead of a widened union (e.g.,(number | string)[]). Thesignalsparameter andMatchHandlerstype now usereadonly [...T]to preserve tuple inference.
watchedpropagation throughderiveCollection()chains: When an effect reads a derived collection, thewatchedcallback on the source List, Store, or Collection now activates correctly — even through multiple levels of.deriveCollection()chaining. Previously,deriveCollectiondid not propagate sink subscriptions back to the source'swatchedlifecycle.- Stable
watchedlifecycle during mutations: Adding, removing, or sorting items on a List (or Store/Collection) consumed throughderiveCollection()no longer tears down and restarts thewatchedcallback. The watcher remains active as long as at least one downstream effect is subscribed. - Cleanup cascade on disposal: When the last effect unsubscribes from a derived collection chain, cleanup now propagates upstream through all intermediate nodes to the source, correctly invoking the
watchedcleanup function.
FLAG_RELINKreplaces source-nulling in composite signals: Store, List, Collection, and deriveCollection no longer null outnode.sources/node.sourcesTailon structural mutations. Instead, a newFLAG_RELINKbitmap flag triggers a trackedrefresh()on the next.get()call, re-establishing edges cleanly vialink()/trimSources()without orphaning them.- Cascading
trimSources()inunlink(): When a MemoNode loses all sinks, its own sources are now trimmed recursively, ensuring upstreamwatchedcleanup propagates correctly through intermediate nodes. - Three-path
ensureFresh()inderiveCollection: The internal freshness check now distinguishes between fast path (has sources, clean), first subscriber (has sinks but no sources yet), and no subscriber (untracked build). This prevents prematurewatchedactivation during initialization.
- Memo
watched(invalidate)option:createMemo(fn, { watched })accepts a lazy lifecycle callback that receives aninvalidatefunction. Callinginvalidate()marks the memo dirty and triggers re-evaluation. The callback is invoked on first sink attachment and cleaned up when the last sink detaches. This enables patterns like DOM observation where a memo re-derives its value in response to external events (e.g., MutationObserver) without needing a separate Sensor. - Task
watched(invalidate)option: Same pattern as Memo. Callinginvalidate()aborts any in-flight computation and triggers re-execution. CollectionChanges<T>type: New typed interface for collection mutations withadd?: T[],change?: T[],remove?: T[]arrays. Replaces the untypedDiffResultrecords previously used byCollectionCallback.SensorOptions<T>type: Dedicated options type forcreateSensor, extendingSignalOptions<T>with optionalvalue.CollectionChangesexport from public API (index.ts).SensorOptionsexport from public API (index.ts).
createSensorparameter renamed:start→watchedfor consistency with Store/List lifecycle terminology.createSensoroptions type:ComputedOptions<T>→SensorOptions<T>. This decouples Sensor options fromComputedOptions, which now carries thewatched(invalidate)field for Memo/Task.createCollectionparameter renamed:start→watchedfor consistency.CollectionCallbackis now generic:CollectionCallback→CollectionCallback<T>. TheapplyChangesparameter acceptsCollectionChanges<T>instead ofDiffResult.CollectionOptions.createItemsignature:(key: string, value: T) => Signal<T>→(value: T) => Signal<T>. Key generation is now handled internally.KeyConfig<T>return type relaxed: Key functions may now returnstring | undefined. Returningundefinedfalls back to synthetic key generation.
DiffResultremoved from public API: No longer re-exported fromindex.ts. The type remains available fromsrc/nodes/list.tsfor internal use but is superseded byCollectionChanges<T>for collection mutations.
Baseline release. Factory function API (createState, createMemo, createTask, createEffect, createStore, createList, createCollection, createSensor) with linked-list graph engine.