Reactive System in Depth
The previous page showed how to use signal, computed, and effect.
This page answers a different question:
why is Ansiq designed this way?
Start with one rule
Ansiq keeps a strict boundary:
- the reactive system decides what became dirty
- the runtime decides how the terminal should update
If that boundary collapses, several bad things happen quickly:
- dependency propagation gets mixed with UI updates
- it becomes hard to tell whether a bug is in the graph or the layout system
- localized updates become much harder to optimize
What the reactive graph actually records
You can think of Ansiq’s reactive graph as three kinds of nodes:
signalcomputedeffect
signal
signal is the base source of reactive state:
- what is the current value?
- who depends on it?
computed
computed is derived state:
- it should not become duplicated business state
- it is calculated from other reactive values
effect
effect is the side-effect boundary:
- it is not where UI is produced
- it is not a second state container
- it reacts after tracked reads change
One important note here:
effectis not React’suseEffect.
It has no dependency array. Dependencies are tracked automatically from reactive reads during execution.
What an update actually looks like
When you call signal.set(...) or signal.set_if_changed(...), Ansiq does not jump straight to drawing the terminal. It first propagates dirty information:
The important part is where the reactive graph stops:
- it marks dirty scopes
- then it hands control to the runtime
It does not decide:
- which ancestors must relayout
- which regions should redraw
- what exact terminal patches get emitted
Those are runtime decisions.
Why Ansiq does not center everything on whole-component rerender
If every state change automatically implied “rerender the whole component tree”, then:
- small input changes would expand into large updates
- subtree replacement would become much less useful
- the damage model would stay coarse
Ansiq wants a tighter path:
signal -> dirty scope -> subtree replacement -> partial relayout -> patch
That is one of the main reasons it feels closer to Vue / Solid than to a React-style “state update means rerender everything nearby” model.
Where effect fits
effect has a narrow but important job:
- react to tracked changes
- trigger side effects
- avoid becoming the place where UI computations live
Good uses:
- logging
- syncing non-UI side effects
- task coordination
- triggering follow-up messages
Bad uses:
- replacing
computed - replacing application state
- hiding all business logic inside one reactive callback
A practical rule:
- for displayed derived values, prefer
computed - for source-of-truth state, prefer
signal - for behavior and side effects, prefer
effect
Why this helps debugging
Once the boundary is clear, debugging gets much easier. You can ask:
- is dependency propagation wrong?
- is dirty scope collection wrong?
- or is rerender / relayout / patching wrong?
If the reactive graph and the UI tree are mixed together, those questions blur into each other very quickly.
Where to go next
If you want the full architectural picture, continue with: