Ramonda

The reactivity model

When you change a @state field, Ramonda re-runs the whole component's render() and updates the page to match. It does not track which field each line of your render read and update only the part that used the changed value. This page is why that's a deliberate choice, not a missing feature.

Coarse on purpose

Ramonda could track every read in render() and update surgically. It doesn't, because the bookkeeping rarely pays. When you change a component's state, you almost always change what it shows — that is usually why you changed it — so asking "did this particular field matter to this particular line?" mostly answers "yes". Re-running one render() and patching only the DOM that actually differs is already cheap, and it is simpler to hold in your head: a component re-renders on any of its own state changes, full stop, with no dependency graph to track.

So the mental model stays small. You don't wire fields to pieces of the screen, and you don't get caught out by a change that "should" have updated something but didn't because a tracker missed a read.

Where fine-grained tracking does live

The framework does track individual reads — just not in render(). @compute and a subscription watch exactly the signals their body reads and re-run only when one of those changes. That is where precise tracking earns its keep: a derived total that is expensive to recompute, or a subscription that should only reconnect when its channel changes. Reach for them when you need to react to one specific value; use plain @state for everything else.

A hook's props callback is fine-grained

A hook's props are a callback — this.use(Chart, () => ({ range: this.range })) — and the framework tracks it the way it tracks a @compute: the callback is cached on the signals it reads, re-run on a render where one of them moved, and skipped on a render where none did. Writing a hook has the shape; what belongs here is what it means for the model.

The owner's render and a hook's props are two different questions. The owner re-renders on any of its own state, coarsely, as above. Each use() on it then asks the narrow one: did anything this bag reads move? Ten hooks on a component and one changed signal is one render and one rebuilt bag.

A bag of constants is built once — a callback that reads no signal runs at mount and never again, and the functions inside it keep their identity. So a hook is not disturbed by its owner re-rendering for an unrelated reason. (A development build calls it more than that: a second time at mount, to catch a value that is not a function of state, and once per render to check the cache has not gone stale. Both results are thrown away — the hook is handed the one bag either way.)

Inside the hook, each prop is its own signal, so only the keys whose values actually moved wake anything — the per-key behaviour context has, one level down.

The trade is not free, and it is worth knowing which way it runs. A skipped call is one boolean; a re-run detaches, re-tracks and re-attaches the callback's dependency set, which is more work than calling it unconditionally was. It pays when hooks outnumber the signals that changed, which is the shape of a hook-heavy component.

The one way this cache can hand a hook a stale value is a bag that reads something no signal backs — a module-level variable, an object mutated in place — because nothing marks the cache dirty. Under the double render a development build does, the framework calls the callback anyway and compares, and reports the difference as RMD027. Keep what a bag reads in @state, a @compute, or props.

Props are coarse like state; context is per-key

It is worth being precise about two things that look alike but aren't.

Props are coarse, like @state. A component re-renders whenever its parent hands it a shallowly-different set of props — regardless of which props render() actually reads. A change to a prop the component never touches still re-renders it. (A signal per prop does exist, and it serves whatever READS that prop under tracking — a @compute, a subscription's connect, a hook's props callback. The component's own render is not one of those: it re-renders on the props bag, not on the keys it read.) To skip a prop-driven re-render in the rare case it matters, gate it with @ShouldUpdateOnPropsChange.

Context is per-key — and this one is free. A consumer reacts only to the keys it reads: read ctx.theme and it re-renders when theme changes, but not when a sibling accent does. That falls out of how context is published — each key is its own signal a consumer subscribes to directly, with no parent-to-child pass in between. It is why a Navigator reading only pathname doesn't re-render when a query parameter changes.

The division of labour, then: @state and a component's props are coarse and simple; @compute, @watchProp, subscriptions, context and a hook's props callback are fine-grained where it matters.