@nodegx/export 0.0.0-stage → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +59 -2
  2. package/dist/analyze/appState.d.ts +296 -0
  3. package/dist/analyze/plan.d.ts +3757 -0
  4. package/dist/analyze/querySettings.d.ts +75 -0
  5. package/dist/analyze/reach.d.ts +41 -0
  6. package/dist/analyze/scriptUses.d.ts +29 -0
  7. package/dist/catalog.d.ts +129 -0
  8. package/dist/cli/args.d.ts +90 -0
  9. package/dist/cli/exitCodes.d.ts +125 -0
  10. package/dist/cli/run.d.ts +30 -0
  11. package/dist/cli.inputs.json +174 -0
  12. package/dist/cli.mjs +53746 -0
  13. package/dist/cli.mjs.map +7 -0
  14. package/dist/emit/animateLib.d.ts +43 -0
  15. package/dist/emit/component.d.ts +144 -0
  16. package/dist/emit/dateLib.d.ts +45 -0
  17. package/dist/emit/emitApp.d.ts +38 -0
  18. package/dist/emit/idLib.d.ts +68 -0
  19. package/dist/emit/kits.d.ts +85 -0
  20. package/dist/emit/mediaLib.d.ts +75 -0
  21. package/dist/emit/naming.d.ts +76 -0
  22. package/dist/emit/parseLib.d.ts +106 -0
  23. package/dist/emit/preflight.d.ts +174 -0
  24. package/dist/emit/readme.d.ts +51 -0
  25. package/dist/emit/report.d.ts +309 -0
  26. package/dist/emit/scaffold.d.ts +67 -0
  27. package/dist/emit/statesLib.d.ts +52 -0
  28. package/dist/emit/style.d.ts +165 -0
  29. package/dist/emit/utilLib.d.ts +78 -0
  30. package/dist/errorMessage.d.ts +28 -0
  31. package/dist/index.cjs +52848 -0
  32. package/dist/index.cjs.map +7 -0
  33. package/dist/index.d.ts +37 -0
  34. package/dist/index.mjs +52769 -0
  35. package/dist/index.mjs.map +7 -0
  36. package/dist/ir/types.d.ts +567 -0
  37. package/dist/ledger.cjs +971 -0
  38. package/dist/ledger.cjs.map +7 -0
  39. package/dist/ledger.d.ts +75 -0
  40. package/dist/ledger.mjs +948 -0
  41. package/dist/ledger.mjs.map +7 -0
  42. package/dist/node-catalog.json +65333 -0
  43. package/dist/parse/kitSource.d.ts +135 -0
  44. package/dist/parse/parseModules.d.ts +42 -0
  45. package/dist/parse/parseProject.d.ts +13 -0
  46. package/dist/scriptFiles.d.ts +30 -0
  47. package/dist/structurePorts.d.ts +70 -0
  48. package/dist/write/writeExport.d.ts +67 -0
  49. package/package.json +53 -4
