← the Atlas

The reactor engine — decided

Reference·seedling·accepted·

The ratified engine decision for the reactive bridge: @preact/signals-core behind reactor.ts now; @solidjs/signals the named swap target once Solid 2.0 and its ecosystem stabilize; kolu's frontend stays on Solid 1.9. Grounded in a live-Node-probed comparison of six engines — today's choice is maintenance posture, not architecture, because reactor.ts is a proven two-way door.

The decision. The reactive bridge’s engine is @preact/signals-core, behind reactor.ts, now; @solidjs/signals is the named swap target once Solid 2.0 and its ecosystem stabilize; kolu’s frontend stays on Solid 1.9. Despite the name, @preact/signals-core imports nothing from preact — it is a framework-free graph library (zero deps, no DOM/global reach, fully synchronous) that happens to live in preact’s monorepo; choosing it couples kolu to nothing React-shaped. And because reactor.ts is a demonstrated two-way door (below), today’s choice is maintenance posture and default semantics, not architecture. Everything here is grounded in source reads and live Node probes (Node 24.14.1, 2026-07-10).

Why preact wins today

It is the only candidate that passes every bridge law with zero patches in reactor.ts: explicit nested batch() with promptly reads (a signal modified within a batch reads updated — kolu’s read-your-writes law); a throwing computed caches and rethrows on every read until a dependency changes, then recovers (probed) — loud, never swallowed; effect disposers + cleanup + per-signal watched/unwatched hooks (a direct fit for “a member knows when it has subscribers”) and GC-able unreferenced subgraphs — built for long-lived processes. The one gap — no custom equals option — is a non-cost: the bridge’s law already places equals at the single writer’s publish gate inside the framework (server.ts:198-207); the engine never needs to own it.

The eliminations, one line each

The Solid 2.0 findings

The two-way door, demonstrated

The same four-primitive contract — signal · computed (glitch-free lazy pull + equality-cascade stop) · effect (with disposal) · batch — was exercised on five engines in this comparison with only surface-syntax differences. The wrapper owns the three things engines disagree on — equals gating, error policy, flush discipline — so those laws are pinned by reactor.ts’s own tests, not by engine behavior. A later swap to @solidjs/signals is mechanical: rename the calls, add the flush()-at-publish rule, run the law tests.

The probed comparison

@preact/signals-core @vue/reactivity alien-signals @reactively/core signal-polyfill (TC39) solid-js 1.x deep-import @solidjs/signals 2.0β
Node viability (probed) ✅ zero deps, no globals ✅ (+@vue/shared) ✅ zero deps ⚠️ works via dist/solid.js; dual-graph trap, unsupported ✅ core pkg; solid-js itself still server-stubbed
Glitch-free + batch ✅ probed; explicit nested batch(), promptly reads ✅ probed; no batch export — hand-roll scheduler ✅ probed; startBatch/endBatch, sync flush ✅ algorithm; manual stabilize() graph yes; no effect/batch — build on Watcher ✅ (browser build) ✅ probed; implicit microtask batch, reads stale until flush()
Equality-cascade stop / spec-equals ✅ stop probed; no equals opt → gate in reactor (where kolu’s law puts it anyway) ✅ stop probed; no equals opt ✅; no equals, prev param workaround ✅ custom equals ✅ spec equals ✅ solid equals opt ✅ solid equals opt
Effect disposal / long-lived ownership ✅ disposer + cleanup + watched/unwatched hooks; GC-able subgraphs effectScope (probed) ✅ disposer + scope no dispose API ⚠️ DIY on Watcher createRoot createRoot (probed)
Error propagation ✅ cached + rethrown every read, recovers (probed) — fail-fast ✅ throws at reader/setter (probed) swallows: 2nd read returns undefined (probed) ❌ none ✅ ERRORED rethrow solid semantics solid semantics
Maintenance ✅ Preact team, commit 3 days ago, stable 1.x ✅ Vue team; 3.6 swaps internals ✅ active; StackBlitz; Vue 3.6 basis ❌ dead (2023) ❌ 17 mo stale; “do not use in production” ✅ solid team (but usage unsupported) ⚠️ beta, active daily
Size/deps 259 KB unpacked, 0 deps (min ~4 KB) 343 KB + 1 dep 47 KB, 0 deps 11 KB, 0 deps 142 KB, 0 deps 1 MB pkg 357 KB, 0 deps
reactor.ts adaptation trivial (.value↔fn, equals gate) medium (write the scheduler) small + must add error discipline large (disposal, scheduler) large (write effect+batch) none, but fragile none API-wise; add flush()-at-publish
Future alignment neutral; easy to leave 3.6 internals churn behind stable API becomes Vue’s core lineage moved to @solidjs/signals Stage 1, yrs away dead end (2.0 replaces it) the named future target