--- name: stack-moving-to-react description: Astrolabe's UI stack is React + Zustand (migrated from Preact + signals at M0) metadata: type: project --- The UI stack is **React + Zustand** (user decision, 2026-06-03, executed at M0 before feature work). It started as Preact + `@preact/signals`; a brief intermediate step migrated to React + `@preact/signals-react`, but the final call is **React + Zustand** — signals were dropped. **Why React:** the Preact pain was React-ecosystem friction (real-React-only libraries not cooperating with `preact/compat`) — *not* the signals model. Real React removes that whole class of problem and makes borrowing from the reference [[vega-editor-reference-clone]] (a React app) port directly. **Why Zustand over keeping signals:** signals were never the problem, but switching framework was the one cheap moment (M0, ~nothing implemented) to also pick the lowest-future-migration-risk state lib. Zustand is idiomatic React, has first-class outside-React access (`getState`/`setState`/`subscribe`) that fits the "logic lives in core/services, not components" architecture, and carries no build-time transform. The signals→Zustand cost was only rewriting unimplemented docs. **How to apply:** Stores are `create()` modules exporting a `useXStore` hook (state + actions in one object); components read via `useXStore(selector)` (+ `useShallow` for object selections); non-component code uses `getState()/setState()/subscribe()`; derive in selectors, never store derived fields. See `docs/architecture/01-state-and-stores.md`. The repo-wide Preact/signals→React/Zustand doc sweep is **done** — no stale "Preact"/"signals" wording should remain except where it describes the sibling project Syto, Vega's own signals, or plain English.