Co-occurrence: all members present (→ record) or all absent. A partial set
is a parse failure. mandatory true forbids the all-absent case too — the
link only matches when every member is present (link branches only).
What a @stores row may hold: a keyed Store or a Unit. S is the
state a read of the regent returns — the keyed collection for a store,
the value for a unit — so one typed lookup serves both kinds.
A POSITION in the queue that answers ledger.at(...) with its typed
Handle — the spec instance carries the handle type, so the lookup is
fully typed with no name in between. Every regent kind is a position
(store → StoreMemory, unit → UnitMemory, guard → GuardMemory), and
the two positions nobody declares are the static sentinels:
The message bus — the RICH tier's transport. Dispatch messages through
guards to typed subscribers. Transport-agnostic: feed it from WS, HTTP, a
local DB, or a local optimistic dispatch(..., optimistic: true).
Decoupled from canon and from Flutter; a StoreMemory subscribes to it,
and a riverpod notifier can subscribe via on too — neither owns the other.
THE spec mark: one annotation for every canon spec enum — ids, entities,
regents, screens, link trees. The row MIXIN is the source of truth
(IdNode, entity rows, RegentNode, ScreenNode, LinkNode); the
generator dispatches on it, so the mark only says "generate here".
The main screens enum is the one that declares the NavGraph; a link
domain is declared IN the tree (Domain('https://…')), not here.
A strict, bidirectional codec for a single string token. decode returns
null when the token is not valid for this codec — that null is what lets the
caller fall through (URL match fallthrough, or best-effort restore). encode
is the exact inverse for any value the codec can produce, so a value
round-trips to the same canonical token.
A composite codec (see Codec.composite); const-constructible. Components
are individual fields — a const constructor can't build a list — so the
signature itself caps the arity at 16. The value is a positional RECORD of
the components' decoded values: a record's runtime type comes from its
fields' runtime types, so a 2-part composite of string codecs decodes to a
value that IS a (String, String) — typed casts and structural equality
(repeat-collapse, prefix reuse) work as if it were declared that shape.
See IdNode.compose. Component nodes are individual fields — a const
constructor can't build a list — so the signature itself caps the arity at
16 (à la Object.hash); generators read the n* fields directly.
An ordered sequence of codecs occupying ONE path segment: exactly one
VARIABLE member (carries the value) framed by LiteralCodec members
(fixed text). Invertible because the literals anchor the boundaries —
decode strips the leading/trailing literals, delegates the remainder to the
variable. A second variable has no recoverable boundary, so it is rejected.
The cursor intervals an authority has covered — a plain folded VALUE,
pure and axis-blind (the consumer decides what C means: a DateTime, an
int, a name). Absence WITHIN a covered interval is a fact ("gone");
absence outside is silence.
A domain root — a URL prefix (scheme + optional host). The first domain in
the tree is the one used for output. Inlined (not an enum): root-only by
type. Also placeable bare in a NavGraph's trunk set — the screens tree's
canonical output domain, declared IN the grammar.
A domain root: a URL prefix (scheme + optional host + optional base path) and
its subtree. The prefix is parsed once into components the matcher compares
structurally (not by string prefix), so example.com never matches
example.com.evil.com.
Marks the hand-written enum that is an app's ENTITY SPACE: each row binds an
entity TYPE to its id-NODE. A static final graph = EntityGraph({...})
declares OWNERSHIP as a tree — review({comment}) means a comment belongs
to exactly one review (its state lives inside it; removing the review
removes its comments by construction). The graph carries ownership ONLY:
an unlisted row is a root, and an app whose entities are all flat omits
the graph entirely. The same child kind may appear under several parents
(image({reactor}), moment({reactor})): instances still have exactly one
owner, of either kind. Roots are the aggregate boundaries — stores attach
to roots only.
The declared ownership tree. Structure is read both at build time (the
generator derives store legality, nested-map machinery, and path types from
it) and at runtime (ownersOf/childrenOf — the entity-scope resolution
surface).
The fragment declaration surface: ONE fragment per screen, ONE scheme —
calling it declares the PAIRS scheme (#k=v&k2=v2, exactly the query
machinery); path declares the PATH scheme (#<seg>/<seg>). The two are
mutually exclusive by assignment: whichever is declared last would
overwrite, so declaring both asserts.
One position of a PATH-scheme fragment: a codec (open domain = a slug,
a Codec.literal singleton = a fixed segment) and the positions that may
follow it. Compose with /: .product / {thumb, gallery / {imageIndex}}.
A PURE judge standing at its row of the queue: every traversing message
of the M family is submitted to judge, whose returned set IS its
verdict — one verb for every shape of judgment:
One judgment's full story, emitted AFTER the verdict ran: the submitted
message and what the guard launched for it, atomically — for a Veto,
an empty verdict IS the block. Observation only (ledger.at(spec));
the flow itself never depends on it.
The live handle at a guard's row (ledger.at(const CachedGate())):
the judged input and the verdict, observable — a judge holds no state,
so its memory is pure story. Plural members are streams, all derived
from the atomic events:
A deictic navigation claim: "this identity, from where the claiming
widget stands." Minted by the binding layer (canon_flutter's
IdScope.of<K>(context)); the generator hangs the typed forward verbs on
it as extensions, one set per id NODE — ids resolve ambiently (the
scope's own id plus ids locked on the chain), so item code never passes
the id it is standing on. Each verb anchors at screen on the live chain
and takes the edge with edgeRequired enforcement: navigating somewhere
the placement can't reach is a programmer error the fold throws on,
never a silent kick-start fold.
The contract an @ids enum wears: every row carries a codec. The node IS a
Codec (it delegates to its inner one), so a screen can bind it straight into
a Codec? id field (id: .user) and a store can key by it — the SAME node
across both grammar trees. Generators read node.codec to recover the value
type (the node itself erases to Codec<Object?>).
Marks the HAND-WRITTEN enum that is an app's id-space: each row is an identity
carrying its Codec — how its key serialises in a URL. Nothing is generated;
the enum IS the holder. Other grammar trees (canon's @screens, ledger's
@stores) reference these rows by dot-shorthand and read row.codec to
encode/decode and to validate a screen id or a store key against them.
The chained form of .inherit(...): carries its placement node DIRECTLY
(never stashed), so chaining is explicit in the type system — one
expression, one node, no orphan to mis-claim. Usable as:
One LAUNCH — the element of a guard's verdict. A guard at index x may
target exactly TWO indices, the only two that preserve the system's
theorem (no row ever sees a message that skipped a guard above it):
A link-grammar branch placed in the tree (a trunk, or nested in a .call).
It is GENERATOR-READ ONLY: the runtime nav engine ignores it (links don't
seed the stack). Carries the link DSL node the generator walks to emit Link.
A successful parse. path is the ordered dynamic slot values (statics are
structural, omitted); query/fragment are decoded params by key name.
Unmodeled query keys are ignored, never captured.
Authoring DSL: a runtime-built tree the matcher walks and the generator
reads (via AST). Positional by construction — the same literal can sit at
many positions with different types.
A FACT. The journal stores it, replay
re-delivers it — so it must never mutate after construction. The
annotation makes the analyzer enforce final fields on every subclass.
One entry of a live navigation stack — a screen and its id. T is the screen
representation: the raw Enum internally, or the public Screen<Object?>
wrapper for the consumer-facing stack.
What the FLUTTER layer plugs into the pure engine (canon_flutter's
delegate implements it): rebuild triggers and frame scheduling. Every hook
is a no-op until a host attaches — headless (server, test) graphs simply
never attach one.
An immutable snapshot of one committed navigation, delivered to
NavGraph.navigations. Safe to hold past the commit: it captures the
transition rather than reading live state, so async (stream) delivery never
sees a stale position. from and to are the full active-scope stacks
(bottom-to-top, each entry a (screen, id)); the generated layer retypes
them into the public ScreenEntry stack.
A navigation VERB as a FACT — a Msg, so a ledger can journal, judge,
and replay navigation like any other truth; navReduce folds it.
Placement legality still runs in the pure resolvers; an illegal op
throws (a programmer error, not a state).
The navigation STATE as a pure value: per-scope stacks, the active
trunk, and the batch's commit mode. Verbs are NavOp facts folded by
navReduce; the graph holds a pending state per microtask batch and
commits the diff once. Immutable — every fold returns a new value, so
the fold replays.
The navigation stack as a REGENT — hold it as the LAST row of a
regents enum, route the graph's verbs through the ledger
(graph.routeOps(dispatch)), and mirror folds back with
graph.applyState. Gates above it judge outbound navigation like any
fact; replay carries the session's navigation. Inert (null) until a
SeedOp arrives — the graph seeds on wiring and on every inbound
restore. Reads the one live grammar via NavGraph.boundSpec: immutable
config, one graph per app.
Mutual exclusion: exactly one member present (→ sealed union), or none. Two
or more present is a parse failure. mandatory true forbids the none case —
the link only matches when exactly one member is present (link branches only).
A query (or fragment) schema: a list of terms. Unmodeled keys are always
ignored — what isn't in the schema isn't data. Capture arbitrary query by
modeling an explicit raw key.
One path position. Statics are tried before the (at most one) slot.
A node resolves (is an endpoint) when endpoint is set, when it has a query
or fragment schema (params imply resolution), or when it is a leaf.
A READ RESOLVER for a merge edge (user.merge(viewer, const ViewerSupportsUser()) in the entities graph): the SOURCE store's state
answers the TARGET surface's per-key reads at the source's OWN identity —
S extends Identifiable<K> IS the claim; there is no key method anywhere.
Marks a method (or class) as PURE — its body may read only its parameters,
locals, pattern-bound variables, and compile-time constants, and may call
only other pure functions. No ambient or mutable state, no clock, no
randomness, no I/O. Purity is what makes a fold deterministic and therefore
replay-able: replay(a) == replay(b) is a law only because the folds
between cannot reach outside their arguments.
The app as a VALUE: an ordered set of regents plus merge edges. A graph
IS a regent, and a regent is a one-row graph — Ledger.root takes a
single Regent, so the smallest ledger is Ledger.root(const NavUnit())
and the largest is a const tree:
A REGENT — a row of the ledger — anything that occupies a row of the regents
enum: stores, units, guards, vetoes. Row order is traversal order: a
message walks the rows top to bottom.
A direct stack seed (trunk..target chain of (screen, id)) for the seedChain:
constructor arg — used by engine/restore code and tests to start at a specific
stack. Consumers instead pass root: (the boot widget) and let the resolver
drive the first navigation; see BootScreen.
Marks the library-private spec enum the nav generator reads.
Rows: identifier = screen name, first positional = const widget,
optional second positional = id Type (named types only).
The PURE, const registry descriptor: how a message folds into an entry's
state. No mutable state, no ref, const — so it can sit in a spec. The live
store (StoreMemory) is created separately and wired to a Bus.
One fold's full story, emitted by a store AFTER the reduce ran: the cause
and its consequence, atomically — an effect filtering these can never race
the fold. Filters recover every narrower feed: structural (the list
shell), changed.contains(id) (per key), a before/after delta (state
TRANSITIONS), a msg-type check (post-fold message observation).
The live store for a Store: the folded collection driven off a Bus,
plus read-time merge edges and the change/event feeds. It holds NO other
state — optimism, in-flight status, freshness, and settlement all live in
consumer ROWS (docks, in-flight units, coverage), where they replay.
The runtime link spec the matcher walks. The authoring DSL (Domain/Seg/slot)
builds this; the generator reads the SAME source via AST to emit the typed
Link surface. Plain data — no Flutter, no nav.
A node in a query/fragment schema: a single key, or a typed combinator.
Authoring marker: what a grammar set literal may hold — a screen (via
ScreenNodeBase), a again back-edge, or a graft of another
family. <S> keeps a native literal typed to one family; graft is the one
explicit cross-family bridge.
The flattened branches of an a | b | c union. Decode tries them left-to-
right (first non-null wins); encode uses the left/canonical branch. The
nav/link layer reads branches to recover the ordered union the spec wrote.
The UNIT sibling of Store: one value, cardinality one — for entities
whose identity is the session (the wire sends their facts KEYLESS: a
viewer profile, a requests+unseen state). Same purity contract.
The UNIT form of Projection — a unit-target merge edge
(viewer.from(viewerPending, const ApplyPending()) in the regents
merges set): the SOURCE unit's state answers the TARGET unit's read.
Keyless — a unit has cardinality one, so the edge always applies;
resolve no-ops itself when the source carries nothing. Read-time only:
the fold and guards' read never see it.
One view-state condition term in a selector (.category('books'), .not.byFav).
The generated per-screen …Cond types implement this; Screen.on/context.on
gate on test(liveValue), and key is the aspect a reactive read subscribes to.
The host-facing face of a screen: its widget — NULLABLE, and never forced:
a row with no widget is a LINK-ONLY row (grammar/URL presence, nothing to
render; nav verbs to it are the generator's concern to omit). The engine
stays Flutter-free — W is abstract here; the Flutter alias binds it to
Widget. Lets the host read a widget off an erased (Enum) screen; the
name comes from Enum directly.
The synthetic boot placement. When a graph is built with no seedChain, the
stack is seeded as [(BootScreen.root, null)] — so the always-non-empty
invariant holds — and current/Screen.at report it until the first commit,
which the engine auto-replaces (the boot entry leaves no history). Never part
of a consumer tree; the generated Screen.at maps it to Initial.
How a committed navigation maps to history: push adds a new entry,
replace overwrites the current one (no back-target). Default is push;
the generated Screen.replace flips a batch to replace, and the engine
forces replace for the first commit out of the boot state. The web Router
delegate reads it (pushState/replaceState); the bare stack engine, which
has no history, ignores it.
The shape of a committed navigation, derived from the active scope's stack
delta: forward grew the stack, backward shrank it, roundTrip did both
(a popTo(...).go(...) chain), jump switched scope/trunk (a kick-start).
The contract an @entities enum wears: every row carries the entity type
and the id-node (key) its instances are identified by. call({children})
declares the row's OWNED children in the graph.
Entities are VALUES: folds produce new instances; a mutated entity
corrupts before/after events and optimistic refolds — enforced here for
every class that wears the mixin.
A URL-addressable grammar node with NO presentation and NO fields — the
enum NAME is the whole declaration. The links-only tier of the screens
grammar: a server or API surface authors
enum _Links with LinkNode<_Links> and builds the same tree
(user({product})) with the same link/view-state vocabulary, and nothing
to render. A ScreenNodeBase row is conceptually a LinkNode plus a
widget; the hierarchy unification (placement-as-link, matcher/generator
support) is a coming pass — today this is the authoring surface and the
validated tree (LinkGraph).
Adds (#name) to a codec, overriding a generated field name at the use site
(slot(.uuid(#productId))). The returned codec drops the mixin, so a name can be
applied at most once. Battery codecs mix this in; custom codecs may.
+ is ORDERED concatenation within one segment — the string forms of the
members joined in order (Ids.image + Codec.literal('_thumb') →
{imageId}_thumb). Non-commutative on purpose (prefix ≠ suffix), unlike the
commutative |. Exactly one member carries a value; literals frame it.
a | b — a union: either token addresses the same T. Decode tries the
branches left-to-right (strict codecs return null on a non-match, so the
first that accepts the token wins); encode delegates to the left branch (the
canonical form). The nav/link generator reads the branches structurally; this
runtime form lets a spec written with | compile and round-trip. An
extension, not an interface member, so existing implements Codec classes
are unaffected.
The codec algebra over query/fragment terms: a & b is co-occurrence (all
present together → record), a | b is mutual exclusion (exactly one → sealed
union). Dart binds & tighter than |, so a & b | c reads as (a & b) | c
— use parentheses to group the other way. An extension, so the QueryTerm
interface (and its implementers) stay untouched.
Pure sugar over a UNIT's post-fold event stream — the recurring effect
idioms as verbs. Extensions on the STREAM (not the memory), so they
compose after any filter and over replayed feeds alike.
Decode a raw fragment string against roots. Returns the decoded
positions in order, or null if any position rejects (strict). Tolerates
(strips) a :~: directive tail — that's user-agent territory.
Encode decoded positions back to the raw fragment string, walking the
same tree. Returns null if the values don't fit the tree (a write of an
illegal path). Percent-encodes embedded '/' — the splitter owns it.
Mounts a child from ANOTHER screen family into this family's tree — the one
explicit cross-family edge. Pass a screen, a built Sub.x({...}), or a
reusable static final subtree. Inferred P (the parent family) comes from
the target set's element type; C from the grafted node.
Builds a root PathNode from the .links branches gathered off the
@screens graph (each a screen-rooted SegBuilder) — the runtime link tree
the matcher walks. Domain-agnostic: the caller wraps it in a DomainNode
built from the URL's own origin at parse time.
The PURE navigation fold — the verb semantics as a function of
(state, op): idempotent-tap no-ops, boot exit, trunk park/seed, the
tap>edge>canonical ladder, targeted pops, keep maintenance. Throws
are programmer errors (stale handles, unprovable pops), not states.
warn hosts the canonical-fallback diagnostic.
The state the WHOLE ledger folds from order — its replay. Builds a
pure ledger from root (a Regency or any single regent), folds the
messages synchronously, and returns every regent's state keyed by SPEC
INSTANCE — z[const Todos()]. Deterministic: the folds are pure, so the
same messages always yield the same snapshot — replay is the operation
purity buys you.
REQUIRED co-occurrence: the link only matches when all members are
present — a URL missing them is rejected, not resolved with a null group.
Link branches (.link/slot) only; rejected on screen view-state, where a
query is decoration, not part of the route's identity.
REQUIRED mutual exclusion: the link only matches when exactly one member is
present — a URL with none is rejected. Link branches only (see requireAllOf).
A path slot: the next segment is one value of codec. A union is a single
codec built with | — slot(.uuid(#userId) | .username) — whose branches are
expanded here, tried in order (first match wins), generating a sealed type at
the use-site. An affix segment ({imageId}_thumb) is a concat codec —
slot(Ids.image + Codec.literal('_thumb')).
Flattens a query/fragment term set into a key→codec schema (codec null = a
flag). View-state placements (screen(...).query({...})) use this so the
engine can encode/decode the stored values against the URL.
A guard's view of the world: this ledger's own state, looked up by
regent IDENTITY — read(const BrowseDeck()), read(const AuthMachine()).
Const canonicalization makes the constructor expression the regent's
canonical NAME: const X() written in a judge IS the instance the row
holds (same class, different args = a different regent; two rows may not
hold identical instances — enforced at registration). Bound to the ledger
the guard stands in, so a replayed ledger reads itself. Throws when no
row holds the instance (wrong args, or a missing const).