@@ -0,0 +1,3757 @@
1
+ /**
2
+ * Component analysis — EXP-002 step 4's decision stage, extended by step 5. Parsing recorded
3
+ * what the project says; this walks each component's visual tree and decides what the generator
4
+ * will do about it: which nodes render (and as what), which collapse, which become stubs, and
5
+ * which defer to EXP-003. Dispositions are analysis output about the graph, never facts of it
6
+ * (IR design).
7
+ *
8
+ * Step 5 adds the app-state constructs (EXP-002-STEP5-TARGET-OUTPUT.md): Variables become the
9
+ * stores module, Send/Receive Event pairs become channels, and signal wires compile into
10
+ * handler *actions* — a navigate, a store `.set`, a channel `.emit` — attached to whichever
11
+ * handler owns the triggering signal (a rendered element's DOM event, or a receiver's
12
+ * `useSignal`). Value wires resolve by sink context: the same `Variable2.value` read is a
13
+ * `useValue` hook in rendered content and a `.get()` snapshot inside a handler.
14
+ *
15
+ * Step 6 adds statically-knowable logic (EXP-002-LOGIC-TARGET-OUTPUT.md): String Format and
16
+ * Condition resolve into *expression trees* over the same source vocabulary, rendered inline
17
+ * wherever their output lands — the `derived()` row of EXP-001's table compiled away, because
18
+ * the hooks a component already earns are the reactivity a Derived would provide. A Condition
19
+ * in a handler chain (`trigger → eval`, `ontrue → sink`) becomes a branch action — an `if`
20
+ * statement in the trigger's handler — and only when the author unticked Run On Value Change,
21
+ * because Evaluate is additive and an onClick cannot carry on-change firing.
22
+ *
23
+ * Everything here is pure decision-making over the IR; no text is generated. The emit layer
24
+ * (emit/component.ts) turns a ComponentPlan into TSX/CSS.
25
+ */
26
+ import { CatalogIndex } from '../catalog';
27
+ import { ComponentIR, Disposition, ExportIR, KitNodeIR, ModuleIR, RefusedNode } from '../ir/types';
28
+ import { Reachability } from './reach';
29
+ import { ScaffoldPage } from '../emit/scaffold';
30
+ import { DateHelper } from '../emit/dateLib';
31
+ import { UtilHelper } from '../emit/utilLib';
32
+ import { IdHelper } from '../emit/idLib';
33
+ import { StyleRole } from '../emit/style';
34
+ import { AnimateEaseName } from '../emit/animateLib';
35
+ import { StatesValueType } from '../emit/statesLib';
36
+ import { ScriptUsePlan } from './scriptUses';
37
+ import { QueryParamsPlan } from './querySettings';
38
+ import { ChannelPlan, CollectionPlan, StorePlan, VariablePlan } from './appState';
39
+ export type { ChannelPlan, CollectionKeyPlan, CollectionPlan, StoreKeyPlan, StorePlan, VariablePlan } from './appState';
40
+ /**
41
+ * How a node participates in the render, or null for pure logic nodes.
42
+ *
43
+ * `'custom'` is EXP-010's: a visual node from a `noodl_modules` kit. It is its own role rather
44
+ * than a `StyleRole` because a kit node has no Noodl style ports at all — every parameter on one
45
+ * is a port the kit itself declared (measured across every project on this machine: not one custom
46
+ * node carries a `width`, a `margin` or a `backgroundColor`), so the style tables have nothing to
47
+ * say about it and `computeNodeStyle` must not be asked.
48
+ */
49
+ export type RenderRole = StyleRole | 'instance' | 'repeater' | 'custom' | 'slot';
50
+ /**
51
+ * EXP-011 §51. The marker the runtime reads structurally rather than as a node: `nodescope.ts:217`
52
+ * creates nothing for it and, when it has a parent, makes that parent the instance's child root
53
+ * (`componentinstance.ts` `setChildRoot`). The placed children of an instance are inserted into
54
+ * that parent at the marker's index, in order, contiguously — React's `children`, rendered where
55
+ * the marker sits.
56
+ */
57
+ export declare const CHILD_SLOT_TYPE = "Component Children";
58
+ /** Parent of every node, read from both `parent` fields and `children` arrays (two corpus files carry only the latter). */
59
+ export declare function parentMapOf(component: ComponentIR): Map<string, string>;
60
+ /**
61
+ * Which `Component Children` receives an instance's children, by the runtime's own two rules:
62
+ * `createNodeFromModel` runs per node in file order and every marker with a parent calls
63
+ * `setChildRoot(parent)`, so the LAST marker's parent wins; then `getChildRootIndex` inserts at the
64
+ * FIRST marker among that parent's children. A parentless marker sets nothing. Null when no marker
65
+ * has a parent — an instance's children are then never drawn.
66
+ */
67
+ export declare function chooseChildSlot(component: ComponentIR): {
68
+ nodeId: string;
69
+ parentId: string;
70
+ } | null;
71
+ /** `Hash` (EXP-011 §59). A SHA-2 digest of a UTF-8 string through `crypto.subtle.digest` — asynchronous. */
72
+ export declare const HASH_TYPE = "net.noodl.Hash";
73
+ /** `Random Bytes` (EXP-011 §59). `crypto.getRandomValues` rendered as text — synchronous, never `Math.random()`. */
74
+ export declare const RANDOM_BYTES_TYPE = "net.noodl.RandomBytes";
75
+ /**
76
+ * `Pattern Extractor` (EXP-011 §76). A regular expression over a text — `Hash`'s shape (an action port, an
77
+ * outcome pair, a raise on failure) with SEVEN value outputs where Hash has one, and a third signal pair
78
+ * (`Found` / `Not Found`) the runtime fires BEFORE `Done` off the same Extract: two RESULTS, one outcome.
79
+ */
80
+ export declare const PATTERN_EXTRACTOR_TYPE = "net.noodl.PatternExtractor";
81
+ /** The crypto family's members — `node` on {@link CRYPTO_NODES}, on `crypto-out` and on `crypto-call`. */
82
+ export type CryptoNodeKind = 'hash' | 'random-bytes' | 'pattern';
83
+ /**
84
+ * `Pattern Extractor`'s value outputs (EXP-011 §76), each a row of its own when read outside the chain —
85
+ * and, unlike the two crypto rows, each SEEDED: `initialize` writes every internal (`match: null`, `[]`,
86
+ * `{}`, `''`) and the getters answer them, so nothing here is ever undefined. `tsType` is what the getter
87
+ * publishes; `boot` / `bootCode` is what `initialize` leaves for the getter to read.
88
+ */
89
+ export declare const PATTERN_VALUE_OUTPUTS: Record<string, {
90
+ label: string;
91
+ tsType: string;
92
+ boot: string | number;
93
+ bootCode?: string;
94
+ }>;
95
+ /**
96
+ * The two crypto verbs (EXP-011 §59), which are `UUID`'s shape (§37) and not a request's: read the
97
+ * inputs the setters stored, answer a value or a failure in the one call (`_run` / `_generate`),
98
+ * write the value row or leave it as it was, clear or write the Error, report the outcome. Two
99
+ * things differ from `UUID` and the export keeps both: **the value row boots empty** (neither node
100
+ * has an `initialize` that seeds it — `Digest` / `Value` read undefined until the first Do) and
101
+ * **a failure raises on the error channel** (`reportOutcome(…, 'failure', { code })` raises
102
+ * `hash/failed` / `random-bytes/failed` before the Failure pulse — `node.ts`). `Hash` awaits.
103
+ *
104
+ * `inputs` is the port order the emitted verb takes its arguments in; `numeric` names the one
105
+ * port whose setter is `Number(value)`, applied at the delivery site (a wire) and not in the lib.
106
+ */
107
+ export declare const CRYPTO_NODES: Record<string, {
108
+ node: CryptoNodeKind;
109
+ /** The value output port — the ONE for the two verbs, the first of {@link PATTERN_VALUE_OUTPUTS} for the extractor. */
110
+ output: string;
111
+ /**
112
+ * EXP-011 §76. Every value output where a node has more than one, each typed and seeded; a read of any of
113
+ * them is a `crypto-out` carrying the port as `field`. Absent for the two verbs (their one output is `output`).
114
+ */
115
+ values?: Record<string, {
116
+ label: string;
117
+ tsType: string;
118
+ boot: string | number;
119
+ bootCode?: string;
120
+ }>;
121
+ /**
122
+ * EXP-011 §76. The RESULT pulses the node fires before `Done` — `Found` when the pattern matched at least
123
+ * once, `Not Found` when it ran and matched nothing (ERG-001 §4: a result, not an `Unchanged`). Two more
124
+ * chains compiled off one Extract, printed inside the Done arm ahead of the Done chain.
125
+ */
126
+ results?: readonly ['found', 'notFound'];
127
+ /** The action port, and what the editor shows it as. */
128
+ trigger: string;
129
+ triggerLabel: string;
130
+ /** The value output's display name, for the sentence about a read nothing ever writes. */
131
+ outputLabel: string;
132
+ /** The lib verb, the raised code, whether the call is awaited. */
133
+ fn: 'tryHash' | 'tryRandomBytes' | 'tryExtract';
134
+ code: string;
135
+ async: boolean;
136
+ inputs: readonly string[];
137
+ numeric?: string;
138
+ /** What the node is called in a deferral sentence. */
139
+ label: string;
140
+ /** The sentence for a read while nothing fires the trigger. */
141
+ neverFired: string;
142
+ }>;
143
+ /** `Screen Resolution` (EXP-011 §59). The type id IS the display name. Three numbers off one `resize` listener. */
144
+ export declare const SCREEN_RESOLUTION_TYPE = "Screen Resolution";
145
+ export declare const SCREEN_RESOLUTION_OUTPUTS: readonly ["width", "height", "aspectRatio"];
146
+ /**
147
+ * `Drag` (EXP-011 §63). The type id IS the display name. A visual node that renders no element of its own — `Drag.tsx`
148
+ * wraps its FIRST child in `react-draggable` — so the export drags a wrapper `<div>` bound to a `useDrag` handle.
149
+ */
150
+ export declare const DRAG_TYPE = "Drag";
151
+ /** The option ports in the order the hook prints them: the port, the `useDrag` option key, and the panel's label. */
152
+ export declare const DRAG_OPTION_PORTS: ReadonlyArray<{
153
+ port: string;
154
+ key: string;
155
+ label: string;
156
+ }>;
157
+ /** The four value outputs → the handle's live getters. */
158
+ export declare const DRAG_VALUE_OUTPUTS: Readonly<Record<string, 'x' | 'y' | 'deltaX' | 'deltaY'>>;
159
+ /** The three pulses → the hook's listeners, and the outcome trio the two snaps share (`outcomeOutputs`). */
160
+ export declare const DRAG_PULSE_OUTPUTS: Readonly<Record<string, {
161
+ listener: 'onStart' | 'onMove' | 'onEnd' | 'done' | 'failure' | 'completed';
162
+ label: string;
163
+ }>>;
164
+ /** The two action ports — `Do|Snap To Position X` and `Do|Snap To Position Y` — and the axis each snaps. */
165
+ export declare const DRAG_SNAP_TRIGGERS: Readonly<Record<string, 'x' | 'y'>>;
166
+ declare const TIMER_TRIGGERS: readonly ["start", "restart", "stop"];
167
+ type TimerTrigger = (typeof TIMER_TRIGGERS)[number];
168
+ /** `log.ts`'s `LEVELS`, in its order — an authored level outside it falls back to `info` as the setter does. */
169
+ declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
170
+ export type LogLevel = (typeof LOG_LEVELS)[number];
171
+ /**
172
+ * EXP-011 §61. The component-stack trio: the visual container (display name "Component Stack"), the pusher
173
+ * ("Push Component To Stack") and the popper ("Pop Component Stack"). A push is a call and a pop is its
174
+ * return — the pusher grows a `backResult-<k>` output per result and a `backAction-<a>` signal per way of
175
+ * closing that any Pop in the target declares (`navigate.ts` setup), and the Pop's `result-<k>` values and
176
+ * `backAction-<a>` signals are the other end of the same wire (`navigate-back.ts`).
177
+ */
178
+ export declare const PAGE_STACK_TYPE = "Page Stack";
179
+ export declare const STACK_PUSH_TYPE = "PageStackNavigate";
180
+ export declare const STACK_POP_TYPE = "PageStackNavigateBack";
181
+ export declare const PAGE_STACK_OUTPUTS: readonly ["topPageName", "stackDepth"];
182
+ /** The reserved prop the stack row passes to the component it shows — the Pop's way back (§61.0). */
183
+ export declare const PAGE_STACK_ENTRY_PROP = "pageStackEntry";
184
+ /**
185
+ * EXP-011 §40. The outputs each node's **own compile** consumes when the node attaches — its
186
+ * Done/Failure chains and the sibling pulses it drops with a reason of its own.
187
+ *
188
+ * 🔴 **The attach pass must skip a wire off any of these, whatever order the file lists it in.**
189
+ * It walks connections in file order, and a chain wire listed *before* the wire that fires its
190
+ * node used to fall to *"the trigger is not a rendered element event or a receiver"* — reported
191
+ * dropped, marked consumed, and then emitted anyway when the node attached (`doneChainOf` filters
192
+ * by port, not by `consumed`). §39.3 found it on `Log`, `Delay` and `Value Changed` and measured
193
+ * the rest as unowned; §40.1 measured every family here order-dependent, and a node fired only
194
+ * from a reactive Condition or a Value Changed reported the same false note in *every* order,
195
+ * because that trigger wire is never the attach pass's to take.
196
+ *
197
+ * ⚠️ A port here must be one the node's compile answers for — consumed, deferred by name, or
198
+ * dropped with a reason — or the skip turns a false note into a silent one. Every entry below was
199
+ * read off the compile it names.
200
+ */
201
+ /**
202
+ * EXP-011 §53 — `Run Tasks` (type id `RunTasks`; `Run Tasks` is its display name, and a match on the
203
+ * display name never fires — §5.7's `runtasks` branch was unreachable until this row spelled it right).
204
+ * The five signal outputs are the node's whole output set (`outcomeOutputs` + `aborted`); the four
205
+ * contract names are `runtasks-template-contract.ts`'s defaults, applied when the parameter is absent
206
+ * or empty (`resolveTemplateContract`: "an empty name falls back rather than being honoured").
207
+ */
208
+ /** `On App Error` (EXP-011 §54) — the app-wide error boundary. The type id IS the display name. */
209
+ export declare const ON_APP_ERROR_TYPE = "On App Error";
210
+ /** The boundary's six value outputs — read off the `useAppError` handle's `last` (onapperror.ts's getters). */
211
+ export declare const ON_APP_ERROR_VALUE_OUTPUTS: readonly ["message", "code", "nodeId", "componentName", "nodeType", "errorObject"];
212
+ /** The Error Object's type, structurally — `src/lib/errors.ts`'s AppError, spelled so a store row needs no import. */
213
+ export declare const APP_ERROR_TS_TYPE = "{ code: string; message: string; nodeId: string; componentName: string; nodeType: string; detail?: unknown }";
214
+ /**
215
+ * EXP-011 §58 — the streaming trio (`agent/json-stream-parser.ts`, `stream-buffer.ts`, `text-accumulator.ts`).
216
+ * The type ids carry the `net.noodl.` prefix; the display names do not. Each is one hook over one `_internal`
217
+ * (`src/lib/streaming.ts`): the data port is read at the pulse, the config ports live off an options object, the
218
+ * signal outputs are listeners, the value outputs are live getters on the handle. The table is the catalog's
219
+ * port set, read off the node files — every port of the three translates, so what is refused is only the graph
220
+ * shapes every hook refuses (two wires, no static source, a pulse read as a value, an uncompilable chain).
221
+ */
222
+ export declare const STREAM_PARSER_TYPE = "net.noodl.JSONStreamParser";
223
+ export declare const STREAM_BUFFER_TYPE = "net.noodl.StreamBuffer";
224
+ export declare const TEXT_ACCUMULATOR_TYPE = "net.noodl.TextAccumulator";
225
+ export type StreamKind = 'parser' | 'buffer' | 'accumulator' | 'sse' | 'websocket' | 'subscription' | 'repeat' | 'dispatcher' | 'handler' | 'history' | 'undo' | 'snapshot' | 'optimistic' | 'signin';
226
+ /** EXP-011 §64. `Server-Sent Events` — the fourth member of the table: no data port, two Actions, its own module. */
227
+ export declare const SSE_TYPE = "net.noodl.SSE";
228
+ /** EXP-011 §65. `WebSocket` — the fifth member: a data port (Message) that only the Send verb carries, three Actions, its own module. */
229
+ export declare const WEBSOCKET_TYPE = "net.noodl.WebSocket";
230
+ /**
231
+ * EXP-011 §66. `Subscribe To Changes` — the sixth member, and the last scheduled node: no data port, no Actions, two config
232
+ * ports (the Class — a runtime-discovered `collectionName` parameter, hence `param` — and Enabled), five signals, seven values;
233
+ * its own module, riding EXP-009's client. The Backend picker, the Filter and its `qp-` ports are refused by name (streamPlanOf).
234
+ */
235
+ export declare const SUBSCRIBE_TO_CHANGES_TYPE = "SubscribeToChanges";
236
+ /**
237
+ * GAM-013 (P88). `Repeat` — the seventh member: no data port, one config port (Interval), two Actions (Start, Stop), Tick
238
+ * and the outcome trio as listeners, Count a live getter; its own module, `src/lib/repeat.ts`. The table fits it exactly,
239
+ * where Delay's per-verb callbacks (§39) could not carry a node whose Tick belongs to the node and whose Count is a value.
240
+ */
241
+ export declare const REPEAT_TYPE = "Repeat";
242
+ /**
243
+ * EXP-011 §77. `Action Dispatcher` and `Action Handler` — the eighth and ninth members, one module (`src/lib/actions.ts`)
244
+ * holding the app's one registry, so the two meet across components by channel name alone, as in the runtime.
245
+ */
246
+ export declare const ACTION_DISPATCHER_TYPE = "net.noodl.ActionDispatcher";
247
+ export declare const ACTION_HANDLER_TYPE = "net.noodl.ActionHandler";
248
+ /** The four built-in store actions (action-dispatcher.ts BUILT_IN_ACTIONS) — a dispatcher that enables one is refused in this slice. */
249
+ export declare const ACTION_BUILT_INS: readonly ["SET_STORE", "MERGE_STORE", "DELETE_STORE_KEY", "CLEAR_STORE"];
250
+ /**
251
+ * EXP-011 §78. `State History`, `Undo / Redo` and `State Snapshot` — the tenth, eleventh and twelfth members, one module
252
+ * (`src/lib/history.ts`) holding the app's one history manager and its named checkpoints over the exported Global Store.
253
+ * Each names its store by a literal Store Name, printed as the store module itself (`store: app`), so the history reads
254
+ * and writes the same `store()` the app's Set and Subscribe nodes do.
255
+ */
256
+ export declare const STATE_HISTORY_TYPE = "net.noodl.StateHistory";
257
+ export declare const UNDO_REDO_TYPE = "net.noodl.StateHistory.Undo";
258
+ export declare const STATE_SNAPSHOT_TYPE = "net.noodl.StateSnapshot";
259
+ /**
260
+ * EXP-011 §78 part 2. `Optimistic Update` — the thirteenth member, in the same module: a patch on the exported Global Store
261
+ * (apply now, commit or roll back later, overlapping patches handing values to each other) is one write through the
262
+ * history's store seam, so the history records it and every Subscribe sees it.
263
+ */
264
+ export declare const OPTIMISTIC_UPDATE_TYPE = "net.noodl.OptimisticUpdate";
265
+ /**
266
+ * EXP-011 §79. `Sign In With` — the fourteenth member, its own module (`src/lib/signIn.ts`): a launcher (Do hands the browser
267
+ * over to the backend's `/oauth/<provider>/start`) and a receiver (at mount it hears the sign-in coming back, which the
268
+ * client consumes when the app starts). Rides EXP-009's client, so it is kept only where the project declares a backend.
269
+ */
270
+ export declare const SIGN_IN_WITH_TYPE = "net.noodl.user.SignInWith";
271
+ /** EXP-011 §66. The structural RealtimeError — `src/lib/realtime.ts`'s, spelled so a store row needs no import. */
272
+ export declare const REALTIME_ERROR_TS_TYPE = "{ message: string; code: string; kind: string } | null";
273
+ export interface StreamValueField {
274
+ tsType: string;
275
+ /** How the value casts at a sink: the port's declared type, which is what NDA-014's typecast keys on. */
276
+ cast: 'string' | 'number' | 'boolean' | 'array' | 'unknown';
277
+ /** Undefined before anything happened (must agree with component.ts). */
278
+ maybeUndefined: boolean;
279
+ }
280
+ export interface StreamNodeSpec {
281
+ kind: StreamKind;
282
+ displayName: string;
283
+ hook: string;
284
+ localStem: string;
285
+ /** The module the hook lives in (component.ts maps it to a path; emitApp.ts ships it). */
286
+ lib: 'streaming' | 'sse' | 'websocket' | 'realtime' | 'repeat' | 'actions' | 'history' | 'signIn';
287
+ /** The runtime file the hook transcribes, for the emitted comment. */
288
+ sourceFile: string;
289
+ /** The data port, delivered with the pulse — absent on a node whose actions carry nothing (§64's SSE). */
290
+ data?: {
291
+ port: string;
292
+ displayName: string;
293
+ };
294
+ /** EXP-011 §78. The input naming the store module the hook is handed (StreamPlan.store) — never an option. */
295
+ store?: {
296
+ port: string;
297
+ displayName: string;
298
+ };
299
+ /** The config ports, read live off the options object. `param` names the on-disk parameter / wire port where it differs from the option (§66's Class is `collectionName` on disk). */
300
+ config: Array<{
301
+ port: string;
302
+ displayName: string;
303
+ param?: string;
304
+ }>;
305
+ /** The action ports: the verb on the handle, and whether the call carries the data port's value. */
306
+ actions: Record<string, {
307
+ verb: string;
308
+ takesData: boolean;
309
+ }>;
310
+ /** The signal outputs, as the listeners the hook takes, in the runtime's declaration order. */
311
+ signals: readonly string[];
312
+ values: Record<string, StreamValueField>;
313
+ }
314
+ export declare const STREAM_NODES: Record<string, StreamNodeSpec>;
315
+ /** EXP-011 §64. The type id behind a `stream-out`'s kind — the one ladder component.ts and the maybe-undefined answer share. */
316
+ export declare function streamTypeOfKind(kind: StreamKind): string;
317
+ export declare const RUN_TASKS_TYPE = "RunTasks";
318
+ /** EXP-011 §57. `Repeater Item` — the type id is the runtime's `name`, not the display name. */
319
+ export declare const REPEATER_ITEM_TYPE = "For Each Actions";
320
+ /** EXP-011 §60. The component-object trio — the Set, and the parent pair that read/write an ancestor's record. */
321
+ export declare const SET_COMPONENT_OBJECT_TYPE = "net.noodl.SetComponentObjectProperties";
322
+ export declare const PARENT_COMPONENT_OBJECT_TYPE = "net.noodl.ParentComponentObject";
323
+ export declare const SET_PARENT_COMPONENT_OBJECT_TYPE = "net.noodl.SetParentComponentObjectProperties";
324
+ /** EXP-011 §60. The message setparentcomponentobjectproperties.ts raises on a miss — verbatim (the lib exports the same constant). */
325
+ export declare const NO_ANCESTOR_WRITE_MESSAGE = "No ancestor component has a Component Object node \u2014 nothing was written";
326
+ export declare const RUN_TASKS_OUTPUTS: readonly string[];
327
+ export declare const RUN_TASKS_CONTRACT_TEMPLATE_PARAM = "taskTemplate";
328
+ export declare const RUN_TASKS_CONTRACT: {
329
+ readonly start: {
330
+ readonly parameter: "taskStartInput";
331
+ readonly default: "Do";
332
+ };
333
+ readonly success: {
334
+ readonly parameter: "taskSuccessOutput";
335
+ readonly default: "Success";
336
+ };
337
+ readonly failure: {
338
+ readonly parameter: "taskFailureOutput";
339
+ readonly default: "Failure";
340
+ };
341
+ readonly error: {
342
+ readonly parameter: "taskErrorOutput";
343
+ readonly default: "Error";
344
+ };
345
+ };
346
+ /** EXP-011 §62. The api-call / MutationPlan verb the pair compiles to. */
347
+ export type RelationVerb = 'add-relation' | 'remove-relation';
348
+ export type UserVerb = 'login' | 'logout' | 'signup' | 'update-user' | 'magic-link';
349
+ /** One refused wire, as EXP-004's marker needs to talk about it. */
350
+ export interface DroppedWire {
351
+ key: string;
352
+ fromId: string;
353
+ fromProperty: string;
354
+ toId: string;
355
+ toProperty: string;
356
+ /** The author's own label on the wire — irrecoverable once the wire is gone, so it travels. */
357
+ label?: string;
358
+ /** The exporter's own reason, in the words the note carries. */
359
+ reason: string;
360
+ }
361
+ export type BindingSource = {
362
+ kind: 'prop';
363
+ name: string;
364
+ }
365
+ /**
366
+ * A Variable read. `untyped` marks one whose writers this analysis could not type — the
367
+ * emitted `value<unknown>` (EXP-011 §10). It is not a defect and not a deferral: the render
368
+ * sinks coerce it, exactly as the runtime's own sinks coerce whatever the variable holds.
369
+ */
370
+ | {
371
+ kind: 'store';
372
+ variableName: string;
373
+ untyped?: true;
374
+ }
375
+ /**
376
+ * A single-key Global Store read. `untyped` is the Variable rule above, one construct over
377
+ * (EXP-011 §10.5): a key whose value this analysis could not type as `string`/`number` — a
378
+ * `boolean`, or an `unknown` written from an HTTP body or a Function output — binds and is
379
+ * coerced at the sink, rather than dropping the read the way it did before.
380
+ *
381
+ * 🔴 The gate is widened **here only**. `resolveExpr` still refuses the same key, because an
382
+ * expression position is arithmetic, a date argument or a url segment, and none of those has
383
+ * a sink that can state what it holds. §10.5 named that as the reason not to lift the gate
384
+ * wholesale, and it is still the reason.
385
+ */
386
+ | {
387
+ kind: 'store-key';
388
+ storeName: string;
389
+ key: string;
390
+ untyped?: true;
391
+ } | {
392
+ kind: 'computed';
393
+ expr: ValueExpr;
394
+ } | {
395
+ kind: 'unresolved';
396
+ fromId: string;
397
+ fromProperty: string;
398
+ };
399
+ /**
400
+ * A value read inside a handler or binding, resolved to what the generated code can actually
401
+ * say. `input-text` and `payload` are context-bound: they only exist inside the owning input's
402
+ * onChange and the owning receiver's handler respectively — attachment validates that.
403
+ *
404
+ * Step 6's additions compose: `format` is a String Format resolved to alternating static text
405
+ * and sub-expressions (a template literal at emit), and `store-key-get` is a single-key
406
+ * Subscribe read usable in either context (the selector hook local in render, `.get().<key>`
407
+ * in a handler).
408
+ *
409
+ * The boolean kinds (`logical`, `not`, `truthy` — LOGIC-TARGET §6) are truthiness devices:
410
+ * their short forms (`a && b`, `!x`, the bare operand) are truthiness-equal to the runtime's
411
+ * strict-boolean outputs, not value-equal, so analysis admits them into truthiness sinks only
412
+ * (a Condition's test, another logical's operand, the `enabled` render sink). `truthy` marks a
413
+ * Condition's `result` — provenance the value-sink gates need, emitted as the bare condition.
414
+ *
415
+ * `undefined` is a Component Object property's boot value (COMPONENT-OBJECT-TARGET §3): the
416
+ * record boots empty and no wire writes the key, so the read is the constant the runtime
417
+ * would deliver. It is maybe-undefined by definition and folds at its sinks the way the
418
+ * runtime folds it — format parts to '', truthiness to false, render children/attrs to the
419
+ * empty/omitted form.
420
+ */
421
+ /**
422
+ * EXP-011 §56. The neutral operators a `Filter Records` condition may carry and still translate — the ones
423
+ * `queryutils.ts`'s local matcher answers from a loaded record alone, after `toParseWhere`'s lowering. Everything
424
+ * schema-bound (`pointsTo`, `relatedTo`, `textSearch`, the id and geo operators) is refused by name in
425
+ * `recordFilterReadOf`, because the runtime resolves those through the backend or not at all.
426
+ */
427
+ export type RecordFilterOp = 'equalTo' | 'notEqualTo' | 'lessThan' | 'greaterThan' | 'lessThanOrEqualTo' | 'greaterThanOrEqualTo' | 'containedIn' | 'notContainedIn' | 'exists' | 'matchesRegex' | 'contains' | 'notContains' | 'containsIgnoreCase' | 'startsWith' | 'notStartsWith' | 'startsWithIgnoreCase' | 'endsWith' | 'notEndsWith' | 'endsWithIgnoreCase' | 'between' | 'notBetween' | 'isEmpty' | 'isNotEmpty';
428
+ /**
429
+ * EXP-011 §56. A `Filter Records` filter tree as the emitted app evaluates it — the builder's saved shape (either
430
+ * generation) read the way `savedFilterToNeutral` / `visualQueryToNeutral` read it, with every value either a
431
+ * static JSON value or the expression wired into the condition's `fp-<name>` port. `connected` carries the runtime's
432
+ * `dropUnresolvedConnected` rule to the client: a connected condition whose value is `undefined` does not narrow.
433
+ */
434
+ export type RecordWhere = {
435
+ and: RecordWhere[];
436
+ } | {
437
+ or: RecordWhere[];
438
+ } | {
439
+ field: string;
440
+ op: RecordFilterOp;
441
+ value: {
442
+ kind: 'json';
443
+ value: unknown;
444
+ } | {
445
+ kind: 'expr';
446
+ expr: ValueExpr;
447
+ };
448
+ connected: boolean;
449
+ };
450
+ export type ValueExpr = {
451
+ kind: 'prop';
452
+ name: string;
453
+ } | {
454
+ kind: 'input-text';
455
+ inputId: string;
456
+ } | {
457
+ kind: 'store-get';
458
+ variableName: string;
459
+ } | {
460
+ kind: 'store-key-get';
461
+ storeName: string;
462
+ key: string;
463
+ } | {
464
+ kind: 'payload';
465
+ key: string;
466
+ receiverId: string;
467
+ }
468
+ /**
469
+ * A `Page Inputs` parameter (EXP-011 Tier 2.5) — one `pm-*` output, read from the url.
470
+ *
471
+ * 🔴 **One expression for both stringlists, because the runtime has one namespace.** The node
472
+ * declares `pathParams` and `queryParams` separately and the editor draws a port per name, but
473
+ * the Router hands the node a single flat map built as
474
+ * `Object.assign({}, match.params, urlQuery)` (`router.tsx:456`) — so a name in both lists is
475
+ * one port, and **every** query parameter in the url overrides the path segment of the same
476
+ * name, whether or not the author listed it. `?id=99` on `/product/42` reads `99`.
477
+ *
478
+ * That is why this carries no `from: 'path' | 'query'` discriminator: a read that consulted
479
+ * only the list the name was declared in would be a different function from the one the
480
+ * running app computes, and would differ exactly on the urls a user can type by hand.
481
+ *
482
+ * Maybe-undefined by definition — an unmatched segment or an absent query key — and it folds
483
+ * at its sinks the way the runtime's own Text node folds it ("an empty value renders nothing
484
+ * rather than the words null or undefined", text.ts).
485
+ */
486
+ | {
487
+ kind: 'page-param';
488
+ name: string;
489
+ } | {
490
+ kind: 'literal';
491
+ value: string | number | boolean;
492
+ } | {
493
+ kind: 'format';
494
+ parts: Array<string | ValueExpr>;
495
+ } | {
496
+ kind: 'logical';
497
+ op: 'and' | 'or';
498
+ operands: ValueExpr[];
499
+ } | {
500
+ kind: 'not';
501
+ operand: ValueExpr;
502
+ } | {
503
+ kind: 'truthy';
504
+ operand: ValueExpr;
505
+ } | {
506
+ kind: 'undefined';
507
+ }
508
+ /**
509
+ * A re-hosted Function/Expression output (EXP-003 §4): in render it reads the node's render
510
+ * local (`formatShoutOut.text`); in a handler it inlines the call over `.get()` snapshots —
511
+ * legal because the gate admits only pure bodies, so recomputation is unobservable. `fold`
512
+ * carries Expression's typed getters (`asString` → `String(x ?? '')`, `asNumber` →
513
+ * `Number(x) || 0`, `asBoolean` → `!!x` — expression.ts, verbatim semantics). `viaState` is
514
+ * the 4f landing zone (CONTROLLED-STATE-TARGET): an *invoked* node whose outputs feed render
515
+ * sinks materializes its output record as a state var, and render reads go through it
516
+ * (`stockCheckOut?.warning`) — maybe-undefined until the first invocation, the runtime's own
517
+ * pre-first-run contract.
518
+ */
519
+ | {
520
+ kind: 'jsfun-out';
521
+ nodeId: string;
522
+ output: string;
523
+ fold?: 'string' | 'number' | 'boolean';
524
+ viaState?: string;
525
+ }
526
+ /**
527
+ * A state var read (CONTROLLED-STATE-TARGET §3.2): the render closure's value in both render
528
+ * and handler positions. Inside a handler chain the attachment pass applies the chain-local
529
+ * snapshot rule — a read after a `state-set` in the same chain is rewritten to the written
530
+ * expression, because the runtime updates state synchronously mid-chain and React closures
531
+ * do not.
532
+ */
533
+ | {
534
+ kind: 'state-get';
535
+ name: string;
536
+ maybeUndefined?: boolean;
537
+ }
538
+ /**
539
+ * EXP-011 §100. A repeater's relayed row value (`itemOutput-<output>`) read inside a chain one of its rows' relayed
540
+ * signals runs: the firing row's own value, which the runtime copies onto the repeater before it sends the signal
541
+ * (`foreach.tsx` `itemOutputSignalTriggered`). Minted only while such a chain is compiled (`rowChainScopes`), valid only
542
+ * in that repeater's row callbacks, and printed there as what the row is handed for `input` (the row's input the output
543
+ * passes straight through). `tsType` is the row template's declared output type.
544
+ */
545
+ | {
546
+ kind: 'row-relay';
547
+ repeaterId: string;
548
+ output: string;
549
+ input: string;
550
+ tsType: string;
551
+ }
552
+ /**
553
+ * The user-path value inside a control's own onChange (CONTROLLED-STATE-TARGET §4c) — the
554
+ * `input-text` context rule generalized per control role: `event.target.checked` for a
555
+ * checkbox, `Number(event.target.value)` for a range, `event.target.value` for a dropdown.
556
+ */
557
+ | {
558
+ kind: 'control-event';
559
+ controlId: string;
560
+ form: 'string' | 'checked' | 'number';
561
+ }
562
+ /**
563
+ * A read of the signed-in user (USER-FAMILY-TARGET §4c) — the `User` node's outputs, which are
564
+ * getters over the session rather than stored values. `authenticated` is
565
+ * `model !== undefined` in the runtime, so it is a real boolean and lands at value sinks as
566
+ * well as truthiness ones; the other three are `model.get(…)` and read undefined while nobody
567
+ * is signed in, which is exactly the state the stub reports.
568
+ */
569
+ | {
570
+ kind: 'session-get';
571
+ nodeId: string;
572
+ field: 'authenticated' | 'id' | 'username' | 'email';
573
+ }
574
+ /**
575
+ * A named client-side array, read as a list (EXP-011 Tier 1.1).
576
+ *
577
+ * The collections slice could reach a named array only as a repeater's feed, through
578
+ * {@link RepeaterPlan.itemsCollectionName} — a dedicated field, not an expression. That was
579
+ * enough while `For Each` was the only consumer; `Array Filter` and `Array Map` are a second
580
+ * and third, and they compose (`notes → filter → map → For Each`), which a per-consumer field
581
+ * cannot express. So the array becomes an ordinary value expression and the transforms are
582
+ * ordinary functions of it.
583
+ *
584
+ * In render it is the `useCollection` local, so the component re-renders when the array
585
+ * changes; in a handler it is `.peek()` — a read that deliberately creates no dependency edge,
586
+ * which is what a handler wants and what `Collection.get()` would get wrong.
587
+ */
588
+ | {
589
+ kind: 'collection-get';
590
+ collectionName: string;
591
+ }
592
+ /**
593
+ * FLD-015. A `Static Data` node's `items`, read as a value rather than fed to a repeater —
594
+ * the hoisted module constant by name.
595
+ *
596
+ * 🔴 **Before this existed, `items` had exactly one destination: a `For Each`.** The wire was
597
+ * matched by target (`toNode.type === 'For Each'`), so an array going anywhere else — a chart
598
+ * kit's `series`, any custom node with an `array` port — reached no branch at all and left as
599
+ * *"has no deterministic translation"*. The rows were already hoisted, already typed and
600
+ * already sitting in the emitted file; nothing could say their name.
601
+ *
602
+ * The constant is module scope and frozen at build time, so this reads in every context and is
603
+ * never undefined — `collection-get`'s answer, for a stronger reason: there is no hook and no
604
+ * boot value, the array IS the literal.
605
+ */
606
+ | {
607
+ kind: 'static-rows';
608
+ nodeId: string;
609
+ constName: string;
610
+ rowType: string;
611
+ }
612
+ /**
613
+ * EXP-011 §55. A `Create New Array`'s array, read through an `Array` node bound to it **by wire** — the
614
+ * handle in the minting component's state row. In render it is the `useCollection` hook local over
615
+ * `<row> ?? noArray`; in a handler `(<row>?.peek() ?? [])`; inside the mint's own Done chain the
616
+ * chain-local (`viaLocal`) — `setX` does not change `x` in that closure, and the chain must see the array
617
+ * it just made (the id nodes' Done-arm rule, §37). Type `any[]`, exactly as `collection-get` answers.
618
+ */
619
+ | {
620
+ kind: 'minted-array-get';
621
+ nodeId: string;
622
+ viaLocal?: string;
623
+ }
624
+ /**
625
+ * `Array Map` over a list (EXP-011 Tier 1.1) — `mapcollectionnode.ts`'s `map({…})` with every
626
+ * mapping a **property name**, which is `m.set(key, model.get(mapping))` in the runtime and
627
+ * `{ key: row.mapping }` here.
628
+ *
629
+ * ⚠️ A mapping whose value is a *function* is arbitrary JavaScript over a live `Model` and
630
+ * defers to EXP-003 — {@link parseIdentityMapping} answers null for it, which is the same
631
+ * gate `For Each`'s own mapping script goes through.
632
+ */
633
+ | {
634
+ kind: 'list-map';
635
+ source: ValueExpr;
636
+ entries: Array<{
637
+ key: string;
638
+ field: string;
639
+ }>;
640
+ }
641
+ /**
642
+ * `Array Filter` over a list (EXP-011 Tier 1.1) — the panel-authored filter, sort and
643
+ * skip/limit, which are `filterSettings` and not a script (`filtercollectionnode.ts`).
644
+ *
645
+ * Each piece is optional and they compose in the runtime's own order: filter, then sort, then
646
+ * skip, then limit (`scheduleFilter`). Emitting them in any other order would silently change
647
+ * which rows survive a limit.
648
+ */
649
+ | {
650
+ kind: 'list-filter';
651
+ source: ValueExpr;
652
+ tests: Array<{
653
+ field: string;
654
+ op: 'eq' | 'neq' | 'gt' | 'lt' | 'gte' | 'lte';
655
+ value: string | number | boolean;
656
+ }>;
657
+ sort: Array<{
658
+ field: string;
659
+ direction: 'ascending' | 'descending';
660
+ }>;
661
+ skip?: number;
662
+ limit?: number;
663
+ }
664
+ /**
665
+ * EXP-011 §56. A `Query Records`' result, read as a list: the state row its fetch effect fills (`QueryPlan`),
666
+ * typed `<TypeName>[]` off the project's declared collection — the same row the repeater branch reads directly.
667
+ */
668
+ | {
669
+ kind: 'query-get';
670
+ nodeId: string;
671
+ stateName: string;
672
+ typeName: string;
673
+ collectionName: string;
674
+ }
675
+ /**
676
+ * EXP-011 §56. `Filter Records` over a list — the saved filter tree, the visual sorting and skip/limit, applied
677
+ * in `scheduleFilter`'s order by `src/lib/filterRecords.ts` (the runtime's local matcher, transcribed). Unlike
678
+ * `list-filter` the rows keep their static type: a filter selects, it does not reshape.
679
+ */
680
+ | {
681
+ kind: 'record-filter';
682
+ source: ValueExpr;
683
+ where: RecordWhere | null;
684
+ sort: Array<{
685
+ field: string;
686
+ direction: 'ascending' | 'descending';
687
+ }>;
688
+ skip?: number;
689
+ limit?: number;
690
+ }
691
+ /** EXP-011 §56. A list transform's `Count` — the derived list's length, always current. */
692
+ | {
693
+ kind: 'list-count';
694
+ source: ValueExpr;
695
+ }
696
+ /**
697
+ * An `HTTP Request` output (EXP-011 Tier 1.2) — the answer the last request produced.
698
+ *
699
+ * Two forms, and which one a read takes is decided by **where the read is**, not by the port:
700
+ *
701
+ * - Inside the node's own outcome chain, `viaState` is absent and the read is the chain's
702
+ * local (`quoteAnswer.response`, `quoteMessage`). It has to be: the setter has been called
703
+ * but React state does not change inside the closure that called it, so a state read here
704
+ * would deliver the *previous* request's answer — the chain-local snapshot rule
705
+ * (CONTROLLED-STATE-TARGET §3.2) reaching a construct that cannot recompute itself.
706
+ * - Anywhere else — render, another handler — `viaState` names the state row the call writes,
707
+ * maybe-undefined until the first request, which is the runtime's own pre-first-fetch state.
708
+ *
709
+ * `output` is a runtime port name: `response`, `statusCode`, `responseHeaders`, `error`, or
710
+ * an author's Response Mapping as `out-<name>` (`registerOutputIfNeeded` prefixes every one of
711
+ * them, so a mapping an author calls "response" is `out-response` and cannot collide).
712
+ */
713
+ | {
714
+ kind: 'http-out';
715
+ nodeId: string;
716
+ output: string;
717
+ viaState?: string;
718
+ }
719
+ /** EXP-011 §41 — a `Cloud Function`'s `Error` or an `out-<result>`, the HTTP shape one node over. */
720
+ | {
721
+ kind: 'cloud-out';
722
+ nodeId: string;
723
+ output: string;
724
+ viaState?: string;
725
+ }
726
+ /**
727
+ * EXP-011 §43 — a `Record`'s `Error` or a `prop-<column>`. `tsType` is the column's declared
728
+ * type off the project's schema snapshot (`tsColumnType`), `unknown` for a column the snapshot
729
+ * does not carry — the sink decides what an `unknown` needs, as it does for an untyped Variable.
730
+ */
731
+ | {
732
+ kind: 'record-out';
733
+ nodeId: string;
734
+ output: string;
735
+ tsType: string;
736
+ viaState?: string;
737
+ }
738
+ /**
739
+ * EXP-011 §45 — a files node's answer, the Record shape with three families behind one kind:
740
+ * `pick` reads the chosen `File` (`file` is the object itself; `name`/`size`/`type` its
741
+ * members), `upload` reads the stored file (`cloudFile` the object; `url`/`name`/`contentType`
742
+ * /`size` its members — and `name` is `cloudFileName(...)`, the prefix stripped as the Cloud
743
+ * File node strips it), `sign` reads the minted link (`url`/`kind`/`isShareable`/`expiresAt`/
744
+ * `ttlSeconds`). `error` is the message in every family. A `Cloud File` node resolves to the
745
+ * `upload` family of the Upload File that feeds it — it holds nothing of its own.
746
+ */
747
+ | {
748
+ kind: 'file-out';
749
+ family: 'pick' | 'upload' | 'sign';
750
+ nodeId: string;
751
+ output: string;
752
+ tsType: string;
753
+ viaState?: string;
754
+ }
755
+ /**
756
+ * EXP-011 §46 — a member of a stored file that is *not* a files node's own answer: a `Cloud
757
+ * File` fed from a `Record`'s File-typed column reads `source.url` / `cloudFileName(source)` /
758
+ * `source.contentType` / `source.size`, where `source` is the record's column read (a
759
+ * `record-out` typed `CloudFile`, in either of its forms). `name` goes through `cloudFileName()`
760
+ * as the upload family's does; the two optional members are optional here too.
761
+ */
762
+ | {
763
+ kind: 'file-field';
764
+ source: ValueExpr;
765
+ field: 'url' | 'name' | 'contentType' | 'size';
766
+ tsType: string;
767
+ }
768
+ /**
769
+ * A `Now` output (EXP-011 Tier 1.3) — the instant of the last Read.
770
+ *
771
+ * 🔴 **`Now` is not a live clock, and reading it as one is the trap this kind exists to avoid.**
772
+ * The node's own port description says the outputs "hold the instant of the last Read, not a
773
+ * live value", and `initialize` seeds `_internal.now` so they are never empty before the first
774
+ * one. That is a `useState` with a **lazy initializer** — `useState(() => new Date())`, read
775
+ * once per mount exactly as `initialize` runs once per node. A bare `new Date()` in render
776
+ * would recompute on every pass and re-render forever; the runtime's own `runOnChange`
777
+ * vocabulary answered this rather than the export choosing.
778
+ *
779
+ * Two forms, and which one a read takes is decided by **where the read is** — the `http-out`
780
+ * rule (§8.2) reaching the second construct that needs it. `setNow(...)` does not change `now`
781
+ * inside the closure that called it, so a state read inside the Read chain would deliver the
782
+ * *previous* instant; the chain binds the instant to a local and reads that. Two bare
783
+ * `new Date()` calls in one chain could also straddle a millisecond boundary, which is the
784
+ * same bug wearing a different hat.
785
+ *
786
+ * ⚠️ Never maybe-undefined, alone in this family: the row is seeded at mount and every write
787
+ * is a fresh `Date`, so `now.getTime()` needs no guard.
788
+ */
789
+ | {
790
+ kind: 'now-out';
791
+ nodeId: string;
792
+ output: 'date' | 'timestamp' | 'iso';
793
+ viaState?: string;
794
+ }
795
+ /**
796
+ * A `Unique Id`'s or `UUID`'s `Id` output (EXP-011 §37) — the row anywhere, the chain's own
797
+ * local inside the `New` chain, on `now-out`'s rule and for `now-out`'s reason.
798
+ *
799
+ * `fn` rides on the expression rather than being looked up from the node, because the one
800
+ * question every consumer asks about this kind — can the read be empty — is answered by which
801
+ * generator it is: `randomId` cannot fail, so its row is seeded; `randomUuid` can, so its row
802
+ * boots `undefined` on a host with no CSPRNG.
803
+ */
804
+ | {
805
+ kind: 'id-out';
806
+ nodeId: string;
807
+ fn: 'randomId' | 'randomUuid';
808
+ viaState?: string;
809
+ }
810
+ /**
811
+ * `Hash`'s Digest or `Random Bytes`' Value (EXP-011 §59) — `id-out`'s two forms: the row anywhere,
812
+ * the chain-local (`<local>.digest` / `<local>.value`) inside the node's own Done arm. The row is
813
+ * always maybe-undefined: neither node seeds it, so it reads nothing until the first Do.
814
+ */
815
+ | {
816
+ kind: 'crypto-out';
817
+ nodeId: string;
818
+ node: CryptoNodeKind;
819
+ viaState?: string;
820
+ field?: string;
821
+ }
822
+ /**
823
+ * EXP-011 §76. A `Pattern Extractor`'s value output carries its port as `field` (`match`, `matches`,
824
+ * `groups`, `firstGroup`, `namedGroups`, `matchCount`); the row is one per field, seeded, never
825
+ * undefined; inside ANY of the node's four arms the read is the local's field — the Failure arm too,
826
+ * because a failed pattern clears the values and the render closure's row is the stale one.
827
+ */
828
+ /**
829
+ * A `Screen Resolution`'s width, height or aspect ratio (EXP-011 §59) — a number off the
830
+ * `useScreenResolution` handle, never undefined (the hook seeds from the window at mount).
831
+ */
832
+ | {
833
+ kind: 'screen-out';
834
+ nodeId: string;
835
+ local: string;
836
+ field: (typeof SCREEN_RESOLUTION_OUTPUTS)[number];
837
+ }
838
+ /** EXP-011 §61. A Component Stack's two outputs off its `usePageStack` handle — a string and a number, never undefined. */
839
+ | {
840
+ kind: 'stack-out';
841
+ nodeId: string;
842
+ local: string;
843
+ field: (typeof PAGE_STACK_OUTPUTS)[number];
844
+ }
845
+ /**
846
+ * EXP-011 §61. A pusher's `backResult-<k>`: inside one of its `backAction-*` chains the callback's own
847
+ * `results` argument (the runtime flags the outputs dirty BEFORE sending the signal, and a state read
848
+ * there would be the stale closure); anywhere else the row `viaState`, typed `unknown` — the port is `*`.
849
+ */
850
+ | {
851
+ kind: 'stack-back-result';
852
+ nodeId: string;
853
+ key: string;
854
+ viaState?: string;
855
+ local?: string;
856
+ }
857
+ /**
858
+ * EXP-011 §60. A key of a component-object record: `<local>.value.<key>` in render, `<local>.get().<key>` in a
859
+ * handler (live, as `model.get` is). `parent` reads the nearest ancestor's record through the context hook, whose
860
+ * handle is `undefined` at the root — the read prints `?.` and answers undefined, as the unbound node's output does.
861
+ * `tsType` is the key's own type (`unknown` for a parent read, or a key with no statically-known own writer).
862
+ */
863
+ | {
864
+ kind: 'component-object-out';
865
+ nodeId: string;
866
+ local: string;
867
+ key: string;
868
+ parent: boolean;
869
+ tsType: string;
870
+ }
871
+ /**
872
+ * A `Drag`'s Drag X / Drag Y / Delta X / Delta Y (EXP-011 §63) — a number off the `useDrag` handle's live getters,
873
+ * never undefined (the hook seeds all four at mount from Start Drag X/Y).
874
+ */
875
+ | {
876
+ kind: 'drag-out';
877
+ nodeId: string;
878
+ local: string;
879
+ field: 'x' | 'y' | 'deltaX' | 'deltaY';
880
+ }
881
+ /**
882
+ * The `Error` output of `External Link` or `Navigate To Path` (EXP-011 §24) — the message the
883
+ * node wrote just before it fired `Failure`.
884
+ *
885
+ * One kind for two nodes, because the two ports are the same port: a short string out of
886
+ * `_internal.lastError`, written immediately before the Failure fork and **never cleared**.
887
+ * `http-out`'s two forms (§8.2) reaching the third and fourth construct that needs them, and
888
+ * which form a read takes is decided by **where the read is**:
889
+ *
890
+ * - Inside the node's own **Failure** chain, `viaState` is absent and the read is `local` — the
891
+ * `const` the failure arm binds the message to. It has to be: the setter has been called but
892
+ * React state does not change inside the closure that called it, so a state read there would
893
+ * deliver the *previous* failure's message.
894
+ * - Everywhere else — render, another handler, and **the node's own Done chain** — `viaState`
895
+ * names the row the failure arm writes. In the Done chain that is not a concession: the
896
+ * interpreter never clears `lastError` either, so a read there is the previous failure's
897
+ * message in both, and the stale row is the faithful answer rather than the tolerable one.
898
+ *
899
+ * ⚠️ **`Navigate To Path`'s Completed chain gets neither form, and that is the asymmetry this
900
+ * kind is easiest to get wrong at.** `Completed` is printed as a join *beneath* both arms, so
901
+ * the failure arm's `const` is out of scope there, while the row is still the pre-render value
902
+ * — and unlike the Done chain that is now *wrong*, because Completed runs after the failure
903
+ * that just wrote the message. Neither form is faithful, so the read stays refused.
904
+ *
905
+ * `local` is carried on the expression rather than looked up from the node, so the emitter
906
+ * needs no second table to agree with about a name.
907
+ */
908
+ | {
909
+ kind: 'outcome-error';
910
+ nodeId: string;
911
+ local: string;
912
+ viaState?: string;
913
+ seeded?: true;
914
+ }
915
+ /**
916
+ * EXP-011 §76. `seeded` marks an Error row that `initialize` wrote `''` into and every run rewrites — the
917
+ * extractor's — so the row form is never undefined either. Absent for every other node, whose row reads
918
+ * undefined until the first failure.
919
+ */
920
+ /**
921
+ * One of the five **pure** date nodes (EXP-011 Tier 1.3) — `Date Add`, `Date Compare`,
922
+ * `Date Difference`, `Date Parts`, `Date To String`.
923
+ *
924
+ * They are value-driven: each recomputes whenever any input changes and holds nothing else, so
925
+ * a node becomes **an ordinary function call** the way Tier 1.1's arrays became ordinary value
926
+ * expressions. `fn` names a helper in the emitted `src/lib/date.ts`, and `args` are its
927
+ * arguments in position order — a wired input is the expression that feeds it, an authored one
928
+ * a literal. They compose, which is the whole point: `Now → Date Add → Date To String` is one
929
+ * nested expression and needs no per-consumer field to carry it.
930
+ *
931
+ * Every input may be wired, `Unit` and `Granularity` included since P109-S21-EXPORTDATEUNITWIRED:
932
+ * the emitted helpers coerce both as the setters do (`unitOrDays`; an unknown Granularity
933
+ * truncates nothing), and none of them has a throwing branch.
934
+ */
935
+ | {
936
+ kind: 'date-call';
937
+ fn: DateHelper;
938
+ args: ValueExpr[];
939
+ }
940
+ /**
941
+ * One of the three small **pure** utilities (EXP-011 Tier 2.7) — `Substring`,
942
+ * `Number Remapper`, `String Mapper`.
943
+ *
944
+ * Structurally `date-call`'s twin: a node that is a function of its inputs and holds nothing
945
+ * else becomes one call into an emitted library, `args` in the helper's parameter order, and
946
+ * the calls compose. Every clause in this package that walks a `date-call`'s arguments walks
947
+ * this one's for the same reason, which is why the two are handled together at each of them
948
+ * rather than by a second copy of each walker.
949
+ *
950
+ * They are a separate kind rather than a widened `DateHelper` because the two libraries are
951
+ * separate modules in the emitted app: a project that formats a date should not ship the
952
+ * string utilities, and the import lines are earned per helper.
953
+ *
954
+ * ⚠️ **`cases` is `mapString`'s table and prints between `args[0]` and `args[1]`.** The two
955
+ * numbered-input families are index-aligned lists in the interpreter — `inputs[n]` matched by
956
+ * `indexOf`, `mappings[n]` read off the index it found — and an object literal reproduces that
957
+ * exactly once two things are true of how it is built: **first-wins on a repeated Input**,
958
+ * because `indexOf` finds the first and an object literal keeps the last; and a **present key
959
+ * with an `undefined` value** for an Input whose Mapping was never filled in, because that
960
+ * publishes empty rather than falling through to Default. Both are done where the table is
961
+ * built, and the helper's `hasOwnProperty` is the second half of the second one.
962
+ */
963
+ | {
964
+ kind: 'util-call';
965
+ fn: UtilHelper;
966
+ args: ValueExpr[];
967
+ cases?: Array<{
968
+ from: string;
969
+ to: string | undefined;
970
+ }>;
971
+ /**
972
+ * P109-S21-EXPORTBLENDNAN — `blendColorKept`'s first argument: the `useRef` local holding the blend this
973
+ * Color Blend last received and the colour it last showed, declared once per node at the top of the component. 🔴 Both expression walkers
974
+ * (`collectExprUse` and `hookExprSources`) record it; missing one prints a call naming a local nobody declared.
975
+ */
976
+ kept?: string;
977
+ }
978
+ /**
979
+ * A `States` node's value outputs (EXP-011 §49) — a read off the `useStates` handle the
980
+ * component declares: `state` is the State output (`<local>.state`), `value` one of the
981
+ * authored values (`<local>.values.<name>`, typed by its `type-<name>`), `at` an `At <state>`
982
+ * (`<local>.state === '<state>'`, a real boolean, so it lands at value sinks too), `error` the
983
+ * last Error (`<local>.error`, undefined until a failure). No `viaState`: the handle *is* the
984
+ * render-scope value, read the same way in a handler — the hook re-renders the component on
985
+ * every frame of a tween, and a handler closes over the latest render, which is what the
986
+ * interpreter's getter answers too.
987
+ */
988
+ | {
989
+ kind: 'states-out';
990
+ nodeId: string;
991
+ local: string;
992
+ field: 'state' | 'value' | 'at' | 'error';
993
+ name?: string;
994
+ tsType: string;
995
+ }
996
+ /**
997
+ * An `Animate To Value`'s `Current Value` (EXP-011 §49) — the number `useAnimatedValue` returns,
998
+ * never undefined (it boots 0 and only ever holds a number).
999
+ */
1000
+ /**
1001
+ * A `Script` node's value output (EXP-011 §52) — read off the `useScript` handle's `outputs`, which
1002
+ * reads the live record in both modes: render re-renders on every write, and a handler that fires a
1003
+ * signal reads what the code has written so far, exactly as the runtime getter answers. Always
1004
+ * maybe-undefined — an output the code has not written yet reads undefined.
1005
+ */
1006
+ | {
1007
+ kind: 'script-out';
1008
+ nodeId: string;
1009
+ local: string;
1010
+ output: string;
1011
+ tsType: string;
1012
+ }
1013
+ /**
1014
+ * An `On App Error` boundary's value output (EXP-011 §54) — read off the `useAppError` handle's `last`, the
1015
+ * latest error it accepted, in both modes: the hook re-renders on every accepted error, and a chain fired
1016
+ * by its Error signal reads the error that fired it (the values are updated before the signal, as
1017
+ * onapperror.ts flags them dirty before `sendSignalOnOutput('error')`). Always maybe-undefined — before
1018
+ * the first error there is nothing to read.
1019
+ */
1020
+ | {
1021
+ kind: 'app-error-out';
1022
+ nodeId: string;
1023
+ local: string;
1024
+ field: (typeof ON_APP_ERROR_VALUE_OUTPUTS)[number];
1025
+ tsType: string;
1026
+ }
1027
+ /**
1028
+ * A streaming node's value output (EXP-011 §58) — a live getter on the hook's handle, read the same way in
1029
+ * both contexts: the host re-renders once per invocation, and a chain fired by one of the node's signals
1030
+ * reads the state the runtime's getter would answer at that moment. `maybeUndefined` is the table's.
1031
+ */
1032
+ | {
1033
+ kind: 'stream-out';
1034
+ nodeId: string;
1035
+ local: string;
1036
+ node: StreamKind;
1037
+ field: string;
1038
+ tsType: string;
1039
+ } | {
1040
+ kind: 'animate-out';
1041
+ nodeId: string;
1042
+ local: string;
1043
+ };
1044
+ /**
1045
+ * A `Script` node's signal input pulsed (EXP-011 §52) — `<local>.signals.<port>()`, one call on the
1046
+ * handle. The code's function for that signal runs after the handler that asked (a microtask), which is
1047
+ * where `scheduleSignal` puts it in the runtime; nothing rides on the action, and the node's signal
1048
+ * outputs are its listeners ({@link ScriptPlan.listeners}), passed once to the hook.
1049
+ */
1050
+ /**
1051
+ * A `Run Tasks` node's two action ports (EXP-011 §53) — `Do` is `<local>.run(<items>)`, the list read at
1052
+ * the pulse (the runtime reads `internal.items`, the last value delivered, at `run()`); `Abort` is
1053
+ * `<local>.abort()`. Neither awaits: `run()` schedules its work through the node's own serial queue and
1054
+ * answers through the listeners ({@link RunTasksPlan.listeners}), passed once to the hook.
1055
+ */
1056
+ export type RunTasksAction = {
1057
+ kind: 'runtasks-run';
1058
+ nodeId: string;
1059
+ local: string;
1060
+ items: ValueExpr;
1061
+ } | {
1062
+ kind: 'runtasks-abort';
1063
+ nodeId: string;
1064
+ local: string;
1065
+ };
1066
+ /**
1067
+ * P18-S116-QUERYFETCH. A `Query Records`' Fetch (`storageFetch` → `scheduleFetch`): the query asks again, its wired
1068
+ * filter values read at the pulse, and the list is replaced by the answer. `void fetch<Plural>(…).then(set<Plural>)`:
1069
+ * the runtime's fetch is scheduled, so the chain does not wait for it. `values` are {@link QueryPlan.wiredValues}, carried
1070
+ * so every walker sees what the action reads. A query the export does not keep prints nothing (`expandActions`).
1071
+ */
1072
+ export type QueryFetchAction = {
1073
+ kind: 'query-fetch';
1074
+ nodeId: string;
1075
+ values: Array<{
1076
+ name: string;
1077
+ expr: ValueExpr;
1078
+ }>;
1079
+ };
1080
+ export type ScriptSignalAction = {
1081
+ kind: 'script-signal';
1082
+ nodeId: string;
1083
+ /** The `useScript` local. */
1084
+ local: string;
1085
+ port: string;
1086
+ };
1087
+ export type HandlerAction =
1088
+ /**
1089
+ * A Router navigation (EXP-011 Tier 2.5). `to` is the target page's **authored** url path,
1090
+ * braces and all, and `pathParams` names the expression each braced segment is filled from —
1091
+ * the emitter builds the concrete url, because it is the only place the values exist.
1092
+ *
1093
+ * ⚠️ `query` is the runtime's *leftover* rule, not a second authored list: `getRelativeURL`
1094
+ * substitutes every `pm-` value whose name appears in the path and appends **whatever is
1095
+ * left** as a query string (`router.tsx:625-633`). So a `pm-sort` on a Navigate whose target
1096
+ * path has no `{sort}` is `?sort=…`, and that is a feature authors use rather than an
1097
+ * accident — it is how a page takes an optional parameter without putting it in the route.
1098
+ */
1099
+ {
1100
+ kind: 'navigate';
1101
+ to: string;
1102
+ pathParams: Array<{
1103
+ name: string;
1104
+ expr: ValueExpr;
1105
+ }>;
1106
+ query: Array<{
1107
+ name: string;
1108
+ expr: ValueExpr;
1109
+ }>;
1110
+ /**
1111
+ * P18-S110-NEWTAB. `Open In New Tab` ticked: router.tsx `navigateAsync` builds the page's
1112
+ * url and calls `window.open(url, '_blank')` — no features string, no blocked test, `Done`
1113
+ * either way. The exported url is the BrowserRouter path the in-place arm would push.
1114
+ */
1115
+ newTab?: true;
1116
+ }
1117
+ /**
1118
+ * `Navigate To Path` (EXP-011 §15, session 44) — a navigation to an **authored path string**
1119
+ * rather than to a page component, and four things about it differ from {@link HandlerAction}'s
1120
+ * `navigate` even though the two nodes look like a pair:
1121
+ *
1122
+ * | | `getRelativeURL` (RouterNavigate) | `navigate()` (this node) |
1123
+ * |---|---|---|
1124
+ * | an unset placeholder | left as the literal `{id}`, **and** `?id=undefined` appended | substituted with `''` |
1125
+ * | encoding | `encodeURIComponent` on every value | none — `String(v)` and `q + '=' + v` |
1126
+ * | the query set | whatever is **left over** after substitution | the authored `Query` list |
1127
+ * | an unset query value | n/a | **omitted from the url** |
1128
+ *
1129
+ * 🔴 **So §11.6's rules do not carry across, and copying them would have been wrong in all
1130
+ * four places.** The unset placeholder is the sharpest: it is deferred one node over precisely
1131
+ * because the Router's three readings of it disagree, and it is *translated* here because this
1132
+ * node's own function is coherent about it.
1133
+ *
1134
+ * {@link to} is normalised to exactly one leading slash. The runtime compares trimmed paths
1135
+ * (`_trimUrlPart`, `router.tsx:39`) so `note/42` and `/note/42` are one route there; in
1136
+ * react-router the first is **relative to the current route** and the second is absolute.
1137
+ *
1138
+ * `then` is the `Done` chain and `completedThen` the `Completed` one, in that order, because
1139
+ * `reportOutcome` sends the outcome and then `Completed` (`node.ts:958-995`).
1140
+ *
1141
+ * 🔴 **`newTab` decides which of two different actions this is** (EXP-011 §17, session 46).
1142
+ * Off, the node is `history.pushState` and cannot fail, so `failThen` is empty and
1143
+ * `completedThen` follows the one outcome there is. On, it is `window.open` — which *can* be
1144
+ * refused — so `Failure` becomes live, `Error` becomes writable, and `Completed` stops being
1145
+ * a continuation of `Done` and becomes a **join beneath both arms**. §15.4 earned `Completed`
1146
+ * on "the gates leave exactly one outcome reachable", and opening this arm is exactly the
1147
+ * change that retires that argument rather than inheriting it.
1148
+ */
1149
+ | {
1150
+ kind: 'navigate-path';
1151
+ /** Needed by every registry keyed on the node — the Error row, and the attachment sweep. */
1152
+ nodeId: string;
1153
+ to: string;
1154
+ pathParams: Array<{
1155
+ name: string;
1156
+ expr: ValueExpr;
1157
+ }>;
1158
+ /** `omittable` values print inside the `if` the runtime's `v !== undefined && v !== null` test describes (P107-D21). */
1159
+ query: Array<{
1160
+ name: string;
1161
+ expr: ValueExpr;
1162
+ omittable: boolean;
1163
+ }>;
1164
+ /** The `const` the omittable form collects into; unused when every value is present. */
1165
+ queryLocal: string;
1166
+ /**
1167
+ * 🔴 **This field means “the new-tab arm is reachable”, not “the port is on”** (EXP-011 §18,
1168
+ * session 47). Every consumer already asks it that question — is `Failure` live, is `Error`
1169
+ * writable, is `Completed` a join, is there an outcome to bind — and a wired port answers
1170
+ * all four the same way an authored `true` does, because the tab can still be refused.
1171
+ * What a wire adds is that the *other* arm is reachable too, and that is {@link newTabExpr}
1172
+ * rather than a second reading of this one.
1173
+ *
1174
+ * ⚠️ **`default: false` here and `default: true` on `External Link`** — the two nodes look
1175
+ * like a pair and their unset state lands on opposite sides.
1176
+ */
1177
+ newTab: boolean;
1178
+ /**
1179
+ * The wired `Open In New Tab`, as the expression the emitted handler branches on — set
1180
+ * **only** where the port carries a wire, which is the only way both of this node's two
1181
+ * actions become reachable in one handler (EXP-011 §18).
1182
+ *
1183
+ * 🔴 **The port is answerable because the runtime reads it exactly once, as `!!value`**
1184
+ * (`navigate-to-path.ts:89`, an input `set` that stores a boolean), and `navigate` then
1185
+ * reads that stored boolean (`:204`). ⚠️ `External Link`'s identically-named port is read
1186
+ * **two different ways in two adjacent lines** — truthiness for the features string and
1187
+ * strict equality for the target — which is why a wire defers there and translates here.
1188
+ * The two nodes are not a pair on this port either.
1189
+ *
1190
+ * It is a truthiness sink, so a logic truth value lands here rather than deferring on the
1191
+ * standing rule: the emitted read is the `if` test itself, which coerces exactly as `!!`
1192
+ * does. Read **once**, at the top of the handler, so there is no local to bind it to.
1193
+ */
1194
+ newTabExpr?: ValueExpr;
1195
+ /**
1196
+ * The `const` the `window.open` result binds to. Emitted only where something branches on
1197
+ * it: a new tab with no chains and no Error row is the bare call the graph asked for.
1198
+ */
1199
+ openedLocal: string;
1200
+ /**
1201
+ * The `Error` output as a state row, allocated **by the read** (§14.2's rule, fifth
1202
+ * instance). Only ever set for `newTab`: in-tab, neither of the node's two failures can
1203
+ * fire, so there is no read to earn it.
1204
+ */
1205
+ errorState?: string;
1206
+ /**
1207
+ * The `const` the failure arm binds the message to, set only where something inside that
1208
+ * arm reads `Error` (EXP-011 §24). Minted by the read, like the row above it — the
1209
+ * commonest node here reads neither and emits neither.
1210
+ */
1211
+ errorLocal?: string;
1212
+ then: HandlerAction[];
1213
+ /** The `Failure` chain — reachable only for `newTab`, where the browser can refuse. */
1214
+ failThen: HandlerAction[];
1215
+ completedThen: HandlerAction[];
1216
+ }
1217
+ /**
1218
+ * `External Link` (EXP-011 Tier 2.5) — `window.open`, plus the two outcome chains the
1219
+ * runtime's own fork owes (`externallink.ts`).
1220
+ *
1221
+ * The emitted shape is the interpreter's, guard for guard:
1222
+ *
1223
+ * ```
1224
+ * const link = donateUrl; // bound once — the guard and the open must
1225
+ * if (link === undefined || link === null || link === '') { …failThen } // read one value
1226
+ * else if (!window.open(link, '_blank', 'noopener,noreferrer')) { …failThen }
1227
+ * else { …then }
1228
+ * ```
1229
+ *
1230
+ * 🔴 **`Open In New Tab` unset is `true`, not `undefined`.** The port declares `default: true`
1231
+ * and `registerInput` writes a declared default straight into `_inputValues` (`node.ts:138`),
1232
+ * so `getInputValue` never sees the `undefined` its own target expression tests for — which is
1233
+ * what makes {@link newTab} a plain boolean here rather than a third state.
1234
+ *
1235
+ * That question is worth asking because the runtime reads this one input **two different ways
1236
+ * in two adjacent lines**: `params` is truthiness (`openInNewTab ? … : ''`) and `target` is
1237
+ * strict equality (`=== true || === undefined`). They agree for `true`, for `false` and for
1238
+ * the unset port — every value the *editor* can put there — and disagree for every other
1239
+ * truthy value, which only a wire can deliver. So a wired `Open In New Tab` defers, and the
1240
+ * three cases that remain are the three an author can actually author.
1241
+ *
1242
+ * ⚠️ **There is no `unchanged` arm and its absence is a decision.** The runtime reports
1243
+ * `unchanged` when `typeof window === 'undefined'` — a server-side render. The scaffold
1244
+ * mounts with `createRoot` (`scaffold.ts:249`) and has no server pass at all, so that chain
1245
+ * is unreachable in the exported app exactly as it is in the app running in a browser. The
1246
+ * wire is dropped with a note rather than deferring the node, on `Clear Array`'s rule: a wire
1247
+ * already dead in the interpreter must not cost a translation.
1248
+ */
1249
+ | {
1250
+ kind: 'external-link';
1251
+ /** The node, so the attachment pass and the late row sweep can find it (§14). */
1252
+ nodeId: string;
1253
+ link: ValueExpr;
1254
+ /** `Open In New Tab` — resolved here, because only an unwired port reaches this action. */
1255
+ newTab: boolean;
1256
+ /**
1257
+ * The chain-local the link binds to, emitted only where the empty-link guard needs it —
1258
+ * the guard and the `window.open` must read one value, not two evaluations of one
1259
+ * expression (a link read from a Variable can change between them).
1260
+ */
1261
+ local: string;
1262
+ /**
1263
+ * The local the blocked-tab test binds to, emitted only for a new tab whose outcome is
1264
+ * read (EXP-011 §14).
1265
+ *
1266
+ * 🔴 **The binding is the correctness, and it is not a tidiness choice.** `window.open`
1267
+ * **consumes** the transient activation — measured in Chrome 151, one control arm varying
1268
+ * only whether the call sits between two reads of `navigator.userActivation.isActive`:
1269
+ * without it both reads are `true`, with it the second is `false` while a tab opens. So
1270
+ * the emitted test had to move *before* the call, exactly where the runtime reads it
1271
+ * (`externallink.ts:96`). Read after, it is `false` on every tab successfully opened —
1272
+ * which is DEF-016's own symptom, reintroduced on the export side by DEF-016's fix.
1273
+ */
1274
+ blockedLocal: string;
1275
+ /**
1276
+ * False only where the link is provably non-empty — a non-empty literal. The runtime
1277
+ * refuses before touching the browser on `undefined`, `null` and `''`, and `window.open('')`
1278
+ * opens a blank tab, so this guard is observable even with no Failure chain wired.
1279
+ */
1280
+ guardLink: boolean;
1281
+ /**
1282
+ * The `Error` output as a state row (EXP-011 §14), filled by the late sweep and undefined
1283
+ * where nothing reads it — which is the ordinary case, and why a button opening a literal
1284
+ * url still emits one expression and no `useState`.
1285
+ *
1286
+ * 🔴 **This is the field that makes the two failures tell themselves apart.** Everywhere
1287
+ * else in this action they are one arm, because "no link" and "the browser blocked the
1288
+ * tab" both run the Failure chain and nothing else. `Error` is the one port that
1289
+ * distinguishes them — two static strings, `externallink.ts:62` and `:101` — so the
1290
+ * emitted failure arm re-tests the link to pick the message. Both strings are the node's
1291
+ * own `_internal.lastError`, which is the SHORT one; the longer sentence beside it goes to
1292
+ * the outcome channel, not to this port.
1293
+ */
1294
+ errorState?: string;
1295
+ /**
1296
+ * The `const` the failure arm binds the message to (EXP-011 §24), set only where something
1297
+ * inside that arm reads `Error`.
1298
+ *
1299
+ * 🔴 **It is what stops the message being computed twice.** The write above picks between
1300
+ * two strings by re-testing the link; a chain reading `Error` would otherwise have to print
1301
+ * that same ternary again at every sink, and the two copies would be free to disagree.
1302
+ */
1303
+ errorLocal?: string;
1304
+ then: HandlerAction[];
1305
+ failThen: HandlerAction[];
1306
+ } | {
1307
+ kind: 'emit';
1308
+ channelName: string;
1309
+ payload: Array<{
1310
+ key: string;
1311
+ expr: ValueExpr;
1312
+ }>;
1313
+ }
1314
+ /**
1315
+ * EXP-011 §74. `fallback`: the value typed into the Set's own port UNDER the wire `expr` came from, kept only when
1316
+ * `expr` may read `undefined` — the runtime writes the wire's last defined delivery, else the typed-in value
1317
+ * (measured: `exp-011-s96-a-literal-under-a-wire.test.ts`), so the handler prints `expr ?? fallback`. Absent when
1318
+ * nothing is typed in, or when the wire's source always carries a value (the typed-in value is then never written,
1319
+ * in the runtime or here — a note says so).
1320
+ *
1321
+ * P18-S127-SETDONE (EXP-011 §95). `then`: the Set's Done chain, then its Completed chain, as following statements
1322
+ * (`expandActions`), present only when something hangs off them. The runtime writes, notifies every Variable reading
1323
+ * the name, then sends Done and Completed (`setvariablenode.ts` `scheduleStore`), and a Done's target is updated before
1324
+ * the click's next wire (it is flagged dirty with its sender and pulls it first): depth first, as printed.
1325
+ */
1326
+ | {
1327
+ kind: 'store-set';
1328
+ variableName: string;
1329
+ expr: ValueExpr;
1330
+ fallback?: string | number | boolean;
1331
+ then?: HandlerAction[];
1332
+ } | {
1333
+ kind: 'globalstore-set';
1334
+ storeName: string;
1335
+ key: string;
1336
+ expr: ValueExpr;
1337
+ fallback?: string | number | boolean;
1338
+ }
1339
+ /**
1340
+ * `Set Object Properties` on a named Object (EXP-011 §47): one patch over every wired property
1341
+ * the node's list admits — `profile.set({ name: …, city: … })` — then its Done chain as
1342
+ * following statements. No failure arm: with a literal Id the runtime's only failure (no
1343
+ * object bound) is unreachable, because `Model.get` creates the object on read.
1344
+ */
1345
+ | {
1346
+ kind: 'object-set';
1347
+ storeName: string;
1348
+ entries: Array<{
1349
+ key: string;
1350
+ expr: ValueExpr;
1351
+ fallback?: string | number | boolean;
1352
+ }>;
1353
+ then: HandlerAction[];
1354
+ }
1355
+ /**
1356
+ * EXP-011 §60. `Set Component Object Properties` (`parent: false`) — `<local>.set({ … })`, then Done, then Completed, as
1357
+ * following statements (one arm, always taken: the self variant cannot miss its own record). `Set Parent Component
1358
+ * Object Properties` (`parent: true`) — the block form: `if (<local> === undefined) { raise no-ancestor; failThen }
1359
+ * else { <local>.set({ … }); then }` — the runtime's `resolution.id === undefined` fork in base.ts, verbatim.
1360
+ */
1361
+ | {
1362
+ kind: 'component-object-set';
1363
+ nodeId: string;
1364
+ local: string;
1365
+ parent: boolean;
1366
+ entries: Array<{
1367
+ key: string;
1368
+ expr: ValueExpr;
1369
+ }>;
1370
+ then: HandlerAction[];
1371
+ completedThen: HandlerAction[];
1372
+ failThen: HandlerAction[];
1373
+ } | {
1374
+ kind: 'collection-add';
1375
+ collectionName: string;
1376
+ entries: Array<{
1377
+ key: string;
1378
+ expr: ValueExpr;
1379
+ }>;
1380
+ minted?: MintedTarget;
1381
+ }
1382
+ /**
1383
+ * `Clear Array` (EXP-011 Tier 1.1) — `notes.clear()`, plus the two chains the runtime's own
1384
+ * outcome fork owes.
1385
+ *
1386
+ * `collectionnode-clear.ts` measures `wasEmpty` **before** `set([])` and reports `unchanged`
1387
+ * for an array that was already empty, `done` otherwise (ERG-001: "the post-condition already
1388
+ * held"). So a `done` chain emitted unconditionally would fire where the interpreter stays
1389
+ * silent. The emitted form tests the length instead of keeping a local, which is exact rather
1390
+ * than merely close: `Collection.clear()` is itself `if (length === 0) return`, so skipping
1391
+ * the call on the empty arm is the same no-op the guard would have made.
1392
+ */
1393
+ | {
1394
+ kind: 'collection-clear';
1395
+ collectionName: string;
1396
+ then: HandlerAction[];
1397
+ unchangedThen: HandlerAction[];
1398
+ minted?: MintedTarget;
1399
+ }
1400
+ /**
1401
+ * `Remove Object From Array` (EXP-011 §30) — `notes.remove(item)`, inside the row callback of
1402
+ * the repeater named by {@link repeaterId}.
1403
+ *
1404
+ * 🔴 **This action is only meaningful in one place, and the type says so.** The node's Object
1405
+ * Id is a `For Each`'s `itemActionItemId`, which the runtime sets to `model.getId()` of the
1406
+ * row that fired *immediately before* pulsing `itemOutputSignal-<name>` (`foreach.tsx`). Read
1407
+ * anywhere else it is "whichever row fired last", which the emitted list holds no state for —
1408
+ * so `actionsValidIn` refuses this action outside that repeater's own row signal, and the row
1409
+ * the interpreter names becomes the `item` the callback already closes over.
1410
+ *
1411
+ * There is no `unchangedThen` and no failure arm because in this shape neither can fire, and
1412
+ * the compile proves it rather than assuming it: see `compileCollectionRemove`.
1413
+ */
1414
+ | {
1415
+ kind: 'collection-remove';
1416
+ collectionName: string;
1417
+ repeaterId: string;
1418
+ then: HandlerAction[];
1419
+ minted?: MintedTarget;
1420
+ }
1421
+ /**
1422
+ * A Condition in a handler chain: `if (cond) whenTrue; else whenFalse;` (LOGIC-TARGET §3). `flat` (EXP-011 §87): a
1423
+ * Condition whose answer is a literal and which writes its sinks' cells has one side; its `cond` is `true`, its
1424
+ * `whenFalse` empty, and `whenTrue` prints as following statements.
1425
+ */
1426
+ | {
1427
+ kind: 'branch';
1428
+ cond: ValueExpr;
1429
+ whenTrue: HandlerAction[];
1430
+ whenFalse: HandlerAction[];
1431
+ flat?: true;
1432
+ }
1433
+ /** Fires the component's own signal output: `onWaved?.()` (COMPONENT-OUTPUTS-TARGET §4). */
1434
+ | {
1435
+ kind: 'output-signal';
1436
+ prop: string;
1437
+ }
1438
+ /**
1439
+ * Opens a popup slot: `setOpenPopup('AboutDialog')`, then the Show Popup's `done`-chain as
1440
+ * following statements in the same handler (POPUPS-TARGET §3).
1441
+ */
1442
+ | {
1443
+ kind: 'popup-show';
1444
+ slotKey: string;
1445
+ then: HandlerAction[];
1446
+ }
1447
+ /**
1448
+ * Closes the enclosing popup through the reserved prop (POPUPS-TARGET §4): with a
1449
+ * `done`-chain, `if (onClose) { onClose('ok'); …then }`; without one, `onClose?.('ok')`.
1450
+ * `action` undefined is the plain `Close` (the runtime's `Closed` outcome).
1451
+ */
1452
+ | {
1453
+ kind: 'popup-close';
1454
+ action?: string;
1455
+ then: HandlerAction[];
1456
+ }
1457
+ /**
1458
+ * A pure Function/Expression fired from a handler chain (EXP-003 §4 A2h): the actions its
1459
+ * `done` wires described, in wire order. The node's own compute needs no statement — output
1460
+ * reads inside the chain inline the call at their sinks, and a pure body run without reading
1461
+ * its outputs is unobservable. `done` is invocation-only in both runtimes, so this is exact.
1462
+ * `materialize` (CONTROLLED-STATE-TARGET §4f) names the state var the run writes when the
1463
+ * node's outputs also feed render sinks: `setStockCheckOut(stockCheck({ … }))` precedes the
1464
+ * chain, and render reads go through the var.
1465
+ * `ticked` (P18-S119-TICKEDRUN) marks the run a ticked input's new value starts (`TickedRunPlan`): it has no Done, and
1466
+ * its effect has already recorded the values it read.
1467
+ */
1468
+ | {
1469
+ kind: 'jsfun-run';
1470
+ nodeId: string;
1471
+ then: HandlerAction[];
1472
+ materialize?: string;
1473
+ ticked?: true;
1474
+ /**
1475
+ * EXP-019 slice 1b (session 134). The body awaits at its top level: where in `then` the chain the runtime sends after
1476
+ * the run's promise settles begins (Success, then Done), after the chains of the signals the body sent before its
1477
+ * first `await`. Printed whole (`runFunction`, the pulses, `await <run>.settled`, the rest) and awaited like a request,
1478
+ * so §90's `detached` lets the click's later wires run before it settles.
1479
+ */
1480
+ awaitsAt?: number;
1481
+ /** EXP-011 §90, as an api call's. */
1482
+ detached?: true;
1483
+ }
1484
+ /**
1485
+ * EXP-011 §86. The chains of a Function's own signals (`Outputs.go()`), first in its run's `then`: one loop over the
1486
+ * pulses the run sent, in the order it sent them, each arm (`jsfun-pulse`) the chain of one signal. Inside an arm the
1487
+ * Function's outputs read as that pulse's targets read them (C7 lockstep, `src/lib/functionRun.ts`).
1488
+ */
1489
+ | {
1490
+ kind: 'jsfun-pulses';
1491
+ nodeId: string;
1492
+ then: HandlerAction[];
1493
+ }
1494
+ /** EXP-011 §86. One signal's chain inside `jsfun-pulses`: the actions its wires describe, in wire order. */
1495
+ | {
1496
+ kind: 'jsfun-pulse';
1497
+ nodeId: string;
1498
+ signal: string;
1499
+ then: HandlerAction[];
1500
+ }
1501
+ /**
1502
+ * A state var write (CONTROLLED-STATE-TARGET §3.3). `op` is a functional update
1503
+ * (`setX(v => !v)`) — immune to closure staleness, which is why Switch's `flip` and
1504
+ * Counter's arithmetic use it, never `expr`.
1505
+ */
1506
+ | {
1507
+ kind: 'state-set';
1508
+ name: string;
1509
+ expr?: ValueExpr;
1510
+ op?: 'toggle' | 'inc' | 'dec';
1511
+ field?: FieldWrite;
1512
+ }
1513
+ /**
1514
+ * P18-S128-FIELDSET (EXP-011 §97). A text field's Focus or Blur: `focus()` / `blur()` on the exported `<input>` through a
1515
+ * ref, as the runtime's focus tracker calls them on the viewer's own (`_focus`, `_blur`). A field that is not on the page
1516
+ * takes nothing, and the Focus is not held (GAM-012 R13): the ref is `null` then.
1517
+ */
1518
+ | {
1519
+ kind: 'field-focus';
1520
+ inputId: string;
1521
+ focus: boolean;
1522
+ }
1523
+ /**
1524
+ * An awaited call into an api stub (RECORD-VERBS-TARGET §4, USER-FAMILY-TARGET §4e): the
1525
+ * vocabulary's asynchronous action. The emitted handler becomes `async`, the call is awaited,
1526
+ * `then` is the `done` chain — which is where `reportOutcomes(…, 'done')` sits in the runtime,
1527
+ * after the service answers — and the catch writes `errorState`, the node's `Error` output,
1528
+ * which the runtime never clears once set.
1529
+ *
1530
+ * ⚠️ **One kind, two families.** The record verbs and the user family emit the *same* action;
1531
+ * they differ only in `fnName` and in the shape of `args`. USER-FAMILY §4e records why this is
1532
+ * a rename of `record-op` rather than a sibling case beside it: a new discriminant recruits
1533
+ * every switch site silently and the wrong ones fail without throwing (s19's dispatcher trap),
1534
+ * whereas a rename is a compile error at each site until it is handled.
1535
+ */
1536
+ | {
1537
+ kind: 'api-call';
1538
+ /**
1539
+ * EXP-011 §90. Set by the planner's last pass where a later action of the same list follows it: printed as
1540
+ * `void (async () => { … })();`, so the click's later wires run before the answer arrives, as the runtime's do
1541
+ * (each node acts after the update; the request answers later). A failure before the first await (the
1542
+ * `Missing Record Id` guard) still runs its chain first, as the runtime's synchronous `setError` does.
1543
+ */
1544
+ detached?: true;
1545
+ nodeId: string;
1546
+ /** What produced the call — for notes and the stub's provenance line. EXP-011 §62: the relation pair rides the same action. */
1547
+ verb: 'create' | 'update' | 'delete' | RelationVerb | UserVerb;
1548
+ /** The api module's exported function: `createPuppy` / `updatePuppy` / `logIn` / `logOut`. */
1549
+ fnName: string;
1550
+ /**
1551
+ * The call's arguments, in position order. `updatePuppy(id, {…})` is `[expr, data]`,
1552
+ * `logIn(u, p)` is `[expr, expr]`, `logOut()` is `[]`.
1553
+ */
1554
+ args: ApiCallArg[];
1555
+ /**
1556
+ * Refuse before calling when the leading id argument is absent or empty, the way the
1557
+ * runtime does (RECORD-VERBS-TARGET §1): `setModelID` reads `undefined`/`null`/`''` as
1558
+ * *clear the binding*, after which every verb answers `setError('Missing Record Id')` and
1559
+ * never reaches the backend. Gate 9 already defers an id that is **statically** absent;
1560
+ * this is its dynamic twin, and the corpus reaches it because every emitted component prop
1561
+ * is optional, so an id wired from one is `string | undefined` at the call.
1562
+ *
1563
+ * False only where the guard is provably dead — a non-empty literal id, and every `create`,
1564
+ * which takes no id at all.
1565
+ */
1566
+ guardId: boolean;
1567
+ /**
1568
+ * EXP-011 §72. The id argument is read from a source typed `unknown` — a Variable nothing
1569
+ * statically writes, or one written by a Function or an HTTP body — and the client's
1570
+ * parameter is `string`: the emitter prints `String(x ?? '')` around the read. The runtime
1571
+ * keys `Model.get(id)` by the value and the client `encodeURIComponent`s it, so the
1572
+ * coercion transcribes both; an empty read still meets `guardId`'s `Missing Record Id`.
1573
+ * Absent for a `string` source — a `string | undefined` narrows on its own.
1574
+ */
1575
+ coerceId?: 'string';
1576
+ /**
1577
+ * EXP-011 §62. The relation verbs' dynamic pre-flight, in the runtime's own order and its own
1578
+ * words (`validateInputs`): a positional argument that is empty at the click throws that
1579
+ * sentence into the catch — *No target record Id (the record to add a relation to) specified*
1580
+ * before *No record Id specified (the record that should get the relation)*, because the
1581
+ * runtime asks about the target first. Printed after `guardId`'s line; emitted only for an
1582
+ * argument that is not a literal (a literal is never empty — the planner refused that).
1583
+ */
1584
+ guards?: Array<{
1585
+ index: number;
1586
+ local: string;
1587
+ message: string;
1588
+ coerce?: 'string';
1589
+ }>;
1590
+ errorState: string;
1591
+ /**
1592
+ * EXP-011 §44 — write `undefined` to the Error row the moment the call answers, before the
1593
+ * done chain: `Request Magic Link` is the one verb whose success clears its Error. Every
1594
+ * other `api-call` keeps the runtime's "never cleared" (RECORD-VERBS-TARGET §1).
1595
+ */
1596
+ clearErrorOnDone?: true;
1597
+ then: HandlerAction[];
1598
+ /**
1599
+ * EXP-011 §90. The `failure` chain, printed in the catch after the Error row and the raise: the runtime's
1600
+ * `setError` writes the row, raises, then pulses Failure (`node.ts` `reportOutcome`). A read of this node's Error
1601
+ * inside it is the catch's `<errorState>Message` local (`outcome-error`), never the row.
1602
+ */
1603
+ failThen: HandlerAction[];
1604
+ }
1605
+ /**
1606
+ * An `HTTP Request`'s `Fetch` (EXP-011 Tier 1.2) — the vocabulary's second asynchronous action,
1607
+ * and the first whose *failure* is a chain rather than only a value.
1608
+ *
1609
+ * The emitted shape is the runtime's own three outcomes, not two:
1610
+ *
1611
+ * ```
1612
+ * try {
1613
+ * const answer = await fetchQuote({ … }); // throws only where no answer arrived
1614
+ * setQuoteOut(answer); // response/statusCode/headers/fields — set for ANY answer
1615
+ * if (answer.ok) { …then }
1616
+ * else { const message = answer.error; setQuoteError(message); …failThen }
1617
+ * } catch (error) {
1618
+ * const message = error instanceof Error ? error.message : String(error);
1619
+ * setQuoteError(message); …failThen
1620
+ * }
1621
+ * ```
1622
+ *
1623
+ * 🔴 **A non-2xx still writes the value outputs, and a network error still does not.**
1624
+ * `processResponse` runs before `doFetch` reports `failure`, so a 404's body and status are on
1625
+ * the node's outputs while `Failure` fires; a request that never reached the server leaves
1626
+ * `Response` and `Status Code` holding exactly what they held. Splitting the answer from the
1627
+ * throw is what reproduces both — the record verbs' single try/catch could not, because for
1628
+ * them a refused write has nothing to publish.
1629
+ *
1630
+ * `failThen` is therefore emitted **twice**, once per arm, and the two arms are identical by
1631
+ * construction because both bind `message` first. A join would need a `finally` that can tell
1632
+ * which arm it is in, which is a variable the arms already are.
1633
+ */
1634
+ | {
1635
+ kind: 'http-call';
1636
+ /** EXP-011 §90, as `api-call`'s. */
1637
+ detached?: true;
1638
+ nodeId: string;
1639
+ /** `src/api/http.ts`'s exported function for this node. */
1640
+ fnName: string;
1641
+ /** Wired inputs, in the emitted parameter order — `{ tag: tagText }` at the call. */
1642
+ args: Array<{
1643
+ param: string;
1644
+ expr: ValueExpr;
1645
+ }>;
1646
+ /** The state row the answer is written to, when anything outside the chain reads a value. */
1647
+ materialize?: string;
1648
+ /** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
1649
+ errorState: string;
1650
+ then: HandlerAction[];
1651
+ failThen: HandlerAction[];
1652
+ }
1653
+ /**
1654
+ * A `Cloud Function`'s `Call` (EXP-011 §41) — the request's shape one hop in: the function
1655
+ * throws where the interpreter reports Failure (an answered error or an unreachable backend),
1656
+ * so the two outcome chains are a try/catch's two arms.
1657
+ *
1658
+ * ```
1659
+ * try {
1660
+ * const answer = await callPublishPage({ pageId }); // throws on any failure
1661
+ * setPublishOut(answer); // only where something outside the chain reads a result
1662
+ * …then
1663
+ * } catch (error) {
1664
+ * const message = error instanceof Error ? error.message : String(error);
1665
+ * setPublishError(message); …failThen
1666
+ * }
1667
+ * ```
1668
+ */
1669
+ | {
1670
+ kind: 'cloud-call';
1671
+ /** EXP-011 §90, as `api-call`'s. */
1672
+ detached?: true;
1673
+ nodeId: string;
1674
+ /** `src/api/functions.ts`'s exported function for this node. */
1675
+ fnName: string;
1676
+ /** Wired parameters, in the emitted parameter order — `{ pageId: pageIdValue }` at the call. */
1677
+ args: Array<{
1678
+ param: string;
1679
+ expr: ValueExpr;
1680
+ }>;
1681
+ /** The results row, when anything outside the chain reads a result. */
1682
+ materialize?: string;
1683
+ /** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
1684
+ errorState: string;
1685
+ then: HandlerAction[];
1686
+ failThen: HandlerAction[];
1687
+ }
1688
+ /**
1689
+ * A `Record`'s `Fetch` (EXP-011 §43) — the Cloud Function shape with one argument, the Id:
1690
+ *
1691
+ * ```
1692
+ * try {
1693
+ * const pageRecord = await fetchPageById(pageId.get());
1694
+ * setPageRow(pageRecord); // only where something outside the chain reads a column
1695
+ * …then
1696
+ * } catch (error) {
1697
+ * const message = error instanceof Error ? error.message : String(error);
1698
+ * setPageError(message); …failThen
1699
+ * }
1700
+ * ```
1701
+ * An empty Id throws `Missing Id.` inside the try — the runtime's own sentence, reported as
1702
+ * Failure before any request (`scheduleFetch`) — so it lands in the catch like every other failure.
1703
+ */
1704
+ | {
1705
+ kind: 'record-fetch';
1706
+ /** EXP-011 §90, as `api-call`'s. */
1707
+ detached?: true;
1708
+ nodeId: string;
1709
+ /** The collection module's exported `fetch<Type>ById`. */
1710
+ fnName: string;
1711
+ id: ValueExpr;
1712
+ /** EXP-011 §72. `id` is typed `unknown` — printed `String(id ?? '')`, as `api-call`'s `coerceId`. */
1713
+ coerceId?: 'string';
1714
+ /** The record row, when anything outside the chain reads a column. */
1715
+ materialize?: string;
1716
+ /** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
1717
+ errorState: string;
1718
+ then: HandlerAction[];
1719
+ failThen: HandlerAction[];
1720
+ }
1721
+ /**
1722
+ * `Open File Picker`'s `Open` (EXP-011 §45) — `pickFile()` from `src/lib/util.ts`, which is the
1723
+ * node's `<input type=file>` and its two listeners as one promise: a `File` on `change` with a
1724
+ * file, `undefined` on `change` with none or on `cancel`, a rejection where `click()` threw.
1725
+ *
1726
+ * ```
1727
+ * try {
1728
+ * const picked = await pickFile({ accept: 'image/*' });
1729
+ * if (picked === undefined) { …unchangedThen }
1730
+ * else { setPickedFile(picked); …then }
1731
+ * } catch (error) {
1732
+ * const message = error instanceof Error ? error.message : String(error);
1733
+ * setPickError(message); …failThen
1734
+ * }
1735
+ * ```
1736
+ * The row is written only on Done — Unchanged leaves every output exactly as it was, which is
1737
+ * the port's definition, and Failure writes only the Error (openfilepicker.ts).
1738
+ */
1739
+ | {
1740
+ kind: 'file-pick';
1741
+ /** EXP-011 §90, as `api-call`'s. */
1742
+ detached?: true;
1743
+ nodeId: string;
1744
+ accept?: ValueExpr;
1745
+ capture?: ValueExpr;
1746
+ /** The chosen-file row, when anything outside the Done chain reads the file or its metadata. */
1747
+ materialize?: string;
1748
+ errorState: string;
1749
+ then: HandlerAction[];
1750
+ unchangedThen: HandlerAction[];
1751
+ failThen: HandlerAction[];
1752
+ }
1753
+ /**
1754
+ * `Upload File`'s `Upload` (EXP-011 §45) — the Cloud Function shape with the file as the one
1755
+ * argument: `uploadFile(file, { private })` from `src/api/files.ts`, which is the client's
1756
+ * `POST /files/<name>` with the bytes as the body and `X-NodeGX-File-Private` where asked.
1757
+ * `No file specified` is thrown inside the try where the file is unset — the node's own
1758
+ * sentence, reported as Failure before any request (uploadfile.ts).
1759
+ */
1760
+ | {
1761
+ kind: 'file-upload';
1762
+ /** EXP-011 §90, as `api-call`'s. */
1763
+ detached?: true;
1764
+ nodeId: string;
1765
+ file: ValueExpr;
1766
+ /** `Private` — absent where unset or authored false; a literal `true` or the wired expression otherwise. */
1767
+ isPrivate?: ValueExpr;
1768
+ materialize?: string;
1769
+ errorState: string;
1770
+ then: HandlerAction[];
1771
+ failThen: HandlerAction[];
1772
+ }
1773
+ /**
1774
+ * `Sign File URL`'s `Sign` (EXP-011 §45) — `signFileUrl(file)` from `src/api/files.ts`, the
1775
+ * client's `GET /files/<name>/sign`; `No file specified` thrown for an unset file, as above.
1776
+ */
1777
+ | {
1778
+ kind: 'file-sign';
1779
+ /** EXP-011 §90, as `api-call`'s. */
1780
+ detached?: true;
1781
+ nodeId: string;
1782
+ file: ValueExpr;
1783
+ materialize?: string;
1784
+ errorState: string;
1785
+ then: HandlerAction[];
1786
+ failThen: HandlerAction[];
1787
+ }
1788
+ /**
1789
+ * `Now`'s `Read` (EXP-011 Tier 1.3) — re-read the clock, then run the `done` chain.
1790
+ *
1791
+ * ```
1792
+ * const nowRead = new Date(); // bound, so every read in the chain is the SAME instant
1793
+ * setNow(nowRead); // only where something outside the chain reads it
1794
+ * …then
1795
+ * ```
1796
+ *
1797
+ * Synchronous, so `done` is the statements that follow rather than an arm: `_read` flags the
1798
+ * three outputs dirty and reports `done` in the same call, with no branch where it declines.
1799
+ * There is deliberately no failure arm — the node's own comment says reading the clock cannot
1800
+ * fail, and two Reads in the same millisecond still re-read.
1801
+ *
1802
+ * `materialize` is filled in the verdict sweep, never at compile time: a render read of
1803
+ * `Timestamp` resolves passes later, so asking "does anything read this" here answers *no* for
1804
+ * every render read there is (§8.3's allocation-order rule, third instance).
1805
+ */
1806
+ | {
1807
+ kind: 'date-now-read';
1808
+ nodeId: string;
1809
+ /** The chain-local the instant binds to — what a read inside `then` says. */
1810
+ local: string;
1811
+ /** The state row the instant is written to, where anything outside the chain reads it. */
1812
+ materialize?: string;
1813
+ then: HandlerAction[];
1814
+ } | IdNewAction | CryptoCallAction | StackPushAction | StackPopAction | StackResetAction | ArrayNewAction | LogAction | DelayAction | StatesGoAction | DragSnapAction | ScriptSignalAction | RunTasksAction | QueryFetchAction | StreamAction;
1815
+ /**
1816
+ * A streaming node's action port pulsed (EXP-011 §58) — `<local>.<verb>(<data>)`. `value` is the data port's
1817
+ * expression, read AT THE PULSE (the runtime appends the last delivered Chunk / Data when the pulse lands, and
1818
+ * the chain that pulses may have written it a statement earlier — `runtasks-run`'s rule); absent when the port
1819
+ * has no source, in which case the call takes no argument and the setter never ran. The node's signal outputs
1820
+ * are its listeners ({@link StreamPlan.listeners}), passed once to the hook.
1821
+ */
1822
+ export type StreamAction = {
1823
+ kind: 'stream-action';
1824
+ nodeId: string;
1825
+ local: string;
1826
+ verb: string;
1827
+ value?: ValueExpr;
1828
+ };
1829
+ /**
1830
+ * A `States` node's `Toggle` or `To <state>` (EXP-011 §49) — one call on the `useStates` handle:
1831
+ * `<local>.toggle()` or `<local>.goTo('<state>')`.
1832
+ *
1833
+ * ⚠️ **No chains ride on the action, and that is the node's own shape, not a shortcut.** Done,
1834
+ * Unchanged and Failure are *ports of the node*, pulsed by `reportOutcome` whichever trigger asked
1835
+ * — a chain wired off `done` fires for a Toggle and for every `To <state>` alike — so they are
1836
+ * listeners on the node ({@link StatesPlan.listeners}), compiled once, not arms printed at each
1837
+ * call site. The interpreter drains requests after the pass, so the outcome arrives from a
1838
+ * microtask; the handler that asked has already finished, exactly as it has there.
1839
+ */
1840
+ export type StatesGoAction = {
1841
+ kind: 'states-go';
1842
+ nodeId: string;
1843
+ /** The `useStates` local. */
1844
+ local: string;
1845
+ verb: 'toggle' | 'to';
1846
+ /** The state a `To <state>` names; absent for Toggle. */
1847
+ state?: string;
1848
+ };
1849
+ /**
1850
+ * A `Drag`'s Snap To Position X / Y — Do (EXP-011 §63): one call on the handle, always an expression. Its Done is the
1851
+ * node's listener — `outcomeOnInnerComponent` reports `done` for every snap call whichever port asked — so it is passed
1852
+ * once to the hook, not printed as an arm here; Failure cannot fire in the emitted app and is dropped with a note.
1853
+ */
1854
+ export type DragSnapAction = {
1855
+ kind: 'drag-snap';
1856
+ nodeId: string;
1857
+ /** The `useDrag` local. */
1858
+ local: string;
1859
+ axis: 'x' | 'y';
1860
+ };
1861
+ /**
1862
+ * A `Log`'s `Log` (EXP-011 §39) — `log(level, message, data)` into `src/lib/util.ts`, then the
1863
+ * Done chain as following statements (`popup-show`'s treatment: the write is synchronous and has
1864
+ * one outcome, so the chain is not an arm).
1865
+ *
1866
+ * `message` is `undefined` where nothing is authored or wired — the helper prints `''` for it,
1867
+ * as `_write` does. `data` is absent (not `undefined`) where the port is unset, because the
1868
+ * runtime writes the data argument only when one arrived, and `console.info('x', undefined)`
1869
+ * prints a trailing `undefined` the interpreter never prints.
1870
+ */
1871
+ export type LogAction = {
1872
+ kind: 'log';
1873
+ nodeId: string;
1874
+ level: LogLevel;
1875
+ message: ValueExpr;
1876
+ data?: ValueExpr;
1877
+ then: HandlerAction[];
1878
+ };
1879
+ /**
1880
+ * One of a `Delay`'s three actions (EXP-011 §39) — `startDelay`/`restartDelay`/`stopDelay` from
1881
+ * `src/lib/timer.ts` over a `useRef` the component declares per node.
1882
+ *
1883
+ * Four chains, and which ones an arm prints depends on the verb:
1884
+ *
1885
+ * | verb | `then` (Done) | `unchangedThen` | `startedThen` / `finishedThen` |
1886
+ * |---|---|---|---|
1887
+ * | `start` | began a countdown | one was already running | passed as the two callbacks |
1888
+ * | `restart` | always | never — Restart cannot no-op (timer.ts) | passed as the two callbacks |
1889
+ * | `stop` | abandoned a running countdown | nothing was running | not this verb's — a stopped countdown never fires them |
1890
+ *
1891
+ * `startDelay` and `duration` are read where the handler is, so a `Set Variable` earlier in the
1892
+ * chain reaches them (the snapshot rule). The two callback chains run *later*, in a timeout, and
1893
+ * close over the same handler scope — the shape `http-call`'s continuations already have.
1894
+ */
1895
+ export type DelayAction = {
1896
+ kind: 'delay';
1897
+ nodeId: string;
1898
+ verb: TimerTrigger;
1899
+ /** The `useRef` local this node's timer handle lives in — one per node, shared by its verbs. */
1900
+ ref: string;
1901
+ startDelay: ValueExpr;
1902
+ duration: ValueExpr;
1903
+ then: HandlerAction[];
1904
+ unchangedThen: HandlerAction[];
1905
+ startedThen: HandlerAction[];
1906
+ finishedThen: HandlerAction[];
1907
+ };
1908
+ /**
1909
+ * A `Unique Id`'s or `UUID`'s `New` (EXP-011 §37) — `date-now-read`'s shape, with a failure arm
1910
+ * on one of the two nodes and none on the other.
1911
+ *
1912
+ * 🔴 **One kind for both nodes, discriminated by `fn`.** §9.4 counted eleven sites that enumerate
1913
+ * this union and named the five the compiler does not hold; a second action kind would have paid
1914
+ * that toll twice for two nodes whose only structural difference is whether `failThen` can be
1915
+ * non-empty. `ID_NODES.canFail` is the fact, and it is stated once.
1916
+ *
1917
+ * Like `date-now-read`, `materialize` and `errorMaterialize` are filled in the verdict sweep and
1918
+ * never at compile time — a render read of `Id` resolves passes later, so asking "does anything
1919
+ * read this" here answers *no* for every render read there is (§8.3's allocation-order rule).
1920
+ */
1921
+ /**
1922
+ * EXP-011 §55. A mutator's Array Id fed by a `Create New Array`'s `id` — the handle, never a string.
1923
+ *
1924
+ * `stateName` is the minting component's row; `viaLocal` is set when the mutator sits inside the mint's own
1925
+ * Done chain (the chain-local is the array just made). Outside that chain the handle can be `null` — no Do
1926
+ * has fired — which is the runtime's `<prefix>/no-array` Failure: the emitter prints the guard, the raise on
1927
+ * the error channel (§54) with the node's own message, and `failThen`. `Remove` never guards: its row exists
1928
+ * only because the handle did (§30's gate 3, restated over the handle).
1929
+ */
1930
+ export type MintedTarget = {
1931
+ /** The `Create New Array` node. */
1932
+ nodeId: string;
1933
+ stateName: string;
1934
+ viaLocal?: string;
1935
+ /** The mutator node — the raise's provenance. */
1936
+ sinkId: string;
1937
+ action: 'insert' | 'clear' | 'remove';
1938
+ failThen: HandlerAction[];
1939
+ };
1940
+ /**
1941
+ * EXP-011 §55. `Create New Array`'s Do: `const <local> = collection<T>([...<source>]); set<Row>(<local>);`
1942
+ * then the Done chain. `source` absent is the empty literal (`items` never received). Every Do mints another
1943
+ * array, exactly as `Collection.get()` with no name does.
1944
+ */
1945
+ export type ArrayNewAction = {
1946
+ kind: 'array-new';
1947
+ nodeId: string;
1948
+ local: string;
1949
+ stateName: string;
1950
+ rowType: string;
1951
+ source?: ValueExpr;
1952
+ /** Whether `source` is statically a list; an untyped source is copied when it is an array and empty otherwise. */
1953
+ sourceIsList?: boolean;
1954
+ then: HandlerAction[];
1955
+ };
1956
+ /** EXP-011 §55. One `Create New Array`'s handle row, and the row type the emitter spells it with. */
1957
+ export interface MintedArrayPlan {
1958
+ nodeId: string;
1959
+ stateName: string;
1960
+ /** `any`, or a named array's interface when the snapshot source is that array (or a filter over it) and nothing inserts. */
1961
+ rowType: string;
1962
+ /** The named array whose interface `rowType` names, for the import. */
1963
+ rowCollection?: string;
1964
+ }
1965
+ /**
1966
+ * A `Hash`'s Do or a `Random Bytes`' New (EXP-011 §59) — {@link IdNewAction}'s two-arm shape with a
1967
+ * value row that boots empty, a raise in the Failure arm, and (Hash) an `await`.
1968
+ */
1969
+ export type CryptoCallAction = {
1970
+ kind: 'crypto-call';
1971
+ /** EXP-011 §90, as `api-call`'s. */
1972
+ detached?: true;
1973
+ nodeId: string;
1974
+ node: CryptoNodeKind;
1975
+ fn: 'tryHash' | 'tryRandomBytes' | 'tryExtract';
1976
+ /** The raised code — `hash/failed` / `random-bytes/failed` (the node files). */
1977
+ code: string;
1978
+ /** Whether the call is awaited (`crypto.subtle.digest` is a promise; `getRandomValues` is not). */
1979
+ async: boolean;
1980
+ /**
1981
+ * The verb's arguments in port order. A wire is the expression the handler closes over, a literal
1982
+ * is the literal, and an input nobody set is absent — the lib applies the node's own fallback
1983
+ * there, as `_run` / `_generate` do. `numeric` marks the one argument printed inside `Number(…)`
1984
+ * (Length's setter), and only where a wire delivers it.
1985
+ */
1986
+ inputs: Array<{
1987
+ port: string;
1988
+ expr?: ValueExpr;
1989
+ numeric?: true;
1990
+ }>;
1991
+ /** The chain-local the call binds — the whole result; the Done arm reads `.digest` / `.value`, the Failure arm `.error`. */
1992
+ local: string;
1993
+ /** The value row, where anything outside the chain reads it. Allocated by a read (§14.2's rule). */
1994
+ materialize?: string;
1995
+ /** The Error row, allocated by a read. */
1996
+ errorMaterialize?: string;
1997
+ /**
1998
+ * EXP-011 §76. A `Pattern Extractor`'s rows, one per value output something reads outside the chain —
1999
+ * written from the local's field of the same name BEFORE the arms, on every run, as `doExtract` writes them.
2000
+ */
2001
+ materializeByPort?: Array<{
2002
+ port: string;
2003
+ state: string;
2004
+ }>;
2005
+ then: HandlerAction[];
2006
+ failThen: HandlerAction[];
2007
+ /** EXP-011 §76. The `Found` / `Not Found` chains — printed inside the Done arm, ahead of `then`. Empty for the two verbs. */
2008
+ foundThen: HandlerAction[];
2009
+ notFoundThen: HandlerAction[];
2010
+ };
2011
+ /**
2012
+ * A `Push Component To Stack`'s Navigate (EXP-011 §61) — `navigate.ts` `navigate()`, printed as the runtime's own call:
2013
+ * `pushComponent(name, { target, params, backCallback, hasNavigated, hasUnchanged })` (or `replaceComponent`, which
2014
+ * installs no callback — `replaceAsync` never does). The three failures are excluded statically (a literal Target inside
2015
+ * the stack's Components list, no transitions), so `hasFailed` is never printed and a Failure wire is dropped with a note.
2016
+ */
2017
+ export type StackPushAction = {
2018
+ kind: 'stack-push';
2019
+ nodeId: string;
2020
+ /** The stack's authored name (`'Main'` by default) — the registry key. */
2021
+ stack: string;
2022
+ mode: 'push' | 'replace';
2023
+ /** The target page id, resolved statically against the stack's Components list. */
2024
+ target: string;
2025
+ /** The component the target page shows — the emit resolves each param's prop identifier off its plan. */
2026
+ targetLegacy: string;
2027
+ /** The target's Component Inputs the pusher sets, by port; the prop identifier is the target's own, read at emit. */
2028
+ params: Array<{
2029
+ port: string;
2030
+ expr: ValueExpr;
2031
+ coerce?: 'string';
2032
+ }>;
2033
+ /** The pusher's local stem — the callback's `results` argument is `<local>Results`. */
2034
+ local: string;
2035
+ /** The back-results row, where anything outside the back chains reads a `backResult-<k>`. Allocated by a read. */
2036
+ backResults?: string;
2037
+ /** The `backAction-<a>` chains, by action, in wire order. */
2038
+ backActions: Array<{
2039
+ action: string;
2040
+ then: HandlerAction[];
2041
+ }>;
2042
+ then: HandlerAction[];
2043
+ unchangedThen: HandlerAction[];
2044
+ };
2045
+ /**
2046
+ * A `Pop Component Stack`'s Navigate or one of its `backAction-<a>` inputs (EXP-011 §61) — `navigate-back.ts`
2047
+ * `navigate()`: `const <local> = popComponent(pageStackEntry, { backAction, results })`, the Done arm, and the
2048
+ * Failure arm, which raises `pop-component-stack/no-stack-in-scope` on the channel (the one failure the export can
2049
+ * reach — `transition-in-progress` needs a transition). Always the block form, on the crypto verbs' reason.
2050
+ */
2051
+ export type StackPopAction = {
2052
+ kind: 'stack-pop';
2053
+ nodeId: string;
2054
+ /** The back action this pop was asked under, or none for the plain Navigate. */
2055
+ backAction?: string;
2056
+ /** The `result-<k>` values, read where the setters stored them — the render expression the handler closes over. */
2057
+ results: Array<{
2058
+ key: string;
2059
+ expr: ValueExpr;
2060
+ }>;
2061
+ local: string;
2062
+ then: HandlerAction[];
2063
+ failThen: HandlerAction[];
2064
+ };
2065
+ /** A `Component Stack`'s Reset (EXP-011 §61): `<local>.reset()` then the Done chain — `resetAsync` reports done last. */
2066
+ export type StackResetAction = {
2067
+ kind: 'stack-reset';
2068
+ nodeId: string;
2069
+ local: string;
2070
+ then: HandlerAction[];
2071
+ };
2072
+ export type IdNewAction = {
2073
+ kind: 'id-new';
2074
+ nodeId: string;
2075
+ fn: 'randomId' | 'randomUuid';
2076
+ /**
2077
+ * The chain-local the `New` binds to.
2078
+ *
2079
+ * ⚠️ **It is not the id in both cases.** For `randomId` the local *is* the string; for
2080
+ * `randomUuid` it is the `UuidResult`, and a read of `Id` inside the Done chain is
2081
+ * `<local>.uuid` — which narrows to `string` there and only there. `idLocalReadOf` is the one
2082
+ * place that spelling lives, so the expression side and the action side cannot disagree.
2083
+ */
2084
+ local: string;
2085
+ /** The id row, where anything outside the chain reads it. */
2086
+ materialize?: string;
2087
+ /**
2088
+ * `UUID` only — the `Error` row, allocated by a *read* rather than by the node (§14.2's rule).
2089
+ * A UUID whose Error nobody reads emits no row and no clearing setter.
2090
+ */
2091
+ errorMaterialize?: string;
2092
+ then: HandlerAction[];
2093
+ /** `UUID` only. Always empty for `Unique Id`, which has no Failure port to wire. */
2094
+ failThen: HandlerAction[];
2095
+ };
2096
+ /**
2097
+ * One argument of an {@link HandlerAction} `api-call`: either a plain value, or the object
2098
+ * literal a record verb's `prop-*` set (and a Sign Up's credentials) collapses into.
2099
+ */
2100
+ export type ApiCallArg = {
2101
+ kind: 'expr';
2102
+ expr: ValueExpr;
2103
+ } | {
2104
+ kind: 'data';
2105
+ props: Array<{
2106
+ key: string;
2107
+ expr: ValueExpr;
2108
+ }>;
2109
+ };
2110
+ /**
2111
+ * One re-hosted Function/Expression node (EXP-003-JS-TARGET-OUTPUT §4): the verbatim body plus
2112
+ * everything the wrapper reproduces of the runtime's contract — inputs arriving as a record,
2113
+ * assignments to `Outputs` publishing, script-declared signal outputs callable (as no-ops when
2114
+ * nothing consumes them, exactly as an unwired pulse lands nowhere).
2115
+ */
2116
+ /**
2117
+ * A node whose authored JavaScript did not reach the emitted code, preserved so the repo carries
2118
+ * a record of it. Not a `JsFunctionPlan`: these nodes have no wrapper and no ports mined from the
2119
+ * body — the script is a *parameter* of a node that does something else (a Map Collection's
2120
+ * mapping, a Repeater's template, a Script node's code).
2121
+ */
2122
+ export interface RefusedScriptPlan {
2123
+ nodeId: string;
2124
+ /** The graph type, for the comment's noun — "Map Collection", "Javascript2". */
2125
+ typeName: string;
2126
+ /** The author's label for the node, when it has one. */
2127
+ label?: string;
2128
+ /** Verbatim, from `NodeIR.sourceText`. Never trimmed, never reformatted. */
2129
+ source: string;
2130
+ }
2131
+ export interface JsFunctionPlan {
2132
+ nodeId: string;
2133
+ kind: 'function' | 'expression' | 'visual';
2134
+ /** Module-scope wrapper name — the sanitized node label, deduped per component file. */
2135
+ fnName: string;
2136
+ /** The verbatim body (Function), expression text (Expression) or generated code (Visual). */
2137
+ body: string;
2138
+ /**
2139
+ * The wrapper's input record, in script order: wire-fed inputs carry the resolved source,
2140
+ * literal `in-*` parameters fold, mined-but-unfed inputs stay fields so the body's reads
2141
+ * typecheck (they read `undefined`, exactly as a never-delivered runtime input does).
2142
+ */
2143
+ inputs: Array<{
2144
+ name: string;
2145
+ tsType: string;
2146
+ expr?: ValueExpr;
2147
+ }>;
2148
+ /** Function only: the Outputs record's value fields (mined + declared + consumed). */
2149
+ outputs: Array<{
2150
+ name: string;
2151
+ tsType: string;
2152
+ }>;
2153
+ /** Function only: signal outputs (mined call syntax / `outtype-*: signal`), seeded no-op. */
2154
+ signals: string[];
2155
+ /** Expression only: preamble Math aliases the expression references (`pi` → `Math.PI`). */
2156
+ mathAliases: string[];
2157
+ /**
2158
+ * Visual only: app-wide Variables the block program reads or writes, from the workspace's
2159
+ * `noodl_get_variable`/`noodl_set_variable` fields — never mined from the generated text.
2160
+ *
2161
+ * These become the `Noodl.Variables` facade the wrapper binds (LOGIC-BUILDER-TARGET §3.4):
2162
+ * the body keeps its verbatim `Noodl.Variables["x"]` reads and writes, and they land on the
2163
+ * export's own variables store. Every name here is registered in `ProjectPlan.variables`,
2164
+ * minted by the block program where no `Variable` node declares it.
2165
+ */
2166
+ variables?: string[];
2167
+ /**
2168
+ * P109 ISL-005. Function only, present when its `uses` lists script files: each file the body
2169
+ * reaches as `Scripts.<alias>`, in the order listed. The wrapper binds `Scripts` from the one
2170
+ * shared registry (`src/lib/projectScriptFiles.ts`); the files are emitted once, never pasted here.
2171
+ */
2172
+ scripts?: ScriptUsePlan[];
2173
+ /**
2174
+ * reactive — no `run` wire: one render local per instance, recomputed per render (grade Q).
2175
+ * invoked — `run` wired: calls inline inside its own handler chain only (grade I).
2176
+ */
2177
+ mode: 'reactive' | 'invoked';
2178
+ /**
2179
+ * EXP-019. Function only: the host traits the body carries ("reads the clock (Date)", "keeps state on `this`", …,
2180
+ * `jsHostTraitsOf`). Present ⇒ the run is `pulsed` (a kept node, one run per trigger, outputs read from the node's
2181
+ * record), because a body with a trait must never run at a read site. A `this` trait binds the body to the node's
2182
+ * `self`. In `reactive` mode (no Run wire, slice 2) the trigger is its inputs' arrival and the load (`TickedRunPlan.atLoad`).
2183
+ */
2184
+ host?: string[];
2185
+ /**
2186
+ * EXP-019 (session 133). Function only: the body may write an output or send a signal after its run has returned, from a
2187
+ * timer or a listener it sets up (`jsCallsBackOf`). Its kept node then hands those to the component (`FunctionNode.late`),
2188
+ * which writes the row and runs the signal's chain, as the runtime's node delivers them when they happen.
2189
+ */
2190
+ late?: true;
2191
+ /**
2192
+ * EXP-019 slice 1b (session 134). Function only: the body awaits at its top level (`jsAwaitsAtTopLevel`). Its wrapper is
2193
+ * `async`, its run hands back the promise (`FunctionRun.settled`), and Success and Done run after it settles, as the
2194
+ * runtime's `runScript` awaits the body before sending them. What it writes after its first `await` arrives `late`.
2195
+ */
2196
+ awaits?: true;
2197
+ /**
2198
+ * EXP-020 (session 134). Function only: the Noodl API members the body reads (`jsNoodlUseOf`), and the Variables it
2199
+ * reads or writes by name. The wrapper binds `Noodl` to `src/lib/noodl.ts`'s object, with a facade over each Variable.
2200
+ */
2201
+ noodl?: {
2202
+ members: string[];
2203
+ variables: string[];
2204
+ };
2205
+ /**
2206
+ * EXP-020 slice 2 (session 135). Function only: the body reads `Component.Object`, its component instance's record. The
2207
+ * component keeps one scope per mount (`src/lib/componentScope.ts`), its kept nodes carry it, and `runFunction` hands it
2208
+ * to the body as the runtime hands it the fourth parameter. Always `pulsed` (it is a host trait).
2209
+ */
2210
+ component?: true;
2211
+ /**
2212
+ * EXP-011 §86. Function only, invoked only: a run of it sends a signal of its own that a chain consumes, or (P18-S121-
2213
+ * FRESHOUTPUTS) it materializes a row a reader reads between runs. The wrapper then takes its `Outputs` record as a
2214
+ * parameter, so `runFunction` can hand it the recording one, and the component keeps the node's last values across
2215
+ * runs (`functionNode`), as the runtime's node does.
2216
+ */
2217
+ pulsed?: true;
2218
+ /**
2219
+ * P18-S110-EVALATLOAD. Expression only, reactive only: present when nothing is sure to arrive at
2220
+ * load — no input has a literal (a parameter is set at load, so it always arrives) and the body
2221
+ * reads at least one port. `unset` names the wire-fed inputs; while every one reads `undefined`
2222
+ * nothing has arrived, because the runtime never delivers `undefined` over a wire.
2223
+ *
2224
+ * That is the state GAM-001's load evaluation runs in (`expression.ts` `_scheduleEvaluateExpression`):
2225
+ * ticked (`evaluate`, the default) it evaluates over the unset inputs but publishes no answer for
2226
+ * a throw or a NaN — Result stays `null`, where an arrival's evaluation answers `0` and `NaN`;
2227
+ * unticked (`wait`) it publishes nothing until an input arrives.
2228
+ */
2229
+ loadGate?: {
2230
+ unset: string[];
2231
+ atLoad: 'evaluate' | 'wait';
2232
+ };
2233
+ }
2234
+ /**
2235
+ * P18-S126-INSIGNAL (EXP-011 §94). A signal input's chain: the actions the Component Inputs port's wires describe, in wire
2236
+ * order, run from an effect keyed on the pulse count the parent passes (`InstancePulsePlan`).
2237
+ */
2238
+ export interface InputPulsePlan {
2239
+ port: string;
2240
+ actions: HandlerAction[];
2241
+ }
2242
+ /**
2243
+ * P18-S126-INSIGNAL. A placed component's signal input the parent pulses: the count (`stateName`, a state row) and the
2244
+ * count the child has acted on (`takenLocal`, a ref the parent keeps, so it outlives the child's unmount). Passed as one
2245
+ * prop, `{ sent, taken }`: the child runs its chain when `sent` passes `taken`, at a mount too (the runtime's instance exists
2246
+ * while it is not drawn and takes the pulse then), and never twice for one pulse (StrictMode's second mount included).
2247
+ */
2248
+ export interface InstancePulsePlan {
2249
+ nodeId: string;
2250
+ port: string;
2251
+ stateName: string;
2252
+ takenLocal: string;
2253
+ /**
2254
+ * P18-S127-SIBLINGVALUE (EXP-011 §96). Set when the placed component also reads an input the parent lifts from another
2255
+ * placed component: the count it is handed (`sent`) is a second row that follows the count from the parent's own
2256
+ * effect, one commit later. A lifted value reaches the parent from the child's push effect, which runs in the commit the
2257
+ * pulse lands in; the parent's effects run after its children's, so the delivered count and the lifted value arrive in
2258
+ * the same render, and the chain inside reads the value sent with the pulse, as the runtime's does.
2259
+ */
2260
+ delivered?: {
2261
+ name: string;
2262
+ setterName: string;
2263
+ };
2264
+ }
2265
+ /** P18-S126-INSIGNAL. The TypeScript type of a signal input's prop, the pair the parent passes. */
2266
+ export declare const INPUT_PULSE_TS_TYPE = "{ sent: number; taken: { current: number } }";
2267
+ export interface ReceiverPlan {
2268
+ nodeId: string;
2269
+ channelName: string;
2270
+ /** Actions in trigger-wire source order — statement order in the useSignal handler. */
2271
+ actions: HandlerAction[];
2272
+ }
2273
+ /**
2274
+ * One `useState` row in the component (CONTROLLED-STATE-TARGET §3.1) — the first construct
2275
+ * that materializes state in the emitted component rather than compiling it away. Names live
2276
+ * in the component's one identifier space (props, hooks, wrappers — the stores rule).
2277
+ */
2278
+ export interface StateVarPlan {
2279
+ name: string;
2280
+ setterName: string;
2281
+ /** The useState type parameter, `' | undefined'` included where the boot is undefined. */
2282
+ tsType: string;
2283
+ /** Boot value; null is the `undefined` boot (`useState<T | undefined>()`). */
2284
+ boot: string | number | boolean | null;
2285
+ /**
2286
+ * An initializer expression emitted verbatim, used **instead of** `boot` (EXP-011 Tier 1.3).
2287
+ *
2288
+ * 🔴 The one row that needs it is `Now`'s, and it needs it to be a *lazy* initializer:
2289
+ * `useState(() => new Date())` constructs once per mount, which is what `initialize` does,
2290
+ * where `useState(new Date())` would construct on every render. No other origin has a boot
2291
+ * value that is not a literal, which is why this is an exception rather than the field's
2292
+ * general form.
2293
+ */
2294
+ bootCode?: string;
2295
+ /**
2296
+ * The emitted-library helper {@link bootCode} calls, where it calls one (EXP-011 §37).
2297
+ *
2298
+ * 🔴 **The import is earned from this field and never by reading `bootCode`.** `bootCode` is a
2299
+ * string emitted verbatim, and the two id rows are the first whose initializer calls into
2300
+ * `src/lib/id.ts` — every other earn site in `component.ts` walks *expressions*, and a row's
2301
+ * boot is not an expression it can see. A `Unique Id` with nothing on `New` has a row and no
2302
+ * action at all, so there is nothing else left to derive the import from.
2303
+ */
2304
+ bootHelper?: IdHelper;
2305
+ originNodeId: string;
2306
+ origin: 'switch' | 'counter' | 'control' | 'lifted' | 'jsfun' | 'record-error' | 'variable' | 'http' | 'http-error'
2307
+ /** EXP-011 §41 — a `Cloud Function`'s results row and its Error row. */
2308
+ | 'cloud' | 'cloud-error'
2309
+ /** EXP-011 §43 — a `Record`'s row and its Error row. */
2310
+ | 'record-row' | 'record-row-error'
2311
+ /** EXP-011 §45 — a files node's answer row (a `File`, a `CloudFile`, a `SignedFileUrl`) and its Error row. */
2312
+ | 'file' | 'file-error' | 'external-link-error' | 'navigate-path-error' | 'now'
2313
+ /** EXP-011 §37 — the id a `Unique Id` or a `UUID` holds. */
2314
+ | 'id' | 'array'
2315
+ /** EXP-011 §37 — a `UUID`'s Error. `Unique Id` has no Failure port and never allocates one. */
2316
+ | 'id-error'
2317
+ /** EXP-011 §59 — a `Hash`'s Digest / a `Random Bytes`' Value row, and its Error row. Both allocated by a read. */
2318
+ | 'crypto' | 'crypto-error'
2319
+ /** EXP-011 §61 — a pusher's back-results row (`Record<string, unknown>`, boots `{}`). */
2320
+ | 'stack-back'
2321
+ /** EXP-011 §87 — a Mounted or Visible written by the Evaluate of the unticked Conditions wired into it. */
2322
+ | 'condition'
2323
+ /** EXP-011 §99 — a repeater's relayed row value, written in the rows' relayed-signal callbacks. */
2324
+ | 'relay';
2325
+ /** The provenance comment above the row. */
2326
+ comment: string;
2327
+ }
2328
+ /**
2329
+ * The graph path of a wired control-state input (CONTROLLED-STATE-TARGET §3.4): a useEffect
2330
+ * running the *input setter's* semantics — coercion, abstain guards, clamping, per §1's table —
2331
+ * and never the `Changed` chain (the runtime's own asymmetry).
2332
+ */
2333
+ export interface SyncEffectPlan {
2334
+ stateName: string;
2335
+ source: ValueExpr;
2336
+ /**
2337
+ * The `variable-*` forms are the value Variables (EXP-011 Tier 1.4) reaching the same
2338
+ * construct from the other direction: their `value` input under Run On Value Change is a
2339
+ * graph path that stores without firing `Changed`, exactly as a control's is, so
2340
+ * `variablebase.setValueTo`'s table — abstain on `undefined`, `Treat empty as` on `null`,
2341
+ * `args.cast` otherwise, `NaN` banned — is written here as one more coercion. `variable-none`
2342
+ * is Color's identity cast.
2343
+ */
2344
+ coerce: 'checkbox' | 'slider' | 'dropdown' | 'textinput' | 'variable-string' | 'variable-number' | 'variable-boolean' | 'variable-none';
2345
+ /** Slider only: the literal clamp bounds (a wired min/max defers the node before this). */
2346
+ min?: number;
2347
+ max?: number;
2348
+ /** `variable-*` only: what a `null` arrival stores — the selected `Treat empty as` coercion. */
2349
+ empty?: string | number | boolean | null;
2350
+ }
2351
+ /**
2352
+ * The lifted value output's child side (CONTROLLED-STATE-TARGET §3.5, CO §6 built): a push
2353
+ * effect firing the optional callback prop on change and once at mount — the boot delivery a
2354
+ * parent wire gets from the interpreter, so the mount fire is the faithful part.
2355
+ */
2356
+ export interface PushEffectPlan {
2357
+ prop: string;
2358
+ expr: ValueExpr;
2359
+ /**
2360
+ * P18-S122-TPLBUILD (TS2345). The port's declared type, when the pushed value's static type is not it (an untyped
2361
+ * Variable is `unknown`, an Expression's answer keeps `null` for a NaN at load): the push says the port's type, as a
2362
+ * cast. The runtime's Component Outputs hands the value on as it is, and so does the cast; it changes no value.
2363
+ */
2364
+ castTo?: string;
2365
+ }
2366
+ /**
2367
+ * A reactive Condition (LOGIC-TARGET §10): the box is ticked, so the node re-tests on every
2368
+ * arrival on `condition` and fires exactly one arm — a re-run keyed on the condition, which is
2369
+ * a `useEffect`, not a handler. The Evaluate-only Condition is the other half and compiles to a
2370
+ * `branch` action inside whatever handler drives it (§3).
2371
+ *
2372
+ * `action` is always a `branch`, so every emit-side sweep that already walks branch arms — the
2373
+ * import, navigate and state-reference collectors — reaches these arms for free.
2374
+ */
2375
+ export interface BranchEffectPlan {
2376
+ nodeId: string;
2377
+ action: HandlerAction;
2378
+ /** The provenance comment above the effect — the node's authored label when it has one. */
2379
+ comment: string;
2380
+ }
2381
+ /**
2382
+ * A `Value Changed` (EXP-011 §39): the node fires whenever its Input becomes a different value,
2383
+ * the first arrival included, and compares by identity — `useEffect` over the value, with the
2384
+ * last value seen in a `useRef` so a re-render that delivers the same value fires nothing.
2385
+ *
2386
+ * ⚠️ React's dependency compare is `Object.is` and the node's is `===`; they differ only on
2387
+ * `NaN` (the node fires on every `NaN` arrival, the effect never re-runs for one). Recorded in
2388
+ * the ledger rather than reproduced — a value that is `NaN` on every arrival is not a change
2389
+ * an author is watching for.
2390
+ */
2391
+ export interface ValueChangedEffectPlan {
2392
+ nodeId: string;
2393
+ /** The Input, as the render-mode expression the effect watches. */
2394
+ watch: ValueExpr;
2395
+ /** The `useRef` local holding the last value the effect saw. */
2396
+ lastLocal: string;
2397
+ actions: HandlerAction[];
2398
+ comment: string;
2399
+ }
2400
+ /**
2401
+ * P18-S119-TICKEDRUN. A Function or Expression whose Run is wired also runs when one of its value inputs whose Run On Value
2402
+ * Change box is ticked (or unset, which is ticked) gets a new value (NDA-017 §2, `setScriptInputValue`). A wired value
2403
+ * arrives when its source first has one and whenever it changes after; a literal arrives once, at load. Such a run sends
2404
+ * the Function's own signals and writes its outputs, but never Done (no Run sent it). Two arrivals, or an arrival and a
2405
+ * Run, in one update make one run.
2406
+ *
2407
+ * Printed as a `useEffect` keyed on the watched values' sources, beside the Run chains: it asks `tickedArrival` whether
2408
+ * a value arrived (not `undefined`, not the one held), and runs `action` if so. A Run records what it read the same way,
2409
+ * so a click that sets a watched value and sends Run makes one run, the Run's, with its Done (measured, s121 drive S4).
2410
+ * Measured in the viewer (s121 drive S0): at load the arrival's run comes first, then a Did Mount's Run, two runs.
2411
+ */
2412
+ export interface TickedRunPlan {
2413
+ nodeId: string;
2414
+ /** The ticked inputs the runtime delivers, in input order: a wired one as the value it reads, a literal as itself. */
2415
+ watch: ValueExpr[];
2416
+ /** The run without its Done: the body, its signals' chains, its §4f write. `ticked: true`. */
2417
+ action: Extract<HandlerAction, {
2418
+ kind: 'jsfun-run';
2419
+ }>;
2420
+ /**
2421
+ * EXP-019 slice 2. A Function with a host trait and no Run wire: the runtime's script setter also runs it once at load
2422
+ * (`simplejavascript.ts`, `if (!this.isInputConnected('run')) this.scheduleRun()`), whatever has arrived, inputs or none.
2423
+ * The effect then runs on its first pass even when no watched value arrived (a ref, so StrictMode's second mount does
2424
+ * not run it twice).
2425
+ */
2426
+ atLoad?: true;
2427
+ comment: string;
2428
+ }
2429
+ /**
2430
+ * A `CSS Definition`'s stylesheet (EXP-011 §48): a module constant holding the authored CSS
2431
+ * verbatim, and a mount effect that appends it to `document.head` in a `<style>` and removes it
2432
+ * on unmount — the node's own `updateStyle`/`removeStyleDeclaration`, per instance.
2433
+ *
2434
+ * ⚠️ One difference, invisible on the page: the runtime keys the element by *node id* and
2435
+ * ref-counts it, so N mounted instances of one component share one `<style>`; the export appends
2436
+ * one per instance. Identical text N times has the same cascade as once.
2437
+ */
2438
+ export interface StyleSheetPlan {
2439
+ nodeId: string;
2440
+ /** `DESK_STYLES` — SCREAMING_SNAKE of the authored label, deduped like a Static Data constant. */
2441
+ constName: string;
2442
+ /** The authored CSS, verbatim — never trimmed, never reformatted. */
2443
+ css: string;
2444
+ comment: string;
2445
+ }
2446
+ /** One `transitiondef-<state>` / `transition-<state>-<value>` — the node's `curve` port shape. */
2447
+ export interface StatesCurve {
2448
+ curve: number[];
2449
+ dur: number;
2450
+ delay: number;
2451
+ }
2452
+ /** One authored value of a States node — its `type-<name>` and its `value-<state>-<name>` per state. */
2453
+ export interface StatesValueDef {
2454
+ name: string;
2455
+ type: StatesValueType;
2456
+ /** The declared TypeScript type of the read: `number`, `string`, `boolean`; a colour is a `string`. */
2457
+ tsType: string;
2458
+ byState: Record<string, string | number | boolean>;
2459
+ transitions: Record<string, StatesCurve>;
2460
+ }
2461
+ /**
2462
+ * A `States` node (EXP-011 §49): a definition constant above the component (`defineStates({…})`),
2463
+ * a `useStates` handle inside it, the listeners its signal outputs compiled into, and the wired
2464
+ * State input as an effect.
2465
+ *
2466
+ * The values are held by the emitted hook, not by state rows: every frame of a tween publishes a
2467
+ * new snapshot, and a row per value would be five setters per frame for the same re-render.
2468
+ */
2469
+ export interface StatesPlan {
2470
+ nodeId: string;
2471
+ /** The `useStates` local — `panel`. */
2472
+ local: string;
2473
+ /** The definition constant — `PANEL_STATES`. */
2474
+ constName: string;
2475
+ states: string[];
2476
+ values: StatesValueDef[];
2477
+ /** `transitiondef-<state>`, by state, where authored. */
2478
+ transitions: Record<string, StatesCurve>;
2479
+ useTransitions: boolean;
2480
+ /** The authored State, when it names a state; the node starts in the first state otherwise. */
2481
+ start: string | undefined;
2482
+ /** The wired State input — `useEffect(() => { local.follow(expr) }, [deps])`. */
2483
+ follow?: ValueExpr;
2484
+ /** The signal outputs, as the chains wired off them. `reached` is by state. */
2485
+ listeners: {
2486
+ stateChanged?: HandlerAction[];
2487
+ reached: Record<string, HandlerAction[]>;
2488
+ done?: HandlerAction[];
2489
+ unchanged?: HandlerAction[];
2490
+ failure?: HandlerAction[];
2491
+ };
2492
+ comment: string;
2493
+ }
2494
+ /**
2495
+ * A `Script` node (EXP-011 §52): `const <local> = useScript(<defName>, { <inputs> }, { <listeners> })`,
2496
+ * with the node's code in its own file under `src/scripts/`, verbatim and unchecked, exported as
2497
+ * `<defName>`. The port set is the one on disk (`scriptPortsOf`); the input record carries only the
2498
+ * inputs the graph feeds, typed by what is delivered.
2499
+ */
2500
+ export interface ScriptPlan {
2501
+ nodeId: string;
2502
+ /** The authored label, or `Script`. */
2503
+ label: string;
2504
+ /** The `useScript` local — `ticker`. */
2505
+ local: string;
2506
+ /** The exported definition constant and the file's base name — `tickerScript`. */
2507
+ defName: string;
2508
+ /** `src/scripts/<dir>/<fileBase>/<defName>.ts` — the file the code lives in. */
2509
+ file: string;
2510
+ /** The author's code, verbatim (`sourceText`'s contract: never trimmed, never reformatted). */
2511
+ code: string;
2512
+ /** Value inputs, in port order: wire-fed carry the resolved source, literals fold, unfed are absent from the record. */
2513
+ inputs: Array<{
2514
+ name: string;
2515
+ tsType: string;
2516
+ expr?: ValueExpr;
2517
+ }>;
2518
+ signalInputs: string[];
2519
+ /** Value outputs, in port order, typed by the declared port type. */
2520
+ outputs: Array<{
2521
+ name: string;
2522
+ tsType: string;
2523
+ }>;
2524
+ signalOutputs: string[];
2525
+ /** The ports as `defineScript` registers them — every port on disk, typed as the editor spelled it. */
2526
+ ports: {
2527
+ inputs: Record<string, string>;
2528
+ outputs: Record<string, string>;
2529
+ };
2530
+ /** The signal outputs, as the chains wired off them. */
2531
+ listeners: Record<string, HandlerAction[]>;
2532
+ comment: string;
2533
+ }
2534
+ /**
2535
+ * A `Run Tasks` node (EXP-011 §53): `const <local> = useRunTasks(<label>, { maxRunningTasks, stopOnFailure },
2536
+ * { <listeners> })` in the host, and the active task instances rendered as the last child of the host's
2537
+ * root container — `{<local>.tasks.map((task) => <Template key={task.key} <inputs from task.item>
2538
+ * onSuccess={task.succeed} onFailure={task.fail} />)}`. The template is a project component with no
2539
+ * visual root, emitted as a null-rendering component whose start-input chain runs on mount
2540
+ * ({@link TaskPlan}). The contract between the two is by NAME, as the runtime's is (`runtasks.ts`
2541
+ * matches the template's outputs by string; there is no wire between host and template).
2542
+ */
2543
+ export interface RunTasksPlan {
2544
+ nodeId: string;
2545
+ /** The authored label, or `Run Tasks`. */
2546
+ label: string;
2547
+ /** The `useRunTasks` local — `sendAll`. */
2548
+ local: string;
2549
+ /** The template's legacy path — `/Notify`. The symbol and file are read off its plan at emit. */
2550
+ templateLegacy: string;
2551
+ /** The resolved contract names (defaults applied). */
2552
+ contract: {
2553
+ start: string;
2554
+ success: string;
2555
+ failure: string;
2556
+ error: string;
2557
+ };
2558
+ /** The list, read at the pulse. */
2559
+ items: ValueExpr;
2560
+ /** Literal or wired (a render read); the hook reads both live, as the runtime reads `_internal`. */
2561
+ maxRunningTasks: ValueExpr;
2562
+ stopOnFailure: ValueExpr;
2563
+ /**
2564
+ * The template's value inputs the host binds from the item, by identity (`runtasks.ts`:
2565
+ * `for (const inputKey in itemNode._inputs) if (model.data[inputKey] !== undefined) setInputValue`).
2566
+ * `prop` is the template's prop identifier for the port; `field` the item key (the port name).
2567
+ */
2568
+ inputs: Array<{
2569
+ port: string;
2570
+ field: string;
2571
+ }>;
2572
+ /** The template's `Id` / `id` inputs, fed the task's model id (`item.id`, else a generated one). */
2573
+ idInputs: string[];
2574
+ /** The template's callback props for the contract's success and failure outputs. */
2575
+ onSuccess?: string;
2576
+ onFailure?: string;
2577
+ /** The signal outputs, as the chains wired off them. */
2578
+ listeners: Partial<Record<'done' | 'failure' | 'unchanged' | 'completed' | 'aborted', HandlerAction[]>>;
2579
+ comment: string;
2580
+ }
2581
+ /**
2582
+ * A component that a `Run Tasks` names as its template (EXP-011 §53) — logic-only, emitted anyway,
2583
+ * rendering `null`; the chain wired off its start input runs once in a mount effect, which is exactly
2584
+ * when `startTask` pulses it (right after `createNode`). Every other signal input stays as before
2585
+ * (declared, unconsumed — COMPONENT-OUTPUTS-TARGET §6). Set only when every host names the same
2586
+ * start input and the chain compiled; otherwise the component keeps no file and
2587
+ * {@link ComponentPlan.taskRefusal} says why, for the hosts to repeat.
2588
+ */
2589
+ export interface TaskPlan {
2590
+ startPort: string;
2591
+ actions: HandlerAction[];
2592
+ /** The Run Tasks nodes that name this component, for the doc comment. */
2593
+ hosts: Array<{
2594
+ componentPath: string;
2595
+ nodeId: string;
2596
+ }>;
2597
+ comment: string;
2598
+ }
2599
+ /**
2600
+ * A `Repeater Item` (EXP-011 §57) whose `Added` drives a chain: once, on mount, guarded against StrictMode's
2601
+ * second mount — `signalAdded` is called synchronously inside `addItem` after `createNode`, once per row
2602
+ * (`foreach.tsx`). Its Item Id, when read, is a row prop ({@link ComponentPlan.rowProps}) rather than anything
2603
+ * here — the parent binds it from `item.id`.
2604
+ */
2605
+ export interface RepeaterItemPlan {
2606
+ nodeId: string;
2607
+ /** The authored label, or `Repeater Item`. */
2608
+ label: string;
2609
+ /** The Added chain, snapped for render context. */
2610
+ actions: HandlerAction[];
2611
+ comment: string;
2612
+ }
2613
+ /**
2614
+ * An `On App Error` node (EXP-011 §54): `const <local> = useAppError({ filter }, () => { …the Error chain… })`.
2615
+ * The boundary subscribes to the app's error channel (`src/lib/errors.ts`) for the life of the component
2616
+ * that hosts it — a page's boundary hears errors while the page is mounted, the root component's for the
2617
+ * app's life (the router shell keeps a file for it, {@link ComponentPlan.shell}) — and every instance
2618
+ * fires (onapperror.ts: no claiming, no consumption). Its value outputs read off the handle's `last`.
2619
+ */
2620
+ export interface AppErrorPlan {
2621
+ nodeId: string;
2622
+ /** The authored label, or `On App Error`. */
2623
+ label: string;
2624
+ /** The `useAppError` local — `catchAll`. */
2625
+ local: string;
2626
+ /** The Filter — a literal, or a render read where wired; absent means every error. Read live by the hook. */
2627
+ filter?: ValueExpr;
2628
+ /** The chain wired off `Error`, when any. */
2629
+ listener?: HandlerAction[];
2630
+ comment: string;
2631
+ }
2632
+ /**
2633
+ * A streaming node (EXP-011 §58): `const <local> = use<Hook>({ label, nodeId, componentName }, { <config> },
2634
+ * { <listeners> })`. The data port's expression rides every action that takes it; the config is the options
2635
+ * object the hook re-reads every render (a literal, or a render read where wired); the signal outputs are the
2636
+ * chains wired off them.
2637
+ */
2638
+ export interface StreamPlan {
2639
+ nodeId: string;
2640
+ type: string;
2641
+ kind: StreamKind;
2642
+ /** The authored label, or the display name. */
2643
+ label: string;
2644
+ /** The hook local — `parser`. */
2645
+ local: string;
2646
+ /** The data port's source — a wire's expression or an authored literal; absent when the port has none. */
2647
+ data?: ValueExpr;
2648
+ /** The config ports that are authored or wired, by port name; an absent port keeps the runtime's initialize() value. */
2649
+ config: Array<{
2650
+ port: string;
2651
+ expr: ValueExpr;
2652
+ }>;
2653
+ /** The signal outputs, as the chains wired off them. */
2654
+ listeners: Record<string, HandlerAction[]>;
2655
+ /** EXP-011 §78. The store a history-family node names — printed as its store module (`store: app`) and imported. */
2656
+ store?: string;
2657
+ comment: string;
2658
+ }
2659
+ /**
2660
+ * A `Screen Resolution` node (EXP-011 §59): `const <local> = useScreenResolution()`. Registered on first
2661
+ * read, the boundary's shape without a listener; its three outputs read off the handle. A node nobody
2662
+ * reads is never registered — the hook would be a listener nothing observes.
2663
+ */
2664
+ export interface ScreenResolutionPlan {
2665
+ nodeId: string;
2666
+ label: string;
2667
+ local: string;
2668
+ comment: string;
2669
+ }
2670
+ /**
2671
+ * EXP-011 §60. A `Component Object` node in **record** mode — its component hosts a `Set Component Object Properties`,
2672
+ * or a descendant reaches the record through the parent pair: `const <local> = useComponentObject<<typeName>>()`,
2673
+ * one mirror effect per wired `value-*` input (`useEffect(() => { <local>.set({ key: src }); }, [deps])`), and the
2674
+ * component's root wrapped in `ParentComponentObjectContext.Provider` — the transcription of "this component owns a
2675
+ * Component Object", which is what `findAncestorWithComponentObject` tests. Alias mode (no writer, no descendant
2676
+ * reach) is COMPONENT-OBJECT-TARGET §3 unchanged and registers nothing here.
2677
+ */
2678
+ export interface ComponentObjectRecordPlan {
2679
+ nodeId: string;
2680
+ label: string;
2681
+ local: string;
2682
+ /** The record's type alias, printed at module level: `type <typeName> = { key?: tsType; … }`. */
2683
+ typeName: string;
2684
+ keys: Array<{
2685
+ key: string;
2686
+ tsType: string;
2687
+ }>;
2688
+ mirrors: Array<{
2689
+ key: string;
2690
+ source: ValueExpr;
2691
+ wireKey: string;
2692
+ }>;
2693
+ comment: string;
2694
+ }
2695
+ /**
2696
+ * A `Component Stack` that renders (EXP-011 §61): `const <local> = usePageStack({ name, pages, startPage })`, and a
2697
+ * visual row that renders the TOP entry only — one `&&` line per page, the entry's params spread as the page
2698
+ * component's props, the reserved `pageStackEntry` prop where the page keeps a Pop.
2699
+ */
2700
+ export interface PageStackPlan {
2701
+ nodeId: string;
2702
+ label: string;
2703
+ local: string;
2704
+ /** The registry key — the authored Name, `Main` by default. */
2705
+ name: string;
2706
+ /** The Components list in authored order, each resolved to the component it shows. */
2707
+ pages: Array<{
2708
+ id: string;
2709
+ label: string;
2710
+ componentLegacy: string;
2711
+ }>;
2712
+ /** The authored Start Page, or the first page's id. */
2713
+ startPage: string;
2714
+ comment: string;
2715
+ }
2716
+ /**
2717
+ * EXP-011 §60. The nearest ancestor's record, read from below — one hook per component, shared by every
2718
+ * `Parent Component Object` and `Set Parent Component Object Properties` in it (each resolves the same nearest
2719
+ * owner): `const <local> = useParentComponentObject<<typeName>>([readers])`. `readers` are the reader nodes that
2720
+ * collapsed — each raises `parent-component-object/no-ancestor` once at mount when the hook answers undefined.
2721
+ */
2722
+ export interface ParentComponentObjectPlan {
2723
+ local: string;
2724
+ typeName: string;
2725
+ keys: Array<{
2726
+ key: string;
2727
+ tsType: string;
2728
+ }>;
2729
+ readers: Array<{
2730
+ nodeId: string;
2731
+ label: string;
2732
+ }>;
2733
+ /** Whether a parent Set prints — the hook prints when a reader collapsed or a writer attached. */
2734
+ comment: string;
2735
+ }
2736
+ /**
2737
+ * A `Drag` node (EXP-011 §63): `const <local> = useDrag({ label, nodeId, componentName }, { …options }, { …listeners })`,
2738
+ * and a wrapper `<div ref style {...handlers}>` where the node sits. Registered for every rendered Drag — the wrapper
2739
+ * needs the hook whether or not anything reads the node.
2740
+ */
2741
+ export interface DragPlan {
2742
+ nodeId: string;
2743
+ label: string;
2744
+ local: string;
2745
+ comment: string;
2746
+ /**
2747
+ * The options that print, in `DRAG_OPTION_PORTS` order — a wire is the render expression the hook reads on arrival,
2748
+ * an authored value a literal, a port nothing set absent (the lib applies the declared default there).
2749
+ */
2750
+ options: Array<{
2751
+ key: string;
2752
+ expr: ValueExpr;
2753
+ }>;
2754
+ /** The listener chains that translated. `done` fires after every snap call; Failure never (dropped with a note). */
2755
+ listeners: {
2756
+ onStart?: HandlerAction[];
2757
+ onMove?: HandlerAction[];
2758
+ onEnd?: HandlerAction[];
2759
+ done?: HandlerAction[];
2760
+ };
2761
+ /** Wires dropped by name — printed as in-code markers on the wrapper, counted by the pre-flight. */
2762
+ drops: Array<{
2763
+ subject: string;
2764
+ reason: string;
2765
+ }>;
2766
+ }
2767
+ /**
2768
+ * An `Animate To Value` node (EXP-011 §49): `const <local> = useAnimatedValue(target, { duration,
2769
+ * delay, ease }, onArrive)`. `duration` and `delay` are read where the hook is — a wired one is
2770
+ * the render expression, an authored one a literal — and `ease` is the authored enum, which a
2771
+ * wire cannot deliver (the node indexes `EaseCurves` by it and an unknown name throws).
2772
+ */
2773
+ export interface AnimationPlan {
2774
+ nodeId: string;
2775
+ local: string;
2776
+ target: ValueExpr;
2777
+ duration: ValueExpr;
2778
+ delay: ValueExpr;
2779
+ /**
2780
+ * P18-S122-TPLBUILD (TS2322). The inputs whose wired value is not statically a number (an untyped Variable is `unknown`):
2781
+ * the runtime's Duration setter stores the value as it is, and the hook's option says `number`, so the option is a cast.
2782
+ */
2783
+ castToNumber?: Array<'duration' | 'delay'>;
2784
+ ease: AnimateEaseName;
2785
+ /** The At Target Value chain, when wired. */
2786
+ arrive?: HandlerAction[];
2787
+ comment: string;
2788
+ }
2789
+ export interface PropPlan {
2790
+ name: string;
2791
+ tsType: string;
2792
+ }
2793
+ export interface QueryPlan {
2794
+ nodeId: string;
2795
+ collectionName: string;
2796
+ /** `puppies` / `setPuppies` / `puppy` / `fetchPuppies` / `Puppy` / api module base `puppies`. */
2797
+ stateName: string;
2798
+ setterName: string;
2799
+ itemName: string;
2800
+ fetchName: string;
2801
+ typeName: string;
2802
+ moduleBase: string;
2803
+ /**
2804
+ * P18-S115-QUERYFILTER (EXP-011 §82). The `where` / `order` / `limit` / `skip` the runtime sends for this node, its
2805
+ * filter translated by the runtime's own translator (`querySettings.ts`). Absent when it sends none of them, so an
2806
+ * unfiltered query's call is the bare `fetch<Plural>()` it always was.
2807
+ */
2808
+ params?: QueryParamsPlan;
2809
+ /**
2810
+ * P18-S115-QUERYPARAMS. With `params.wired`: each wired filter value as the render read its wire resolves to, in
2811
+ * `params.wired.params` order. The query re-runs whenever one changes (`setQueryParameter`, its box ticked).
2812
+ */
2813
+ wiredValues?: Array<{
2814
+ name: string;
2815
+ expr: ValueExpr;
2816
+ }>;
2817
+ /** The project declares a backend, so `src/api/client.ts` exists to put each value on the wire (`filterValue`). */
2818
+ wiredClient?: boolean;
2819
+ /**
2820
+ * P18-S116-QUERYFETCH. Its Fetch is wired, so the query asks when Fetch is sent (`query-fetch` in the sender's
2821
+ * chain). `mount`: it also asks as the node is made, its Class box ticked or its Query settings box ticked with a
2822
+ * setting authored (a wired Fetch settles both unticked, NDA-017; an author can tick them again). `onValues`: the
2823
+ * wired filter values' boxes are ticked, so a new value asks too (the effect, mount included).
2824
+ */
2825
+ fetchWired?: {
2826
+ mount: boolean;
2827
+ onValues: boolean;
2828
+ };
2829
+ }
2830
+ /**
2831
+ * One translated record verb (RECORD-VERBS-TARGET §4d): what the api stub module has to export
2832
+ * for it. The naming is a pure function of the class name, identical to {@link QueryPlan}'s, so a
2833
+ * project that both reads and writes one collection gets a single module.
2834
+ */
2835
+ export interface MutationPlan {
2836
+ nodeId: string;
2837
+ /** EXP-011 §62: `add-relation` / `remove-relation` join the class's module beside the three record verbs. */
2838
+ verb: 'create' | 'update' | 'delete' | RelationVerb;
2839
+ collectionName: string;
2840
+ /** `createPuppy` / `updatePuppy` / `deletePuppy`. */
2841
+ fnName: string;
2842
+ typeName: string;
2843
+ moduleBase: string;
2844
+ /**
2845
+ * The columns this verb actually writes, with the type its argument resolves to — the graph's
2846
+ * own evidence of the record's shape (RECORD-VERBS-TARGET §4d).
2847
+ *
2848
+ * The interface is minted from the project's collection **schema snapshot**, and 32 of the 39
2849
+ * corpus projects carry none at all: `metadata.dbCollections` is absent, so every collection
2850
+ * types as `{ id: string }` and *any* `create`/`update` that writes a property emits a call
2851
+ * that does not compile. The schema is the authority where it exists and silent where it does
2852
+ * not; a `prop-` the graph writes is direct evidence of a column, so the two union. Empty for
2853
+ * `delete`, which writes nothing.
2854
+ */
2855
+ writes: Array<{
2856
+ name: string;
2857
+ tsType: string;
2858
+ }>;
2859
+ }
2860
+ /**
2861
+ * One translated user-family node (USER-FAMILY-TARGET §4d): what `src/api/session.ts` has to
2862
+ * export for it. Unlike {@link MutationPlan} there is nothing to key on — a project has one
2863
+ * session — so the three writes and the read all land in a single module.
2864
+ */
2865
+ export interface SessionCallPlan {
2866
+ nodeId: string;
2867
+ verb: UserVerb | 'read';
2868
+ /** `logIn` / `logOut` / `signUp` / `useSession` / `setUserProperties` / `requestMagicLink`. */
2869
+ fnName: string;
2870
+ /**
2871
+ * EXP-011 §44 — the `_User` columns a `Set User Properties` writes (its `prop-*` inputs, the
2872
+ * prefix stripped), typed the record verbs' way: the wire's type where it is one of the three
2873
+ * JSON scalars, else `unknown`. The session module's `UserProperties` interface is the union
2874
+ * over every site — a project has one `_User` class, as it has one session.
2875
+ */
2876
+ writes?: Array<{
2877
+ name: string;
2878
+ tsType: string;
2879
+ }>;
2880
+ }
2881
+ /**
2882
+ * One value an `HTTP Request` sends (EXP-011 Tier 1.2): a path segment, a query parameter, a
2883
+ * header, a body field or a credential.
2884
+ *
2885
+ * `from` is the whole difference between configuration and data. An authored parameter is folded
2886
+ * into the emitted module as a literal — it is what the interpreter would have read out of
2887
+ * `inputValues` on every fetch, and nothing can change it. A wired one becomes a function
2888
+ * parameter the call site fills. An input that is *neither* never appears: the runtime reads
2889
+ * `undefined` for it, and `undefined` omits the header, the query parameter and the body field
2890
+ * alike, so emitting the guard would be emitting a branch that cannot be taken.
2891
+ */
2892
+ export interface HttpValuePlan {
2893
+ /** The wire name — the header, query parameter, path placeholder or field, verbatim. */
2894
+ name: string;
2895
+ from: {
2896
+ kind: 'literal';
2897
+ value: string | number | boolean;
2898
+ } | {
2899
+ kind: 'param';
2900
+ param: string;
2901
+ tsType: string;
2902
+ };
2903
+ }
2904
+ /**
2905
+ * One translated `HTTP Request` (EXP-011 Tier 1.2) — everything `src/api/http.ts` needs to
2906
+ * rebuild the request the node would have sent.
2907
+ *
2908
+ * The node's configuration is `allowEditOnly` almost throughout (method, body type, the four
2909
+ * string lists, the auth preset), which is why this is a plan and not a solver: what the request
2910
+ * *is* was decided in the editor and cannot change at runtime. What can change is the values, and
2911
+ * those are {@link HttpValuePlan}s.
2912
+ */
2913
+ export interface HttpCallPlan {
2914
+ nodeId: string;
2915
+ /** `fetchQuote` — from the node's authored label, deduped across the project's HTTP nodes. */
2916
+ fnName: string;
2917
+ /** `QuoteAnswer` — the exported result type the component's state row is typed by. */
2918
+ typeName: string;
2919
+ /**
2920
+ * The `const answer = await …` local, and the `const message = …` both failure arms bind.
2921
+ *
2922
+ * They live here rather than on the action because the *expression* side needs them too — a
2923
+ * chain-local read is emitted from a `http-out` that knows only its node — and two places
2924
+ * deriving the same name independently is the divergence FINDINGS B-iv is about.
2925
+ */
2926
+ answerLocal: string;
2927
+ messageLocal: string;
2928
+ /** The authored URL, `{placeholders}` included. */
2929
+ url: string;
2930
+ method: string;
2931
+ /** Milliseconds; `timeout || 30000`, the runtime's own defaulting, already applied. */
2932
+ timeout: number;
2933
+ /**
2934
+ * P18-S104-RESPONSETYPE — how the body is read (FED-001 §3.2). `setResponseType` stores `value || 'auto'` and the
2935
+ * fetch tests for `'text'` and `'json'` only, so any other value reads as Auto; that defaulting is applied here.
2936
+ */
2937
+ responseType: 'auto' | 'text' | 'json';
2938
+ pathParams: HttpValuePlan[];
2939
+ queryParams: HttpValuePlan[];
2940
+ headers: HttpValuePlan[];
2941
+ /** Null for GET/HEAD/OPTIONS, which send none — `buildBody` returns undefined for them. */
2942
+ body: {
2943
+ type: 'json' | 'form' | 'urlencoded';
2944
+ fields: HttpValuePlan[];
2945
+ } | {
2946
+ type: 'raw';
2947
+ raw: HttpValuePlan;
2948
+ } | null;
2949
+ auth: {
2950
+ kind: 'bearer';
2951
+ token: HttpValuePlan;
2952
+ } | {
2953
+ kind: 'basic';
2954
+ username: HttpValuePlan;
2955
+ password: HttpValuePlan;
2956
+ } | {
2957
+ kind: 'apiKey';
2958
+ name: HttpValuePlan;
2959
+ value: HttpValuePlan;
2960
+ location: 'header' | 'query';
2961
+ } | null;
2962
+ /**
2963
+ * The Response Mapping, compiled (`$.data.items[0].name` → `?.data?.items?.[0]?.name`).
2964
+ *
2965
+ * `steps` is empty for the default path `$`, which is the whole body — and `null` for a path
2966
+ * `extractByPath` can never resolve (one that does not start with `$`), where the runtime
2967
+ * publishes `undefined` on every answer and so does the emitted module.
2968
+ */
2969
+ fields: Array<{
2970
+ name: string;
2971
+ steps: Array<string | number> | null;
2972
+ }>;
2973
+ }
2974
+ /** One `Cloud Function` node's call (EXP-011 §41) — what `src/api/functions.ts` emits for it. */
2975
+ export interface CloudCallPlan {
2976
+ nodeId: string;
2977
+ /** The backend function's name, verbatim — the `function` parameter. */
2978
+ functionName: string;
2979
+ /** `callPublishPage` — from the node's authored label, else the function name. */
2980
+ fnName: string;
2981
+ /** `PublishPageResults` — the exported result type the component's state row is typed by. */
2982
+ typeName: string;
2983
+ answerLocal: string;
2984
+ messageLocal: string;
2985
+ /** The `in-*` parameters — a folded literal, or the parameter the call site fills. */
2986
+ params: HttpValuePlan[];
2987
+ /** The `out-*` result names the node declares, in port order. */
2988
+ results: string[];
2989
+ }
2990
+ /** One `Record` node's read (EXP-011 §43) — what its collection module has to export for it. */
2991
+ export interface RecordReadPlan {
2992
+ nodeId: string;
2993
+ collectionName: string;
2994
+ /** `Page` — the collection module's interface, the row's type. */
2995
+ typeName: string;
2996
+ moduleBase: string;
2997
+ /** `fetchPageById`. */
2998
+ fnName: string;
2999
+ answerLocal: string;
3000
+ messageLocal: string;
3001
+ /** The Id, bound before the guard so the emitted `Missing Id.` test and the call read one value. */
3002
+ idLocal: string;
3003
+ /** Columns the graph reads off this node that the schema snapshot does not carry — `unknown` in the interface. */
3004
+ reads: Array<{
3005
+ name: string;
3006
+ tsType: string;
3007
+ }>;
3008
+ /**
3009
+ * P18-S111-RECORDLIVE. "Record properties" ticked under Run On Value Change, the default: the node shows the app's one
3010
+ * copy of its record, so a write anywhere in the app reaches its outputs (dbmodelnode2.ts `onModelChangedCallback`).
3011
+ * Unticked, the callback returns early and the outputs move only on the node's own read, which is the row as it was.
3012
+ * False where the project declares no backend: the read is a stub that throws and no client is emitted.
3013
+ */
3014
+ live: boolean;
3015
+ /**
3016
+ * P18-S113-IDBINDREAD. Fetch unwired: the node never reads, its Id only binds the row to the client's copy of the
3017
+ * record (`RecordEffectPlan`). Its class's module carries no `fetch<Type>ById` for it, and the client holds records.
3018
+ */
3019
+ binds?: true;
3020
+ }
3021
+ /**
3022
+ * One files node that translated (EXP-011 §45) — what `src/api/files.ts` must export (`upload` and
3023
+ * `sign`; a `pick` needs only `pickFile` from the util library) and the chain-locals its two arms
3024
+ * bind, read off the plan so the expression side and the action side cannot disagree about a name.
3025
+ */
3026
+ export interface FileOpPlan {
3027
+ nodeId: string;
3028
+ family: 'pick' | 'upload' | 'sign';
3029
+ answerLocal: string;
3030
+ messageLocal: string;
3031
+ }
3032
+ /**
3033
+ * A `Variable` node carrying an authored `Value` (EXP-011 §67). The runtime's `value` input setter
3034
+ * runs at node creation with the parameter and schedules a store (variablenode2.ts), so the node
3035
+ * rewrites the variable on EVERY mount of the component that holds it — navigating back to the
3036
+ * page resets the variable there. The translation is therefore a mount effect in the host — never
3037
+ * a module-level seed, which would boot once, never reset, and run for a component nothing mounted.
3038
+ */
3039
+ export interface VariableSeedPlan {
3040
+ nodeId: string;
3041
+ variableName: string;
3042
+ value: string | number | boolean;
3043
+ comment: string;
3044
+ }
3045
+ /** The seed as the action it prints — read off the plan so the import sweep and the emitter cannot disagree. */
3046
+ export declare function variableSeedAction(seed: VariableSeedPlan): HandlerAction;
3047
+ /**
3048
+ * EXP-011 §71. An `Object` node whose OWN `prop-<key>` inputs carry typed-in values. The runtime
3049
+ * registers any `prop-*` input on demand and queues every authored parameter at node creation
3050
+ * (nodescope.ts); `userInputSetter` marks the key dirty and `scheduleStore` writes it into the
3051
+ * record in the same pass (modelnode2.ts) — so the node rewrites those keys on EVERY mount of the
3052
+ * component that holds it, and a Set's Do lands over them afterwards (measured, session 94). §67's
3053
+ * shape one construct over: a mount effect in the host, one patch per node, never a module-level
3054
+ * seed (which would boot once, never reset, and run for a component nothing mounted).
3055
+ */
3056
+ export interface ObjectSeedPlan {
3057
+ nodeId: string;
3058
+ storeName: string;
3059
+ /** Parameter order — the order the runtime writes `dirtyValues` in. */
3060
+ entries: Array<{
3061
+ key: string;
3062
+ value: string | number | boolean;
3063
+ }>;
3064
+ comment: string;
3065
+ }
3066
+ /**
3067
+ * P18-S116-FIELDMOUNT. A rendered text input whose Value reaches a chain the export translated (its `onChange`). The
3068
+ * runtime's field announces its Value as it mounts: `componentDidMount` calls `setText(startValue)`, which sends
3069
+ * `onTextChanged` with `outwardValueForFieldType(type, text)` and, through the output's `onChange`, Value Changed
3070
+ * (TextInput.tsx, text-input.ts). So every sink of the Value, and every chain on Value Changed, runs once as the field
3071
+ * appears, with the start value or `""`. The export runs the field's own change chain once from a mount effect, with
3072
+ * the announced value where the chain reads the field.
3073
+ */
3074
+ export interface FieldMountPlan {
3075
+ inputId: string;
3076
+ /** The text the field starts from with nothing wired, as the control's boot computes it (`String(startValue ?? '')`). */
3077
+ start: string;
3078
+ /**
3079
+ * The wired Value's source, where a sync effect carries it. The runtime delivers it at node creation, before the
3080
+ * mount, so the field announces it: `undefined` is never delivered (the start value stands), `null` clears.
3081
+ */
3082
+ source?: ValueExpr;
3083
+ /**
3084
+ * P18 session 126 (EXP-011 §93). The field and the elements above it whose Mounted a value decides: the field is not there
3085
+ * as the component mounts, so it announces the first time those values all say mounted (its later mounts announce only a
3086
+ * real change, GAM-009 R10, and the field keeps what it holds). Absent or empty, it announces as the component mounts.
3087
+ */
3088
+ gates?: string[];
3089
+ comment: string;
3090
+ }
3091
+ /**
3092
+ * P18-S128-FIELDSET and P18-S127-CLEAROUT (EXP-011 §97). A text field's Set or Clear, carried on the `state-set` of the
3093
+ * field's own row, so every walker that reads a state write reads this one; the emitter prints the rest.
3094
+ *
3095
+ * The runtime (`text-input.ts`, `TextInput.tsx`): **Set** writes `_internal.text`, the Value as last delivered (`undefined`
3096
+ * is never delivered, `null` is `''`) or `''` once a Clear has run, and **nothing while the field has focus**; **Clear**
3097
+ * writes `''`, focused or not. Both then call the field's own `setText`, which sends the text to every sink of the Value and
3098
+ * fires Value Changed, changed or not. So the exported write is followed by the field's own change chain, with the text
3099
+ * written where the chain reads the field, as the mount announcement prints it (P18-S116-FIELDMOUNT).
3100
+ *
3101
+ * On a Set, `expr` is the Value's source as a handler reads it (a literal where nothing is wired), and `start` the text a
3102
+ * Value never delivered leaves (the typed-in Value, or `''`); the emitter writes `start` for `undefined`, `''` for `null`,
3103
+ * else `String(value)`. On a Clear, `expr` is the literal `''`.
3104
+ */
3105
+ export interface FieldWrite {
3106
+ inputId: string;
3107
+ port: 'set' | 'clear';
3108
+ start?: string;
3109
+ }
3110
+ /** The seed as the action it prints — one patch, read off the plan so the import sweep and the emitter cannot disagree. */
3111
+ export declare function objectSeedAction(seed: ObjectSeedPlan): HandlerAction;
3112
+ /**
3113
+ * A `Record` whose `Fetch` is unwired (EXP-011 §43, P18-S113-IDBINDREAD): the Id binds the row to the client's copy of
3114
+ * that record, from an effect keyed on the Id. `action` is the node's `record-fetch` with empty chains (a Done or a
3115
+ * Failure wired is refused, as neither fires without a Fetch); it carries the Id and the row the planner fills, and
3116
+ * the emitter prints the bind from it, never the read.
3117
+ */
3118
+ export interface RecordEffectPlan {
3119
+ nodeId: string;
3120
+ action: HandlerAction;
3121
+ comment: string;
3122
+ /** The project declares a backend, so the client holds records; with none, nothing is held and the row stays unset. */
3123
+ held: boolean;
3124
+ /**
3125
+ * "Record properties" ticked (the default): the row follows every later write to the copy (`onModelChangedCallback`).
3126
+ * Unticked, the bind flags the outputs once, so the row is the copy as it was at the bind.
3127
+ */
3128
+ follows: boolean;
3129
+ }
3130
+ export interface RepeaterPlan {
3131
+ nodeId: string;
3132
+ /** Legacy component path of the template ("/Components/PuppyCard"), or null when unset. */
3133
+ templatePath: string | null;
3134
+ /** The DbCollection2 node wired into `items`, or null when nothing statically known feeds it. */
3135
+ itemsQueryId: string | null;
3136
+ /** The named client-side array wired into `items` (Collection2.items), or null. */
3137
+ itemsCollectionName: string | null;
3138
+ /**
3139
+ * The identity mapping parsed from the effective mapping script (authored parameter, else the
3140
+ * declared port's default — the fixture's trap). **No script at all is 'template-inputs'**:
3141
+ * the runtime then identity-maps item properties onto same-named component inputs by itself
3142
+ * (foreach.tsx), so the mapping is the template's input names, resolved at emit. Null when
3143
+ * the script is anything beyond a static string→string `map({...})` literal; that repeater
3144
+ * defers to EXP-003.
3145
+ */
3146
+ mapping: Array<{
3147
+ input: string;
3148
+ field: string;
3149
+ }> | 'template-inputs' | null;
3150
+ /**
3151
+ * A list-typed vocabulary source wired into `items` (CONTROLLED-STATE-TARGET §4e) — a
3152
+ * prop-fed or state-fed plain list. Emitted `(expr ?? []).map(…)`: `?? []` is foreach.tsx's
3153
+ * own "empty arrival clears the list", rows key by index (no identity column, and the
3154
+ * runtime re-renders on array identity change anyway — grade Q).
3155
+ */
3156
+ itemsExpr?: ValueExpr;
3157
+ /**
3158
+ * A Static Data node whose parsed rows are hoisted to a module constant (STATIC-DATA §3).
3159
+ * Deliberately not `itemsExpr`: that path's contract is "no statically-known item shape, so
3160
+ * fields read as `any` and every mapped input is kept". Static Data's shape *is* known, so it
3161
+ * takes the `collection` treatment instead — derived type, `allowedFields`, a real key.
3162
+ */
3163
+ itemsStaticId?: string;
3164
+ }
3165
+ /** EXP-011 §99. One relayed row value: the repeater, the row's output, the row's input it passes through, its state row. */
3166
+ export interface RowRelayPlan {
3167
+ repeaterId: string;
3168
+ output: string;
3169
+ input: string;
3170
+ stateName: string;
3171
+ }
3172
+ /**
3173
+ * One Static Data node hoisted to a frozen module constant (STATIC-DATA-TARGET §3).
3174
+ *
3175
+ * The node's three inputs are all `allowEditOnly` (staticdata.ts), so no wire can feed them and
3176
+ * the rows are knowable by construction rather than by a solver that happened to succeed. Only
3177
+ * nodes that pass every §4 gate get a plan; the rest defer with their reason named.
3178
+ */
3179
+ export interface StaticDataPlan {
3180
+ nodeId: string;
3181
+ /** `PRODUCTS_DATA` — SCREAMING_SNAKE of the authored label, deduped against reserved names. */
3182
+ constName: string;
3183
+ /** `FeaturedProduct` — the row type alias. */
3184
+ typeName: string;
3185
+ /** Nested object/array aliases this row type references, declaration order (§3.1). */
3186
+ nestedTypes: Array<{
3187
+ name: string;
3188
+ decl: string;
3189
+ }>;
3190
+ /** The row type's own field list, source order. */
3191
+ fields: Array<{
3192
+ name: string;
3193
+ tsType: string;
3194
+ optional: boolean;
3195
+ }>;
3196
+ /** The parsed rows, verbatim — emitted as a frozen literal. */
3197
+ rows: Array<Record<string, unknown>>;
3198
+ /**
3199
+ * `id` when every row carries a unique, primitive, non-null one — which mirrors the runtime's
3200
+ * own identity notion, since `Collection.set` mints each row into a Model and treats `id` as
3201
+ * the record's identity rather than ordinary data (collection.ts:542). Null ⇒ key by index.
3202
+ */
3203
+ keyField: string | null;
3204
+ }
3205
+ /**
3206
+ * One conditional popup render in the hosting component (POPUPS-TARGET §2, §5). Show Popup
3207
+ * nodes opening the same target with identical literal params share a slot — safe because a
3208
+ * node with any consumed close outcome defers, so shared keys never conflate observable
3209
+ * behaviour.
3210
+ */
3211
+ export interface PopupSlotPlan {
3212
+ /** The slot's string literal (`'AboutDialog'`), from the target path's last segment. */
3213
+ slotKey: string;
3214
+ /** Legacy component path of the popup component ("/Components/AboutDialog"). */
3215
+ targetLegacy: string;
3216
+ /** Literal `popupParam-*` values, keyed by the target's input port name — props at emit. */
3217
+ params: Array<{
3218
+ input: string;
3219
+ value: string | number | boolean;
3220
+ }>;
3221
+ /**
3222
+ * HLT-014 — Show Popup's `Close On Escape`. The emitted `PopupDialog` closes on Escape unless this is
3223
+ * `false`, matching the runtime's default-on.
3224
+ */
3225
+ closeOnEscape: boolean;
3226
+ /** HLT-014 — Show Popup's `Accessible Name`; absent means "named by the popup's first heading". */
3227
+ label?: string;
3228
+ /** HLT-014 — Show Popup's `Modal`. `false` renders the plain overlay: not a dialog (a toast). */
3229
+ modal: boolean;
3230
+ }
3231
+ export interface ComponentFilePlan {
3232
+ dir: 'pages' | 'components';
3233
+ /** "ThankYou" — file base name, deduplicated per directory. */
3234
+ fileBase: string;
3235
+ /** "ThankYouPage" for pages, "PuppyCard" for components. */
3236
+ symbol: string;
3237
+ }
3238
+ export interface ComponentPlan {
3239
+ path: string;
3240
+ legacyPath: string;
3241
+ role: 'page' | 'component';
3242
+ /** Null when nothing is emitted for this component (no visual root, or the router shell). */
3243
+ file: ComponentFilePlan | null;
3244
+ /** Why file is null, for the report. */
3245
+ skipReason?: string;
3246
+ /**
3247
+ * Which *kind* of skip {@link skipReason} describes — EXP-004's report leads with what worked,
3248
+ * and these two do not belong on the same side of that line.
3249
+ *
3250
+ * 🔴 **A discriminator rather than a read of the sentence.** There are exactly two skips: the
3251
+ * router shell, which the scaffold emits as `src/App.tsx` and is therefore *done*, and a
3252
+ * logic-only component, which is *not*. Both arrive as one `notes` string prefixed with the
3253
+ * component path, and a report that grouped them by matching the prose would call a working
3254
+ * export broken the first time either sentence was reworded. Set at the two sites that know.
3255
+ */
3256
+ skipKind?: 'scaffolded' | 'deferred';
3257
+ /** The node the JSX root renders (the Page node for pages). */
3258
+ rootId: string | null;
3259
+ /** A page's sole Group child merged into the page div (TARGET-OUTPUT §2's shape). */
3260
+ collapsedGroupId?: string;
3261
+ head?: {
3262
+ title?: string;
3263
+ description?: string;
3264
+ };
3265
+ /** Root node's authored label — the component's doc comment. */
3266
+ docComment?: string;
3267
+ props: PropPlan[];
3268
+ /**
3269
+ * Props that carry the enclosing repeater's row (EXP-011 Tier 1.1) — an `Object` node in
3270
+ * "From repeater" mode, compiled away into the component's interface
3271
+ * (EXP-002-MODEL2-TARGET-OUTPUT §4). Minting order.
3272
+ *
3273
+ * 🔴 `field` is carried separately from `prop` and is **not** decoration. The prop name is
3274
+ * deduplicated against everything else the component declares, so an `Object` reading `mood`
3275
+ * in a component that already has a `mood` input becomes the prop `mood2` — and the parent
3276
+ * must still bind it from `item.mood`. Deriving the field from the prop name would silently
3277
+ * read a field no row has, exactly where the collision made it hardest to notice.
3278
+ */
3279
+ rowProps: Array<{
3280
+ prop: string;
3281
+ field: string;
3282
+ /**
3283
+ * EXP-011 §57. Set when the row prop is a `Repeater Item`'s Item Id (field `id`, typed `string` on this
3284
+ * side): the parent then names the node and the port when a feed cannot supply it, rather than the
3285
+ * generic "no row carries this field".
3286
+ */
3287
+ repeaterItem?: string;
3288
+ }>;
3289
+ /**
3290
+ * Declared signal outputs as callback props (`onWaved?: () => void`), declaration order —
3291
+ * the parent side reads the same list off the target's plan (COMPONENT-OUTPUTS-TARGET §2).
3292
+ */
3293
+ outputProps: Array<{
3294
+ port: string;
3295
+ prop: string;
3296
+ }>;
3297
+ /** Render children per node, collapse applied, logic nodes filtered out. */
3298
+ childrenOf: Record<string, string[]>;
3299
+ roleOf: Record<string, RenderRole>;
3300
+ /**
3301
+ * EXP-011 §51. The `Component Children` the runtime honours (chooseChildSlot), when one exists —
3302
+ * the component then declares a `children` prop, and every instance passes its placed children.
3303
+ * Decided by the pure rule on BOTH sides so they cannot disagree; whether the marker also
3304
+ * RENDERS (`roleOf[childSlot] === 'slot'`) is the walk's answer — a marker below a node that did
3305
+ * not draw declares the prop and renders nothing, which is what the running app does.
3306
+ */
3307
+ childSlot?: string;
3308
+ /**
3309
+ * EXP-010. The kit node definition behind each rendered `'custom'` node, plus the module it came
3310
+ * from. Carried on the plan rather than looked up again at emit time so there is one answer to
3311
+ * "which kit owns this node" — the emitted wrapper's import path is derived from it.
3312
+ */
3313
+ customNodes: Record<string, {
3314
+ moduleDir: string;
3315
+ def: KitNodeIR;
3316
+ }>;
3317
+ /**
3318
+ * EXP-010. A custom node's value outputs that something in this component reads, as local state:
3319
+ * the wrapper calls `onXChanged`, the setter writes the row, and the sink binds to it.
3320
+ *
3321
+ * ⚠️ Local, unlike {@link instanceLifted}, and that is not an oversight. A component instance's
3322
+ * value output has to wait for `planProject`'s second phase because whether the *child* lifted
3323
+ * the port is a fact about another plan. A kit node publishes its own outputs from its own
3324
+ * definition, which this component already has — there is nothing to wait for.
3325
+ */
3326
+ customLifted: Record<string, Array<{
3327
+ port: string;
3328
+ setterName: string;
3329
+ }>>;
3330
+ /**
3331
+ * EXP-010 AC3. Visual children of a rendered node that the export could not identify at all,
3332
+ * per parent id, in child order — the emitter prints a comment where each one sat.
3333
+ *
3334
+ * 🔴 **Unidentifiable, not merely undrawn**, and the difference is the whole criterion. A
3335
+ * `Masonry` that defers is a built-in with a reason the report can state and a slice that will
3336
+ * one day handle it. A node whose type is in no catalog and no loaded kit is *the author's own
3337
+ * node*, and before EXP-010 it left the emitted JSX with a gap and nothing in the file to say a
3338
+ * node had ever been there. That silence is the defect this task exists to end, and it is not
3339
+ * ended by a kit that loads — it is ended by the case where the kit does **not**.
3340
+ */
3341
+ droppedChildren: Record<string, Array<{
3342
+ nodeId: string;
3343
+ type: string;
3344
+ reason: string;
3345
+ }>>;
3346
+ /**
3347
+ * Every wire this plan refused, with the endpoints kept as *values* (EXP-004's in-code markers).
3348
+ *
3349
+ * 🔴 **The emitter cannot re-derive this, and must not try.** "A wire whose sink has no binding"
3350
+ * looks like the same population and is not: a signal folded into a handler chain, a control's
3351
+ * state merged into its `onChange`, a `mounted` wire riding the render wrapper and a wire into a
3352
+ * collapsed group all leave no binding behind while having translated perfectly. Guessing from
3353
+ * absence would put `TODO(export)` markers on working code — a worse failure than the missing
3354
+ * marker this closes, in the one file whose job is not misleading its reader.
3355
+ *
3356
+ * So each refusal records itself here, beside the note it files, and `wireNote` is what keeps
3357
+ * the two from drifting: it writes the row and returns the sentence.
3358
+ */
3359
+ droppedWires: DroppedWire[];
3360
+ /** nodeId → toProperty → source, for value wires landing on rendered nodes. */
3361
+ bindings: Record<string, Record<string, BindingSource>>;
3362
+ /** nodeId → source signal port → actions, for signal wires resolved to handler statements. */
3363
+ handlers: Record<string, Record<string, HandlerAction[]>>;
3364
+ /** Rendered text input id → actions its onChange performs (the wired-onTextChanged rule). */
3365
+ changeHandlers: Record<string, HandlerAction[]>;
3366
+ /** Event Receivers this component hosts as useSignal subscriptions. */
3367
+ receivers: ReceiverPlan[];
3368
+ /** P18-S126-INSIGNAL. One effect per signal input whose wires run a chain. */
3369
+ inputPulses: InputPulsePlan[];
3370
+ /** P18-S126-INSIGNAL. One count per placed component's signal input a sender here attached to. */
3371
+ instancePulses: InstancePulsePlan[];
3372
+ /** Popup slots this component renders, registration order — earned by attachment (§2). */
3373
+ popups: PopupSlotPlan[];
3374
+ /** True when a translated Close Popup attached — the component declares `onClose` (§4). */
3375
+ closesPopup: boolean;
3376
+ queries: QueryPlan[];
3377
+ /** Record verbs translated in this component (RECORD-VERBS-TARGET §4), compile order. */
3378
+ mutations: MutationPlan[];
3379
+ /** The user-family nodes that translated — what `src/api/session.ts` must export. */
3380
+ sessionCalls: SessionCallPlan[];
3381
+ /**
3382
+ * HTTP Requests translated in this component (EXP-011 Tier 1.2), compile order — what
3383
+ * `src/api/http.ts` must export. Earned by attachment, exactly as the record verbs are.
3384
+ */
3385
+ httpCalls: HttpCallPlan[];
3386
+ /** EXP-011 §41. */
3387
+ cloudCalls: CloudCallPlan[];
3388
+ /** EXP-011 §43. */
3389
+ recordReads: RecordReadPlan[];
3390
+ /** EXP-011 §45. The files nodes that translated, compile order — earned by attachment. */
3391
+ fileOps: FileOpPlan[];
3392
+ repeaters: Record<string, RepeaterPlan>;
3393
+ /** Static Data nodes hoisted to module constants (STATIC-DATA-TARGET §3), resolution order. */
3394
+ staticData: StaticDataPlan[];
3395
+ /**
3396
+ * Re-host wrapper definitions by node id (EXP-003 §4), registered the moment a read of the
3397
+ * node resolves — the emit layer prints exactly the wrappers that surviving expressions
3398
+ * reference, so a definition nothing kept costs nothing. Insertion order is resolution order
3399
+ * and therefore deterministic.
3400
+ */
3401
+ jsFunctions: Record<string, JsFunctionPlan>;
3402
+ /**
3403
+ * Nodes carrying author-written JavaScript that this component did **not** translate, and that
3404
+ * are not Function/Expression/Visual Function — those three are `jsFunctions`, and the emit
3405
+ * layer already preserves a refused one's body as a comment.
3406
+ *
3407
+ * 🔴 This is the same loss one node-family over, and it was silent. A `Map Collection` whose
3408
+ * Script does more than name properties is deferred with a reason and an in-code marker naming
3409
+ * the node — and its script text appeared **nowhere in the exported repo**, which is exactly
3410
+ * the case where that text is the only statement of what the developer now has to write
3411
+ * (EXP-004, and §20's argument for the Function case verbatim).
3412
+ *
3413
+ * Node order, so the output is deterministic.
3414
+ */
3415
+ refusedScripts: RefusedScriptPlan[];
3416
+ /**
3417
+ * State rows (CONTROLLED-STATE-TARGET §3), registered the moment a use resolves — the emit
3418
+ * layer prints exactly the vars that surviving actions/expressions/effects reference.
3419
+ */
3420
+ stateVars: StateVarPlan[];
3421
+ /** Sync effects (§3.4), registration order — one per translated wired control-state input. */
3422
+ syncEffects: SyncEffectPlan[];
3423
+ /** Push effects (§3.5), registration order — the lifted value outputs' child side. */
3424
+ pushEffects: PushEffectPlan[];
3425
+ /**
3426
+ * EXP-011 §99. A repeater's relayed row values (`itemOutput-<output>`) this component lifts: a state row per value,
3427
+ * written in every row's relayed-signal callback from what the parent hands that row for its input `input`, before
3428
+ * the signal's chain, as the runtime's `itemOutputSignalTriggered` copies the row's outputs and then sends the signal.
3429
+ */
3430
+ rowRelays: RowRelayPlan[];
3431
+ /** Reactive Conditions (LOGIC-TARGET §10), compile order — one useEffect each. */
3432
+ branchEffects: BranchEffectPlan[];
3433
+ /** Value Changed effects (EXP-011 §39), compile order — one useEffect + one useRef each. */
3434
+ valueChangedEffects: ValueChangedEffectPlan[];
3435
+ /** P18-S119-TICKEDRUN. Run-wired Functions a ticked input also runs, node order — one useEffect + one useRef each. */
3436
+ tickedRuns: TickedRunPlan[];
3437
+ /** EXP-011 §43. Records whose Fetch is unwired, compile order — one useEffect keyed on the Id each. */
3438
+ recordEffects: RecordEffectPlan[];
3439
+ /** EXP-011 §67. Variables with an authored Value, node order — one mount effect each. */
3440
+ variableSeeds: VariableSeedPlan[];
3441
+ /** EXP-011 §71. Object nodes with values typed into their own property inputs, node order — one mount effect each. */
3442
+ objectSeeds: ObjectSeedPlan[];
3443
+ /** P18-S116-FIELDMOUNT. Text inputs that announce their Value as they mount, node order — one mount effect each. */
3444
+ fieldMounts: FieldMountPlan[];
3445
+ /** EXP-011 §48. CSS Definitions, node order — one module constant + one mount effect each. */
3446
+ styleSheets: StyleSheetPlan[];
3447
+ /** EXP-011 §49. The States nodes that translated, registration order. */
3448
+ statesMachines: StatesPlan[];
3449
+ /** EXP-011 §49. The Animate To Value nodes that translated, registration order. */
3450
+ animations: AnimationPlan[];
3451
+ /** EXP-011 §52. The Script nodes that translated, registration order. */
3452
+ scripts: ScriptPlan[];
3453
+ /** EXP-011 §53. The Run Tasks nodes hosted here, registration order. */
3454
+ runTasks: RunTasksPlan[];
3455
+ /** EXP-011 §53. Set when this component is a Run Tasks template whose start chain translated. */
3456
+ task?: TaskPlan;
3457
+ /** EXP-011 §57. Repeater Items whose Added chain translated — a once-on-mount effect each. */
3458
+ repeaterItems: RepeaterItemPlan[];
3459
+ /** EXP-011 §53. Why a component named as a template is not one — repeated by every host that names it. */
3460
+ taskRefusal?: string;
3461
+ /** EXP-011 §54. The On App Error boundaries hosted here, registration order. */
3462
+ appErrors: AppErrorPlan[];
3463
+ /** EXP-011 §58. */
3464
+ streams: StreamPlan[];
3465
+ /** EXP-011 §59. The Screen Resolution hooks this component reads, registration order. */
3466
+ screenResolutions: ScreenResolutionPlan[];
3467
+ /** EXP-011 §60. The Component Object record this component materializes (record mode), if any. */
3468
+ componentObject?: ComponentObjectRecordPlan;
3469
+ /** EXP-011 §60. The nearest ancestor's record this component reads or writes through the parent pair, if any. */
3470
+ parentObject?: ParentComponentObjectPlan;
3471
+ /** EXP-011 §63. The Drag nodes rendered here, registration order. */
3472
+ drags: DragPlan[];
3473
+ /** EXP-011 §61. The Component Stacks this component renders, registration order — one hook line each. */
3474
+ pageStacks: PageStackPlan[];
3475
+ /**
3476
+ * EXP-011 §61. Set when a Pop Component Stack attached here: the interface then declares the reserved prop
3477
+ * `pageStackEntry?: PageStackEntryHandle`, and every stack row that shows this component passes it.
3478
+ */
3479
+ popsStack: boolean;
3480
+ /** EXP-011 §55. The `Create New Array` handles this component holds, allocation order. */
3481
+ mintedArrays: MintedArrayPlan[];
3482
+ /**
3483
+ * EXP-011 §54. Set when this is the router shell kept as a file for the logic beside its Router — a
3484
+ * null-rendering component the scaffold's `App.tsx` renders inside the router, for the app's life. The
3485
+ * Router itself is still `collapsed into src/App.tsx`.
3486
+ */
3487
+ shell?: true;
3488
+ /**
3489
+ * EXP-021 slice 1 (P18 session 136). Set when this is a logic component — no node in it draws, and a component places
3490
+ * it — exported as a hook, `use<Name>(props)`, with the props interface a visual component has: the placement's inputs,
3491
+ * its signal inputs' pulses, its signal outputs' callbacks, its lifted value outputs. Its placements are called, not drawn
3492
+ * (`hookCalls`). The runtime keeps a component instance whether or not anything draws it, as a hook call does.
3493
+ */
3494
+ hook?: true;
3495
+ /** EXP-021 slice 1. The placements of logic components in this component, in source order: one hook call each. */
3496
+ hookCalls: string[];
3497
+ /**
3498
+ * Value output ports this component lifts (§4d child side) — the parent side consults this
3499
+ * list off the target's plan, so parent and child agree by construction (the s10 rule).
3500
+ */
3501
+ liftedOutputProps: Array<{
3502
+ port: string;
3503
+ prop: string;
3504
+ tsType: string;
3505
+ }>;
3506
+ /** Instance id → lifted callbacks the parent passes (`onXChanged={setX}`) (§4d parent side). */
3507
+ instanceLifted: Record<string, Array<{
3508
+ prop: string;
3509
+ setterName: string;
3510
+ }>>;
3511
+ /**
3512
+ * Parent-side lifted wires awaiting the target's plan (planProject's second phase): a
3513
+ * consumed instance value output binds only when the child actually lifted the port —
3514
+ * otherwise the parent would pass a prop the child's emitted interface does not declare.
3515
+ */
3516
+ pendingLifted: Array<{
3517
+ connectionKey: string;
3518
+ instanceId: string;
3519
+ targetLegacy: string;
3520
+ port: string;
3521
+ toNodeId: string;
3522
+ toProperty: string;
3523
+ /** P18-S127-SIBLINGVALUE. The sink is another placed component's input: a lift that fails is named on its marker. */
3524
+ intoInstance?: true;
3525
+ }>;
3526
+ dispositions: Record<string, Disposition>;
3527
+ /** Dropped wires, unhandled constructs — EXP-004's report feed. Nothing silently dropped. */
3528
+ notes: string[];
3529
+ /**
3530
+ * EXP-013. Every node `dispositions` refused, as a row with the refused node that starves it.
3531
+ * Built from `dispositions` at each exit, after the sweeps — see {@link RefusedNode}.
3532
+ */
3533
+ refusals: RefusedNode[];
3534
+ }
3535
+ export interface ProjectPlan {
3536
+ plans: ComponentPlan[];
3537
+ byLegacyPath: Map<string, ComponentPlan>;
3538
+ /**
3539
+ * Legacy component path → the scaffold's route-table row for it.
3540
+ *
3541
+ * 🔴 **The whole row, not the url string it used to hold.** A parameterised page has three
3542
+ * paths — the authored `/product/{id}`, the react-router pattern `/product/:id` and the
3543
+ * concrete url a Navigate builds — and a `Map<string, string>` can only carry one of them.
3544
+ * Carrying the row means the route table and the navigations into it cannot disagree.
3545
+ */
3546
+ pageByLegacy: Map<string, ScaffoldPage>;
3547
+ /** Collections needing an api stub module, in first-use order. */
3548
+ stubCollections: string[];
3549
+ /** App-wide Variables, discovery order — src/stores/variables.ts when non-empty. */
3550
+ variables: VariablePlan[];
3551
+ /** Event channels, discovery order — src/events.ts when non-empty. */
3552
+ channels: ChannelPlan[];
3553
+ /** Named Global Stores, discovery order — one src/stores/<exportName>.ts each. */
3554
+ stores: StorePlan[];
3555
+ /** Named client-side arrays, discovery order — one src/collections/<exportName>.ts each. */
3556
+ collections: CollectionPlan[];
3557
+ /**
3558
+ * Which components a route can reach (reach.ts). Not a disposition and never a reason to emit
3559
+ * less — every component is still emitted. It is what lets the report say whether a deferral is
3560
+ * work the app is missing or a node in a component nothing renders, which over this corpus is
3561
+ * the difference between 85.00% and 93.38%.
3562
+ */
3563
+ reachability: Reachability;
3564
+ /**
3565
+ * EXP-010. Node types more than one kit registers, as `type (kit that lost)`.
3566
+ *
3567
+ * 🔴 A collision, not a warning about one: `registerModule` refuses the second registration, so
3568
+ * the running app shows the *first* kit's node. The export follows that rule and says which kit
3569
+ * it therefore ignored — silently taking either one would emit an app that does not match the
3570
+ * one the author is looking at.
3571
+ */
3572
+ kitDuplicates: string[];
3573
+ }
3574
+ export declare function planProject(ir: ExportIR, catalog: CatalogIndex): ProjectPlan;
3575
+ /**
3576
+ * P18-S122-DEADROW, P18-S123-UNDRAWNTRIGGER. What a component's next plan refuses before it attaches anything: chains on
3577
+ * an element the export never draws (`owners`), Did Mount chains on an element a value mounts (`mounts`, which the
3578
+ * emitter does not run), and the render reads of a Function's row that nothing the export runs ever writes (`rows`, by
3579
+ * the Function's id). Each maps a node id to the sentence the refusal says.
3580
+ *
3581
+ * 🔴 **A row's other readers are kept, and said.** A Function input or a Set Variable that reads a dead row reads what
3582
+ * HEAD's export read (nothing yet); refusing the reader would take a chain that works for every other input with it
3583
+ * (the older Site Builder copies' "The slug to show" reads the URL's slug, and the never-written home slug only as a
3584
+ * fallback). They are named in a note instead.
3585
+ */
3586
+ /** P18 session 126 (EXP-011 §93). The words a refusal under a page's own wired Group starts with; the emitter says the same. */
3587
+ export declare const UNCARRIED_PAGE_GROUP = "the page's own Group,";
3588
+ export interface PlanExclusions {
3589
+ owners: Map<string, string>;
3590
+ mounts: Map<string, string>;
3591
+ rows: Map<string, string>;
3592
+ }
3593
+ /**
3594
+ * Why an element of a planned component is never drawn, or `null` when it may be. The emitter's own rule (`render` in
3595
+ * `component.ts`): an element, or an element above it, whose Mounted is bound to a statically false value, or not bound
3596
+ * and authored false, is removed from the tree. A Mounted bound to anything else draws it when that value says.
3597
+ */
3598
+ export declare function undrawnReasonOf(plan: ComponentPlan, component: ComponentIR, id: string): string | null;
3599
+ /**
3600
+ * EXP-011 §99, P18-S131-ROWLESSTRIGGER. Why a repeater draws no rows in the export, or null when it draws them: its items
3601
+ * have no feed the export reads (a query, a named array, an expression, Static Data), or its rows are mapped by a script
3602
+ * (the emitter's `renderRepeater` draws a TODO marker for both). Decided after the list passes.
3603
+ */
3604
+ export declare function rowlessReasonOf(plan: ComponentPlan, repeaterId: string): string | null;
3605
+ /**
3606
+ * The triggers of one planned component that the emitted app never runs (see {@link PlanExclusions}). Read off the
3607
+ * finished plan: its handlers, its bindings after the lift phase, and every action list it carries.
3608
+ */
3609
+ export declare function unreachedTriggersOf(plan: ComponentPlan, component: ComponentIR): PlanExclusions;
3610
+ /**
3611
+ * A component's output interface, from its Component Outputs nodes' declarations — the union
3612
+ * across nodes, first declaration of a name winning (the runtime merges the port sets the same
3613
+ * way). Signal ports become callback props (COMPONENT-OUTPUTS-TARGET §2): a name that is
3614
+ * already an `onX` identifier is kept verbatim, anything else becomes `on` + PascalCase. A
3615
+ * port that cannot become a prop — no identifier material, or a collision with an input prop
3616
+ * or another output — fails with a reason rather than being silently renamed. Deterministic
3617
+ * over the ComponentIR alone, so the child's plan and every parent's plan agree.
3618
+ */
3619
+ export interface OutputInterface {
3620
+ props: Array<{
3621
+ port: string;
3622
+ prop: string;
3623
+ }>;
3624
+ failed: Array<{
3625
+ port: string;
3626
+ reason: string;
3627
+ }>;
3628
+ /** Declared value-kind output ports — the lifted-state candidates (CONTROLLED-STATE §4d). */
3629
+ valuePorts: string[];
3630
+ /**
3631
+ * Value ports named as lifted callback props (`on` + PascalCase + `Changed` — the s10 name
3632
+ * source; collisions fail the port, never silently rename). Whether a port actually lifts is
3633
+ * the owning component's plan's to decide (its feed must resolve); the name is decided here,
3634
+ * off the ComponentIR alone, so the child's plan and every parent's plan agree.
3635
+ */
3636
+ valueProps: Array<{
3637
+ port: string;
3638
+ prop: string;
3639
+ tsType: string;
3640
+ }>;
3641
+ }
3642
+ export declare function signalInputPortsOf(component: ComponentIR | undefined): Set<string>;
3643
+ export declare function componentOutputInterface(component: ComponentIR): OutputInterface;
3644
+ /** node type → the kit node that registers it, and which module folder that kit is. */
3645
+ export type KitIndex = Map<string, {
3646
+ moduleDir: string;
3647
+ def: KitNodeIR;
3648
+ }>;
3649
+ /**
3650
+ * Every node type the project's kits register (EXP-010).
3651
+ *
3652
+ * ⚠️ **First registration wins, and a duplicate is not silently discarded** — the running app has
3653
+ * the same collision (`registerModule` refuses the second) so the export must agree with it rather
3654
+ * than pick the later one and render a different node than the app does. The caller reports it.
3655
+ */
3656
+ export declare function indexKitNodes(modules: ModuleIR[]): {
3657
+ index: KitIndex;
3658
+ duplicates: string[];
3659
+ };
3660
+ /**
3661
+ * EXP-013 AC4 — is losing this node losing a **pathway** rather than a feature?
3662
+ *
3663
+ * Three families, from Richard's ruling (EXP-011 §50): a backend verb (the record verbs, the
3664
+ * relation verbs, `Record`, `Query Records`, the user verbs, `Cloud Function`, `HTTP Request`,
3665
+ * the files), a navigation (`Navigate`, `Navigate To Path`, `External Link`), and `On App Error`,
3666
+ * whose refusal removes the app's whole error pathway. A `Set Variable` behind a refused trigger
3667
+ * is a missing feature; a `Cloud Function` behind one is the app no longer talking to its backend
3668
+ * — the verdict in the pre-flight turns on this distinction, so it lives beside the tables that
3669
+ * define the families rather than in a second list the report would keep.
3670
+ */
3671
+ /** EXP-011 §58. Which of a streaming node's value outputs read undefined before anything happened — the table's answer, shared with component.ts. */
3672
+ export declare function streamFieldMaybeUndefined(node: StreamKind, field: string): boolean;
3673
+ export declare function isPathwayType(type: string): boolean;
3674
+ /** EXP-011 §56. Every wired expression a filter tree reads, for the walkers that follow an expression's reads. */
3675
+ export declare function whereExprs(where: RecordWhere | null): ValueExpr[];
3676
+ /**
3677
+ * Parses a For Each mapping script as far as "is this a static object literal of
3678
+ * string→string" (TARGET-OUTPUT §2). Anything cleverer returns null and defers to EXP-003.
3679
+ */
3680
+ export declare function parseIdentityMapping(script: string): Array<{
3681
+ input: string;
3682
+ field: string;
3683
+ }> | null;
3684
+ /**
3685
+ * §15 — a component prop's type is what the port *declares*, and `any` when it declares nothing.
3686
+ *
3687
+ * A port type is **free text** (`AiAssistant/authoring/plan.ts`: *"names are what bind"*), so
3688
+ * there is no closed vocabulary to fall through to a default with. The editor's own PortEditor
3689
+ * panel opens a new Component Inputs port as `type: { name: '*' }` (`componentinputs.ts`), and
3690
+ * `*` is normatively the untyped wildcard — the Port Type Contract describes "an untyped Function
3691
+ * output defaulted to `*` and connected anywhere". It is two thirds of the corpus's component
3692
+ * ports (471 of 714).
3693
+ *
3694
+ * Mapping that to `string` was the emitter claiming a type the graph refused to make, and it is
3695
+ * the same disagreement §12 refused in the other direction: the declared type is a claim about
3696
+ * the port, not a check on what reaches it. Nothing coerces on delivery — `componentinputs.ts`
3697
+ * registers each output as a bare getter over `_internal.inputValues`, and the one cast on a
3698
+ * connection (`node.ts` `_setValueFromConnection`) is `object`/`array` → `string` only.
3699
+ *
3700
+ * `any` rather than `unknown` for the reason the `array` case already gives, and the reason the
3701
+ * two sibling mappers in this file — `jsOutputTsType` and `valueTsTypeOf` — both already default
3702
+ * to `any`: strict tsc rejects an `unknown` read at the sites that consume these values.
3703
+ *
3704
+ * ⚠️ `string` must stay an explicit case. It is 224 ports, and it reached its type through the
3705
+ * old default — folding it into this one would widen every genuinely-typed prop in the corpus.
3706
+ */
3707
+ /** One row of a Component Stack's Components list, resolved (EXP-011 §61). */
3708
+ interface StackPageInfo {
3709
+ id: string;
3710
+ label: string;
3711
+ /** The `pageComp-<id>` parameter — undefined when unset (the stack then refuses). */
3712
+ componentLegacy: string | undefined;
3713
+ }
3714
+ /** A `Page Stack` anywhere in the project, read off the IR once (EXP-011 §61). */
3715
+ export interface StackInfo {
3716
+ nodeId: string;
3717
+ componentPath: string;
3718
+ label: string;
3719
+ /** The authored Name, `Main` by default — `navigation-handler.ts` keys its registry on `name || 'Main'`. */
3720
+ name: string;
3721
+ pages: StackPageInfo[];
3722
+ /** The authored Start Page, or the first page's id (`resetAsync`). */
3723
+ startPage: string;
3724
+ /** Why the stack does not translate, or null. Decided from the IR alone, so a pusher in any component can ask. */
3725
+ refusal: string | null;
3726
+ }
3727
+ export interface StackIndex {
3728
+ byNode: Map<string, StackInfo>;
3729
+ byName: Map<string, StackInfo[]>;
3730
+ /** The stacks listing a component (by legacy path) among their pages — the Pop's gate. */
3731
+ hostsOf: Map<string, StackInfo[]>;
3732
+ }
3733
+ /** The stringlist parameters of a Pop (`results`, `backActions`) — split as the runtime splits them, no trimming. */
3734
+ export declare function stackStringList(value: unknown): string[];
3735
+ /**
3736
+ * EXP-011 §61. Every `Page Stack` in the project with its refusal, computed once per IR — `popupTargetLegacies`'
3737
+ * shape, because a pusher is planned in one component and its stack sits in another, and the plan runs
3738
+ * component by component. Every check here is a fact the IR states outright (an authored value, a wire into
3739
+ * the stack, a page's existence and root), never a plan-time one.
3740
+ */
3741
+ export declare function indexPageStacks(ir: ExportIR, catalog: CatalogIndex, kits: KitIndex): StackIndex;
3742
+ /**
3743
+ * The api module's names for one class, a pure function of the class name (RECORD-VERBS-TARGET
3744
+ * §4d). Queries and mutations both derive from this, so a project that reads *and* writes one
3745
+ * collection lands both in a single module rather than two that disagree about the type name.
3746
+ */
3747
+ /**
3748
+ * A schema column's declared type as TS (EXP-002 §10a) — the one mapping, read by the collection
3749
+ * module's interface and by a `Record`'s column reads (EXP-011 §43), so the two cannot disagree.
3750
+ */
3751
+ export declare function tsColumnType(columnType: string): string;
3752
+ export declare function collectionModuleNames(collectionName: string): {
3753
+ typeName: string;
3754
+ plural: string;
3755
+ moduleBase: string;
3756
+ fetchName: string;
3757
+ };