Files
ujsx/docs/architecture/decisions/003-preact-signals-for-reactivity.md
glm-5.1 0d5b9d5ea8 stabilize architecture docs: address review findings and advance to stable
Critical fixes:
- Restructure pointers.md: move setNode prop-key writes section under
  its own heading (was incorrectly nested under selectNode)
- Add Context/Density/Direction/RenderContext documentation section
  to host-config.md (was only a brief constraint bullet)
- Advance all 5 ADRs from Status: Proposed → Accepted and frontmatter
  from status: draft → status: stable (decisions are driving implementation)
- Add error handling philosophy section to README

Warning/suggestion fixes:
- Add isUElement null check (node !== null) to schema.md discriminator table
- Add UjsxEnvelope convenience type documentation to events.md
- Add Direction Unicode arrow naming note to transforms.md
- Standardize all cross-references from absolute docs/research/ paths
  to relative ../research/ paths across all architecture docs
- Fix schema.md ADR references to use relative paths
- Reduce redundancy between transforms.md and host-config.md Direction notes
- Update all architecture doc frontmatter from draft → stable

Deferred:
- Performance model section (reconciler not yet built)
- Concepts/glossary document (low ROI at current scale)
- Line counts in source references (would date quickly)
2026-05-18 16:10:24 +00:00

2.1 KiB

status, last_updated
status last_updated
stable 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.