ActionDef
Interface: ActionDef
Defined in: src/tree/types.ts:43
An action on a node. Only does is required — details may materialize at mount.
Extended by
Properties
binding?
optionalbinding?:Binding
Defined in: src/tree/types.ts:47
How to reach it on screen (optional — L0b actuation; handlers don't need it).
blockedBecause?
optionalblockedBecause?:BlockedBecause| (() =>BlockedBecause|undefined)
Defined in: src/tree/types.ts:97
YOUR OWN REASON THIS CONTROL IS OFF, and who clears it — served only while the control is off, and only ever as data.
enabledWhen proves a control is greyed and hands the reader the conjuncts
that failed; that is EVIDENCE, and it is derived. This is the other half:
the sentence your component already knows ("waiting for the upload to
finish") and the one fact no evidence carries — WHO can clear it. See
BlockedBecause.
next: {
does: 'Continue to review',
blockedBecause: { says: 'Waiting for the receipt to finish uploading', clearedBy: 'app' },
}The FUNCTION form is for a reason that changes while the page is open. It
is a READER, declared like holds: it runs at the moment a row is
assembled, never cached, and returning undefined says nothing at all.
Keep it a read — it runs on a hot path, and a reader that throws costs the
row its sentence and nothing else.
blockedBecause: () => (upload.pending
? { says: `Uploading ${upload.name}…`, clearedBy: 'app' }
: undefined),It never disables anything: declaring it on a control nothing has switched
off changes not one byte of what is served. Say WHY here; say WHETHER with
enabledWhen, enabled:, setEnabled, or a live store row.
concurrency?
optionalconcurrency?:ConcurrencyPolicy
Defined in: src/tree/types.ts:237
MAY A SECOND FIRE OVERLAP AN UNRESOLVED FIRST? Default 'parallel' — what
every release before this one did.
'pay-invoice': {
does: 'Pay the invoice', confirm: true, writes: ['invoice.paid'],
concurrency: { mode: 'single-flight', scope: 'payload' },
}Under 'single-flight' a fire made while a prior occurrence is still
unresolved is refused PRIOR_FIRE_PENDING, carrying that fire's id and the
doors that can settle it. It clears on settlement and on nothing else — no
timeout, no second look, and not the caller reporting it done. See
ConcurrencyPolicy.
confirm?
optionalconfirm?:boolean
Defined in: src/tree/types.ts:117
Requires explicit confirmation (the high-effect gate).
does
does:
string
Defined in: src/tree/types.ts:45
AUTHORED intent, one string two readers (consumer label = agent tool description).
enabledWhen?
optionalenabledWhen?:WhereFilter<Record<string,unknown>>
Defined in: src/tree/types.ts:63
Is this control currently CLICKABLE? Declarative disabledness — a different
question from when, which decides whether the control is here at all. A
failed when HIDES the action; a false enabledWhen SERVES it as a greyed
button (enabled: false on the edge) and refuses a fire as TOOL_DISABLED.
Declare it from the same expression that renders <button disabled={…}> and
an agent stops discovering the answer by clicking. Keys it cannot evaluate
never disable anything — the library does not guess a control greyed out.
NOT composed with ancestor whens: this is the control's own state, not
its position in the tree.
freshness?
optionalfreshness?:FreshnessPolicy
Defined in: src/tree/types.ts:219
WHAT THIS CONTROL DOES WHEN SOMETHING IT WAS OFFERED UNDER HAS SINCE MOVED
— declared per axis, and 'disclose' (today's behaviour) wherever you say
nothing.
'settle-claim': {
does: 'Settle the claim',
reads: ['claim.total'], writes: ['purse.left'],
freshness: { readChanges: 'require-ack', writeChanges: 'refuse' },
}It is the enforceable sibling of the staleReads / staleWrites stamps,
which say the same thing and refuse nothing. Declaring it overrides the
session default AXIS BY AXIS, and an enforcing axis makes one new demand of
the caller: cite the offer you planned against
(FireOptions.offerId). See FreshnessPolicy.
goTo?
optionalgoTo?:string
Defined in: src/tree/types.ts:115
Page this action claims to navigate to (a top-level page id).
humanDecides?
optionalhumanDecides?:HumanDecides
Defined in: src/tree/types.ts:161
THIS CHOICE IS THE PERSON'S TO MAKE — not a gate on the agent acting, but a statement that the decision itself belongs to a human.
confirm asks whether the agent may ACT after a human's yes. This says the
agent's correct move is to PRESENT options and stop: the human answers
through this control in the app, and the flow moves because the world moved.
'choose-shipping-speed': {
does: 'Choose a shipping speed',
writes: ['checkout.shipping'],
humanDecides: {
about: 'which shipping speed',
doneWhen: { 'checkout.shipping': { ne: '' } },
},
}It is a fact about the CONTROL, declared once and inherited by every journey that names it — a per-journey split would let two lists disagree about one control's owner. It is DISCLOSURE: nothing is refused, and no refusal word exists for it. See HumanDecides.
input?
optionalinput?:unknown
Defined in: src/tree/types.ts:128
Payload contract: Zod, JSON Schema, any .safeParse/.parse validator —
or the literal 'none', meaning "this control takes NO input". A caller
that sends one anyway is refused with the shape it sent, and a blank
payload is erased before it can reach the handler and override the app's
own defaults.
OMITTING input says something different: the library does not know the
shape, so it advertises nothing rather than inventing an empty contract.
observability?
optionalobservability?:Observability
Defined in: src/tree/types.ts:199
HOW WOULD ANYONE SEE THAT THIS HAPPENED — 'state-delta',
'postcondition', 'navigation', 'external' or 'unobservable'.
Declared, never inferred, and it refuses nothing on its own. A session
created with effectPolicy: { highEffectRequiresVerify: true } reads it and
refuses a high-effect action whose effect nobody could check — where
'state-delta' deliberately does NOT count, because key presence is not
value correctness. See Observability.
Two coherence rules are refused HERE, at authoring, whether or not any
session enforces anything: 'postcondition' needs a verify, and
'navigation' needs a goTo.
principalPolicy?
optionalprincipalPolicy?:PrincipalPolicy
Defined in: src/tree/types.ts:184
WHO MAY PERFORM THIS, WHOSE CHOICE IT IS, AND WHETHER A RECORDED YES IS NEEDED — three separate facts, three fields, never one word.
humanDecides above is disclosure and stays disclosure. This is its
enforceable neighbour, and it enforces NOTHING until the session is created
with enforcePrincipalPolicy: true — declaring it changes not one byte
otherwise.
'transfer-funds': {
does: 'Transfer the balance',
confirm: true,
principalPolicy: { mayInvoke: ['human'], requiresHumanApproval: true },
}Note the vocabulary: a policy names an ACTOR ('human'), while a record
files an act under a principal ('user'). Writing mayInvoke: ['user'] is
refused at this door with the correction, rather than silently locking a
person out of their own control. See PrincipalPolicy.
reads?
optionalreads?:string[]
Defined in: src/tree/types.ts:113
State keys this action's OUTCOME DEPENDS ON — the read side of writes,
and the one an app is asked for so a reader can be told that something it
depends on moved.
settle: { does: 'Settle the claim', writes: ['purse.left'], reads: ['claim.total'] },Not when: that decides whether the control is HERE. This says what the
outcome is computed FROM. Declared, never inferred — see Effect.reads
for the law and for what the serving layer does with it.
role?
optionalrole?:CanonicalRole
Defined in: src/tree/types.ts:238
verify?
optionalverify?:VerifyContract
Defined in: src/tree/types.ts:136
The app's OWN check that firing this really did something — evaluated once,
at settlement, and the only thing that can turn a handler that merely RAN
into an honest refusal. Either a filter over projected state
({ 'wizard.recipe': { ne: '' } }) or a synchronous predicate whose closure
may read whatever the app can see, the DOM included.
when?
optionalwhen?:WhereFilter<Record<string,unknown>>
Defined in: src/tree/types.ts:49
Availability guard over projected state (AND-composed with every ancestor when).
writes?
optionalwrites?:string[]
Defined in: src/tree/types.ts:99
State keys this action claims to change.