Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

store

store app Name { state | derived | [live] data | effect | action | method } · store session Name { … }

`store` declares a headless reactive unit — the same members a component has, minus `render` — meant to hold UI state and actions shared across components with no backend identity. Its scope word is a closed set of exactly two keywords: `app` (one instance for the running app) or `session` (persisted across reloads, cleared on logout). ⚠ MEASURED 2026-09-10: the DECLARATION compiles, but nothing today lets a component READ a store's state or INVOKE its actions — a store's name is not in scope anywhere outside its own body. Read this page before reaching for `store` as a working reuse mechanism; it does not yet do what its name promises.

preview1 example compiled by CIuiauthoringstate

Summary#

A store is a component minus render — the same reactive members (state/derived/[live] data/ effect/action/a plain method), sharable rather than local to one component instance. It carries no backend identity: no table, no security block, nothing persists to the database. It is scoped by lifetime, and that scope word is a closed set of exactly two keywords.

⛔ What a store cannot do TODAY is the whole reason to read this page before writing one. Its declaration resolves and emits cleanly, but no mechanism anywhere — resolver, emitter, or client runtime — lets a component reach a store's name. Reading store.Whatever or calling store.SomeAction() from inside a component body fails with "unknown identifier", exactly as if the store did not exist. See [[#gap]].

Signature#

store app Name { … }       // one in-memory instance for the running app, gone on reload
store session Name { … }   // like `app`, but persisted across reloads and cleared on logout

Description#

The scope word — a closed set of exactly two keywords#

store <scope> Name { … } takes a scope immediately after the store keyword. It is a closed vocabulary of exactly two words:

ScopeLifetime
appOne instance for the running app, in memory, gone on reload.
sessionLike app, but persisted across reloads (cleared on logout).

A third word is a compile error naming the two real options — it used to be accepted SILENTLY: any identifier that was not app or session fell through to a third, unimplemented lifetime (ComponentProvided — see [[#component-provided]]) with no diagnostic at all, so a typo of either keyword (store sesion Cart { … }) compiled clean into a scope nothing could ever reach. That is fixed: an unrecognized scope word is refused with a did-you-mean.

Both scopes declare and resolve cleanly — this half of store genuinely works:

store app Toast {
  int Count = 0;
  live var any = Count > 0;
  action Error(string msg) { Count = Count + 1; }
}

store session Wizard {
  string Step = "start";
  action Advance(string step) { Step = step; }
}

Can a component read or call one today?#

No. A store's name is registered nowhere a component body looks names up — only routed/composable components are wired into the name table a component's body resolves against. Reaching for the store by its declared name from inside a component is refused as an unknown identifier, the same refusal an author would get for any name that was never declared:

store app Toast {
  int Count = 0;
  action Error(string msg) { Count = Count + 1; }
}

component ToastCheck() {
  action Boom() { Toast.Error("bad"); }   // error: unknown identifier 'Toast'
  render { Stack { Text($"{Toast.Count}"); } }   // error: unknown identifier 'Toast'
}

So today, store is a place to write shared reactive logic down, but not a place anything can reach it from. Until this lands, reach for one of these instead:

  • Shared, stateless logic (formatting, a computation more than one component needs) — a public method on a class, held as a field on whichever component needs it. This part is real and works today — see [[ui-action#choosing]].
  • Shared, PERSISTED truth (a cart's contents, anything that should survive a refresh and be visible to more than the browser tab that wrote it) — an entity with a reactive live var query, not UI state at all. This is what component calls "most 'global state' is actually backend data."
  • Genuinely ephemeral, cross-component UI-only state (is the command palette open, a toast queue, an in-progress wizard that must NOT touch the database) has no working substitute today — that gap is real, and store is the intended answer once the read/invoke path is built.

The third scope — reserved, not usable#

The underlying model (UiStoreScope) carries a THIRD lifetime beyond app/session: a store a component instantiates and provides to its own subtree, injected by type — for control-internal shared state (a multi-step form's fields shared across its own inputs; a grid's selection). This is a real, deliberate design point, not a guess: store's own scopes are lifetimes, and "shared with descendants of one component instance" is the third lifetime a scoped-state primitive naturally has.

It has no syntax today — no keyword spells it, nothing declares it, and no client runtime reads it. Do not guess at a spelling for it; none has been decided. The enum value stays reserved for when it is actually built, and the closed-set check above ([[#scopes]]) is what stops an attempt at writing it from being silently misread as something else.

See also#

  • component — the member grammar a store shares (state/derived/[live] data/effect/action/ method), and where "most global state is actually backend data" is explained in full.
  • action vs. method — the action/method distinction, unchanged inside a store.
  • class methods — the reuse mechanism that DOES work today for shared, stateless logic.

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

action vs. method

A component's `action` and its plain `method` run the exact same body grammar with the exact same capabilities — reads…

class methods

Behaviour attached to a class — a method with a receiver, called as value.Method(). Classes are in-memory values, so a…