--- status: stable last_updated: 2026-05-18 --- # ADR-003: Preact signals-core for reactivity **Status**: Accepted ## Context UJSX needs a reactive primitive for propagating changes through element trees. The reactive layer (ReactiveRoot, reactiveComponent, reactiveElement) needs signal, computed, effect, and batch operations. ## Alternatives Considered - **Custom reactive implementation**: Write a signal/computed/effect system from scratch. Rejected because writing a correct implementation (cycle detection, batch scheduling, lazy evaluation) is non-trivial and Preact's implementation is battle-tested. - **Solid.js reactive primitives**: Use Solid's reactive system. Rejected because they're coupled to Solid's render cycle and not published as a standalone package. - **Vue reactivity**: Use Vue's reactive system. Rejected because it uses Proxy-based tracking which has different performance characteristics and isn't designed for tree-agnostic use. - **RxJS**: Use observables for reactivity. Rejected because it's an observable/stream model, not a reactive state model. Different paradigm, much larger API surface. ## Decision Use `@preact/signals-core` as the reactive layer. Re-export its primitives (`signal`, `computed`, `effect`, `batch`) and types (`Signal`, `ReadonlySignal`) from the UJSX reactive module. ## Consequences ### Positive - Proven implementation with ~800 lines of battle-tested code, used in Preact and adopted by other frameworks. - Small dependency surface — `@preact/signals-core` has zero dependencies. - Batch scheduling is built-in, matching the reconciliation model planned for the reconciler. - Future consumers (flowgraph reactive host) can use the same signal/computed/effect primitives without a different reactive library. ### Negative - UJSX re-exports Preact signals, creating a coupling. If Preact signals changes its scheduling model, UJSX is affected. - The `effect` return value (dispose function) is currently discarded in reactiveComponent/reactiveElement — the dispose functions are no-ops. This is a known gap that the reconciler work addresses.