@nodegx/export 0.0.0-stage → 0.2.6

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 (53) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +76 -2
  3. package/dist/analyze/appState.d.ts +303 -0
  4. package/dist/analyze/plan.d.ts +3904 -0
  5. package/dist/analyze/querySettings.d.ts +75 -0
  6. package/dist/analyze/reach.d.ts +41 -0
  7. package/dist/analyze/scriptUses.d.ts +29 -0
  8. package/dist/catalog.d.ts +129 -0
  9. package/dist/cli/args.d.ts +100 -0
  10. package/dist/cli/exitCodes.d.ts +125 -0
  11. package/dist/cli/run.d.ts +30 -0
  12. package/dist/cli.inputs.json +203 -0
  13. package/dist/cli.mjs +62547 -0
  14. package/dist/cli.mjs.map +7 -0
  15. package/dist/emit/animateLib.d.ts +43 -0
  16. package/dist/emit/component.d.ts +166 -0
  17. package/dist/emit/dateLib.d.ts +45 -0
  18. package/dist/emit/emitApp.d.ts +38 -0
  19. package/dist/emit/idLib.d.ts +68 -0
  20. package/dist/emit/kits.d.ts +91 -0
  21. package/dist/emit/mediaLib.d.ts +75 -0
  22. package/dist/emit/naming.d.ts +76 -0
  23. package/dist/emit/parseLib.d.ts +108 -0
  24. package/dist/emit/preflight.d.ts +174 -0
  25. package/dist/emit/readme.d.ts +51 -0
  26. package/dist/emit/report.d.ts +309 -0
  27. package/dist/emit/scaffold.d.ts +83 -0
  28. package/dist/emit/statesLib.d.ts +52 -0
  29. package/dist/emit/style.d.ts +181 -0
  30. package/dist/emit/utilLib.d.ts +78 -0
  31. package/dist/errorMessage.d.ts +28 -0
  32. package/dist/index.cjs +61649 -0
  33. package/dist/index.cjs.map +7 -0
  34. package/dist/index.d.ts +37 -0
  35. package/dist/index.mjs +61570 -0
  36. package/dist/index.mjs.map +7 -0
  37. package/dist/ir/types.d.ts +567 -0
  38. package/dist/ledger.cjs +971 -0
  39. package/dist/ledger.cjs.map +7 -0
  40. package/dist/ledger.d.ts +75 -0
  41. package/dist/ledger.mjs +948 -0
  42. package/dist/ledger.mjs.map +7 -0
  43. package/dist/node-catalog.json +65370 -0
  44. package/dist/parse/kitSource.d.ts +135 -0
  45. package/dist/parse/parseModules.d.ts +42 -0
  46. package/dist/parse/parseProject.d.ts +13 -0
  47. package/dist/react19/VERSION +2 -0
  48. package/dist/react19/react-dom.production.min.js +51 -0
  49. package/dist/react19/react.production.min.js +21 -0
  50. package/dist/scriptFiles.d.ts +30 -0
  51. package/dist/structurePorts.d.ts +70 -0
  52. package/dist/write/writeExport.d.ts +67 -0
  53. package/package.json +53 -4
@@ -0,0 +1,3904 @@
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
+ * P18-EXP022-S4-ADDREFUSED. A Create or Update Record's `Id`, read inside the node's own Done chain: `local` is the `const`
917
+ * the awaited call's answer is bound to, and the read is its `id`. The runtime sets the record (`setModel`, which flags
918
+ * `id`) before it sends Done, so a graph reading Id on Done reads the Id the backend gave a new record, or the one an
919
+ * update was handed. `viaState` is a Create's row form, read where a handler's local does not reach (a placed
920
+ * component's input, which its pulse reads after the commit): the row the answer writes before the Done chain, kept as
921
+ * the runtime keeps the record. Any other read refuses by name.
922
+ */
923
+ | {
924
+ kind: 'verb-id';
925
+ nodeId: string;
926
+ local: string;
927
+ viaState?: string;
928
+ }
929
+ /**
930
+ * EXP-011 §76. `seeded` marks an Error row that `initialize` wrote `''` into and every run rewrites — the
931
+ * extractor's — so the row form is never undefined either. Absent for every other node, whose row reads
932
+ * undefined until the first failure.
933
+ */
934
+ /**
935
+ * One of the five **pure** date nodes (EXP-011 Tier 1.3) — `Date Add`, `Date Compare`,
936
+ * `Date Difference`, `Date Parts`, `Date To String`.
937
+ *
938
+ * They are value-driven: each recomputes whenever any input changes and holds nothing else, so
939
+ * a node becomes **an ordinary function call** the way Tier 1.1's arrays became ordinary value
940
+ * expressions. `fn` names a helper in the emitted `src/lib/date.ts`, and `args` are its
941
+ * arguments in position order — a wired input is the expression that feeds it, an authored one
942
+ * a literal. They compose, which is the whole point: `Now → Date Add → Date To String` is one
943
+ * nested expression and needs no per-consumer field to carry it.
944
+ *
945
+ * Every input may be wired, `Unit` and `Granularity` included since P109-S21-EXPORTDATEUNITWIRED:
946
+ * the emitted helpers coerce both as the setters do (`unitOrDays`; an unknown Granularity
947
+ * truncates nothing), and none of them has a throwing branch.
948
+ */
949
+ | {
950
+ kind: 'date-call';
951
+ fn: DateHelper;
952
+ args: ValueExpr[];
953
+ }
954
+ /**
955
+ * One of the three small **pure** utilities (EXP-011 Tier 2.7) — `Substring`,
956
+ * `Number Remapper`, `String Mapper`.
957
+ *
958
+ * Structurally `date-call`'s twin: a node that is a function of its inputs and holds nothing
959
+ * else becomes one call into an emitted library, `args` in the helper's parameter order, and
960
+ * the calls compose. Every clause in this package that walks a `date-call`'s arguments walks
961
+ * this one's for the same reason, which is why the two are handled together at each of them
962
+ * rather than by a second copy of each walker.
963
+ *
964
+ * They are a separate kind rather than a widened `DateHelper` because the two libraries are
965
+ * separate modules in the emitted app: a project that formats a date should not ship the
966
+ * string utilities, and the import lines are earned per helper.
967
+ *
968
+ * ⚠️ **`cases` is `mapString`'s table and prints between `args[0]` and `args[1]`.** The two
969
+ * numbered-input families are index-aligned lists in the interpreter — `inputs[n]` matched by
970
+ * `indexOf`, `mappings[n]` read off the index it found — and an object literal reproduces that
971
+ * exactly once two things are true of how it is built: **first-wins on a repeated Input**,
972
+ * because `indexOf` finds the first and an object literal keeps the last; and a **present key
973
+ * with an `undefined` value** for an Input whose Mapping was never filled in, because that
974
+ * publishes empty rather than falling through to Default. Both are done where the table is
975
+ * built, and the helper's `hasOwnProperty` is the second half of the second one.
976
+ */
977
+ | {
978
+ kind: 'util-call';
979
+ fn: UtilHelper;
980
+ args: ValueExpr[];
981
+ cases?: Array<{
982
+ from: string;
983
+ to: string | undefined;
984
+ }>;
985
+ /**
986
+ * P109-S21-EXPORTBLENDNAN — `blendColorKept`'s first argument: the `useRef` local holding the blend this
987
+ * Color Blend last received and the colour it last showed, declared once per node at the top of the component. 🔴 Both expression walkers
988
+ * (`collectExprUse` and `hookExprSources`) record it; missing one prints a call naming a local nobody declared.
989
+ */
990
+ kept?: string;
991
+ /**
992
+ * P18-S106-PARSERAISE — a parser's read (Parse CSV, Parse XML, Parse Feed): the node a failed parse raises for,
993
+ * under `code`, and its inputs without the selector (`args` minus the last). The same objects as `args`, so a
994
+ * walker over `args` has walked these. 🔴 Both walkers record it, as for `kept`.
995
+ */
996
+ raiser?: {
997
+ nodeId: string;
998
+ nodeType: string;
999
+ code: string;
1000
+ args: ValueExpr[];
1001
+ };
1002
+ }
1003
+ /**
1004
+ * A `States` node's value outputs (EXP-011 §49) — a read off the `useStates` handle the
1005
+ * component declares: `state` is the State output (`<local>.state`), `value` one of the
1006
+ * authored values (`<local>.values.<name>`, typed by its `type-<name>`), `at` an `At <state>`
1007
+ * (`<local>.state === '<state>'`, a real boolean, so it lands at value sinks too), `error` the
1008
+ * last Error (`<local>.error`, undefined until a failure). No `viaState`: the handle *is* the
1009
+ * render-scope value, read the same way in a handler — the hook re-renders the component on
1010
+ * every frame of a tween, and a handler closes over the latest render, which is what the
1011
+ * interpreter's getter answers too.
1012
+ */
1013
+ | {
1014
+ kind: 'states-out';
1015
+ nodeId: string;
1016
+ local: string;
1017
+ field: 'state' | 'value' | 'at' | 'error';
1018
+ name?: string;
1019
+ tsType: string;
1020
+ }
1021
+ /**
1022
+ * An `Animate To Value`'s `Current Value` (EXP-011 §49) — the number `useAnimatedValue` returns,
1023
+ * never undefined (it boots 0 and only ever holds a number).
1024
+ */
1025
+ /**
1026
+ * A `Script` node's value output (EXP-011 §52) — read off the `useScript` handle's `outputs`, which
1027
+ * reads the live record in both modes: render re-renders on every write, and a handler that fires a
1028
+ * signal reads what the code has written so far, exactly as the runtime getter answers. Always
1029
+ * maybe-undefined — an output the code has not written yet reads undefined.
1030
+ */
1031
+ | {
1032
+ kind: 'script-out';
1033
+ nodeId: string;
1034
+ local: string;
1035
+ output: string;
1036
+ tsType: string;
1037
+ }
1038
+ /**
1039
+ * An `On App Error` boundary's value output (EXP-011 §54) — read off the `useAppError` handle's `last`, the
1040
+ * latest error it accepted, in both modes: the hook re-renders on every accepted error, and a chain fired
1041
+ * by its Error signal reads the error that fired it (the values are updated before the signal, as
1042
+ * onapperror.ts flags them dirty before `sendSignalOnOutput('error')`). Always maybe-undefined — before
1043
+ * the first error there is nothing to read.
1044
+ */
1045
+ | {
1046
+ kind: 'app-error-out';
1047
+ nodeId: string;
1048
+ local: string;
1049
+ field: (typeof ON_APP_ERROR_VALUE_OUTPUTS)[number];
1050
+ tsType: string;
1051
+ }
1052
+ /**
1053
+ * A streaming node's value output (EXP-011 §58) — a live getter on the hook's handle, read the same way in
1054
+ * both contexts: the host re-renders once per invocation, and a chain fired by one of the node's signals
1055
+ * reads the state the runtime's getter would answer at that moment. `maybeUndefined` is the table's.
1056
+ */
1057
+ | {
1058
+ kind: 'stream-out';
1059
+ nodeId: string;
1060
+ local: string;
1061
+ node: StreamKind;
1062
+ field: string;
1063
+ tsType: string;
1064
+ } | {
1065
+ kind: 'animate-out';
1066
+ nodeId: string;
1067
+ local: string;
1068
+ };
1069
+ /**
1070
+ * A `Script` node's signal input pulsed (EXP-011 §52) — `<local>.signals.<port>()`, one call on the
1071
+ * handle. The code's function for that signal runs after the handler that asked (a microtask), which is
1072
+ * where `scheduleSignal` puts it in the runtime; nothing rides on the action, and the node's signal
1073
+ * outputs are its listeners ({@link ScriptPlan.listeners}), passed once to the hook.
1074
+ */
1075
+ /**
1076
+ * A `Run Tasks` node's two action ports (EXP-011 §53) — `Do` is `<local>.run(<items>)`, the list read at
1077
+ * the pulse (the runtime reads `internal.items`, the last value delivered, at `run()`); `Abort` is
1078
+ * `<local>.abort()`. Neither awaits: `run()` schedules its work through the node's own serial queue and
1079
+ * answers through the listeners ({@link RunTasksPlan.listeners}), passed once to the hook.
1080
+ */
1081
+ export type RunTasksAction = {
1082
+ kind: 'runtasks-run';
1083
+ nodeId: string;
1084
+ local: string;
1085
+ items: ValueExpr;
1086
+ } | {
1087
+ kind: 'runtasks-abort';
1088
+ nodeId: string;
1089
+ local: string;
1090
+ };
1091
+ /**
1092
+ * P18-S116-QUERYFETCH. A `Query Records`' Fetch (`storageFetch` → `scheduleFetch`): the query asks again, its wired
1093
+ * filter values read at the pulse, and the list is replaced by the answer. `void fetch<Plural>(…).then(set<Plural>)`:
1094
+ * the runtime's fetch is scheduled, so the chain does not wait for it. `values` are {@link QueryPlan.wiredValues}, carried
1095
+ * so every walker sees what the action reads. A query the export does not keep prints nothing (`expandActions`).
1096
+ */
1097
+ export type QueryFetchAction = {
1098
+ kind: 'query-fetch';
1099
+ nodeId: string;
1100
+ values: Array<{
1101
+ name: string;
1102
+ expr: ValueExpr;
1103
+ }>;
1104
+ };
1105
+ export type ScriptSignalAction = {
1106
+ kind: 'script-signal';
1107
+ nodeId: string;
1108
+ /** The `useScript` local. */
1109
+ local: string;
1110
+ port: string;
1111
+ };
1112
+ export type HandlerAction =
1113
+ /**
1114
+ * A Router navigation (EXP-011 Tier 2.5). `to` is the target page's **authored** url path,
1115
+ * braces and all, and `pathParams` names the expression each braced segment is filled from —
1116
+ * the emitter builds the concrete url, because it is the only place the values exist.
1117
+ *
1118
+ * ⚠️ `query` is the runtime's *leftover* rule, not a second authored list: `getRelativeURL`
1119
+ * substitutes every `pm-` value whose name appears in the path and appends **whatever is
1120
+ * left** as a query string (`router.tsx:625-633`). So a `pm-sort` on a Navigate whose target
1121
+ * path has no `{sort}` is `?sort=…`, and that is a feature authors use rather than an
1122
+ * accident — it is how a page takes an optional parameter without putting it in the route.
1123
+ */
1124
+ {
1125
+ kind: 'navigate';
1126
+ to: string;
1127
+ pathParams: Array<{
1128
+ name: string;
1129
+ expr: ValueExpr;
1130
+ }>;
1131
+ query: Array<{
1132
+ name: string;
1133
+ expr: ValueExpr;
1134
+ }>;
1135
+ /**
1136
+ * P18-S110-NEWTAB. `Open In New Tab` ticked: router.tsx `navigateAsync` builds the page's
1137
+ * url and calls `window.open(url, '_blank')` — no features string, no blocked test, `Done`
1138
+ * either way. The exported url is the BrowserRouter path the in-place arm would push.
1139
+ */
1140
+ newTab?: true;
1141
+ }
1142
+ /**
1143
+ * `Navigate To Path` (EXP-011 §15, session 44) — a navigation to an **authored path string**
1144
+ * rather than to a page component, and four things about it differ from {@link HandlerAction}'s
1145
+ * `navigate` even though the two nodes look like a pair:
1146
+ *
1147
+ * | | `getRelativeURL` (RouterNavigate) | `navigate()` (this node) |
1148
+ * |---|---|---|
1149
+ * | an unset placeholder | left as the literal `{id}`, **and** `?id=undefined` appended | substituted with `''` |
1150
+ * | encoding | `encodeURIComponent` on every value | none — `String(v)` and `q + '=' + v` |
1151
+ * | the query set | whatever is **left over** after substitution | the authored `Query` list |
1152
+ * | an unset query value | n/a | **omitted from the url** |
1153
+ *
1154
+ * 🔴 **So §11.6's rules do not carry across, and copying them would have been wrong in all
1155
+ * four places.** The unset placeholder is the sharpest: it is deferred one node over precisely
1156
+ * because the Router's three readings of it disagree, and it is *translated* here because this
1157
+ * node's own function is coherent about it.
1158
+ *
1159
+ * {@link to} is normalised to exactly one leading slash. The runtime compares trimmed paths
1160
+ * (`_trimUrlPart`, `router.tsx:39`) so `note/42` and `/note/42` are one route there; in
1161
+ * react-router the first is **relative to the current route** and the second is absolute.
1162
+ *
1163
+ * `then` is the `Done` chain and `completedThen` the `Completed` one, in that order, because
1164
+ * `reportOutcome` sends the outcome and then `Completed` (`node.ts:958-995`).
1165
+ *
1166
+ * 🔴 **`newTab` decides which of two different actions this is** (EXP-011 §17, session 46).
1167
+ * Off, the node is `history.pushState` and cannot fail, so `failThen` is empty and
1168
+ * `completedThen` follows the one outcome there is. On, it is `window.open` — which *can* be
1169
+ * refused — so `Failure` becomes live, `Error` becomes writable, and `Completed` stops being
1170
+ * a continuation of `Done` and becomes a **join beneath both arms**. §15.4 earned `Completed`
1171
+ * on "the gates leave exactly one outcome reachable", and opening this arm is exactly the
1172
+ * change that retires that argument rather than inheriting it.
1173
+ */
1174
+ | {
1175
+ kind: 'navigate-path';
1176
+ /** Needed by every registry keyed on the node — the Error row, and the attachment sweep. */
1177
+ nodeId: string;
1178
+ to: string;
1179
+ pathParams: Array<{
1180
+ name: string;
1181
+ expr: ValueExpr;
1182
+ }>;
1183
+ /** `omittable` values print inside the `if` the runtime's `v !== undefined && v !== null` test describes (P107-D21). */
1184
+ query: Array<{
1185
+ name: string;
1186
+ expr: ValueExpr;
1187
+ omittable: boolean;
1188
+ }>;
1189
+ /** The `const` the omittable form collects into; unused when every value is present. */
1190
+ queryLocal: string;
1191
+ /**
1192
+ * 🔴 **This field means “the new-tab arm is reachable”, not “the port is on”** (EXP-011 §18,
1193
+ * session 47). Every consumer already asks it that question — is `Failure` live, is `Error`
1194
+ * writable, is `Completed` a join, is there an outcome to bind — and a wired port answers
1195
+ * all four the same way an authored `true` does, because the tab can still be refused.
1196
+ * What a wire adds is that the *other* arm is reachable too, and that is {@link newTabExpr}
1197
+ * rather than a second reading of this one.
1198
+ *
1199
+ * ⚠️ **`default: false` here and `default: true` on `External Link`** — the two nodes look
1200
+ * like a pair and their unset state lands on opposite sides.
1201
+ */
1202
+ newTab: boolean;
1203
+ /**
1204
+ * The wired `Open In New Tab`, as the expression the emitted handler branches on — set
1205
+ * **only** where the port carries a wire, which is the only way both of this node's two
1206
+ * actions become reachable in one handler (EXP-011 §18).
1207
+ *
1208
+ * 🔴 **The port is answerable because the runtime reads it exactly once, as `!!value`**
1209
+ * (`navigate-to-path.ts:89`, an input `set` that stores a boolean), and `navigate` then
1210
+ * reads that stored boolean (`:204`). ⚠️ `External Link`'s identically-named port is read
1211
+ * **two different ways in two adjacent lines** — truthiness for the features string and
1212
+ * strict equality for the target — which is why a wire defers there and translates here.
1213
+ * The two nodes are not a pair on this port either.
1214
+ *
1215
+ * It is a truthiness sink, so a logic truth value lands here rather than deferring on the
1216
+ * standing rule: the emitted read is the `if` test itself, which coerces exactly as `!!`
1217
+ * does. Read **once**, at the top of the handler, so there is no local to bind it to.
1218
+ */
1219
+ newTabExpr?: ValueExpr;
1220
+ /**
1221
+ * The `const` the `window.open` result binds to. Emitted only where something branches on
1222
+ * it: a new tab with no chains and no Error row is the bare call the graph asked for.
1223
+ */
1224
+ openedLocal: string;
1225
+ /**
1226
+ * The `Error` output as a state row, allocated **by the read** (§14.2's rule, fifth
1227
+ * instance). Only ever set for `newTab`: in-tab, neither of the node's two failures can
1228
+ * fire, so there is no read to earn it.
1229
+ */
1230
+ errorState?: string;
1231
+ /**
1232
+ * The `const` the failure arm binds the message to, set only where something inside that
1233
+ * arm reads `Error` (EXP-011 §24). Minted by the read, like the row above it — the
1234
+ * commonest node here reads neither and emits neither.
1235
+ */
1236
+ errorLocal?: string;
1237
+ then: HandlerAction[];
1238
+ /** The `Failure` chain — reachable only for `newTab`, where the browser can refuse. */
1239
+ failThen: HandlerAction[];
1240
+ completedThen: HandlerAction[];
1241
+ }
1242
+ /**
1243
+ * `External Link` (EXP-011 Tier 2.5) — `window.open`, plus the two outcome chains the
1244
+ * runtime's own fork owes (`externallink.ts`).
1245
+ *
1246
+ * The emitted shape is the interpreter's, guard for guard:
1247
+ *
1248
+ * ```
1249
+ * const link = donateUrl; // bound once — the guard and the open must
1250
+ * if (link === undefined || link === null || link === '') { …failThen } // read one value
1251
+ * else if (!window.open(link, '_blank', 'noopener,noreferrer')) { …failThen }
1252
+ * else { …then }
1253
+ * ```
1254
+ *
1255
+ * 🔴 **`Open In New Tab` unset is `true`, not `undefined`.** The port declares `default: true`
1256
+ * and `registerInput` writes a declared default straight into `_inputValues` (`node.ts:138`),
1257
+ * so `getInputValue` never sees the `undefined` its own target expression tests for — which is
1258
+ * what makes {@link newTab} a plain boolean here rather than a third state.
1259
+ *
1260
+ * That question is worth asking because the runtime reads this one input **two different ways
1261
+ * in two adjacent lines**: `params` is truthiness (`openInNewTab ? … : ''`) and `target` is
1262
+ * strict equality (`=== true || === undefined`). They agree for `true`, for `false` and for
1263
+ * the unset port — every value the *editor* can put there — and disagree for every other
1264
+ * truthy value, which only a wire can deliver. So a wired `Open In New Tab` defers, and the
1265
+ * three cases that remain are the three an author can actually author.
1266
+ *
1267
+ * ⚠️ **There is no `unchanged` arm and its absence is a decision.** The runtime reports
1268
+ * `unchanged` when `typeof window === 'undefined'` — a server-side render. The scaffold
1269
+ * mounts with `createRoot` (`scaffold.ts:249`) and has no server pass at all, so that chain
1270
+ * is unreachable in the exported app exactly as it is in the app running in a browser. The
1271
+ * wire is dropped with a note rather than deferring the node, on `Clear Array`'s rule: a wire
1272
+ * already dead in the interpreter must not cost a translation.
1273
+ */
1274
+ | {
1275
+ kind: 'external-link';
1276
+ /** The node, so the attachment pass and the late row sweep can find it (§14). */
1277
+ nodeId: string;
1278
+ link: ValueExpr;
1279
+ /** `Open In New Tab` — resolved here, because only an unwired port reaches this action. */
1280
+ newTab: boolean;
1281
+ /**
1282
+ * The chain-local the link binds to, emitted only where the empty-link guard needs it —
1283
+ * the guard and the `window.open` must read one value, not two evaluations of one
1284
+ * expression (a link read from a Variable can change between them).
1285
+ */
1286
+ local: string;
1287
+ /**
1288
+ * The local the blocked-tab test binds to, emitted only for a new tab whose outcome is
1289
+ * read (EXP-011 §14).
1290
+ *
1291
+ * 🔴 **The binding is the correctness, and it is not a tidiness choice.** `window.open`
1292
+ * **consumes** the transient activation — measured in Chrome 151, one control arm varying
1293
+ * only whether the call sits between two reads of `navigator.userActivation.isActive`:
1294
+ * without it both reads are `true`, with it the second is `false` while a tab opens. So
1295
+ * the emitted test had to move *before* the call, exactly where the runtime reads it
1296
+ * (`externallink.ts:96`). Read after, it is `false` on every tab successfully opened —
1297
+ * which is DEF-016's own symptom, reintroduced on the export side by DEF-016's fix.
1298
+ */
1299
+ blockedLocal: string;
1300
+ /**
1301
+ * False only where the link is provably non-empty — a non-empty literal. The runtime
1302
+ * refuses before touching the browser on `undefined`, `null` and `''`, and `window.open('')`
1303
+ * opens a blank tab, so this guard is observable even with no Failure chain wired.
1304
+ */
1305
+ guardLink: boolean;
1306
+ /**
1307
+ * The `Error` output as a state row (EXP-011 §14), filled by the late sweep and undefined
1308
+ * where nothing reads it — which is the ordinary case, and why a button opening a literal
1309
+ * url still emits one expression and no `useState`.
1310
+ *
1311
+ * 🔴 **This is the field that makes the two failures tell themselves apart.** Everywhere
1312
+ * else in this action they are one arm, because "no link" and "the browser blocked the
1313
+ * tab" both run the Failure chain and nothing else. `Error` is the one port that
1314
+ * distinguishes them — two static strings, `externallink.ts:62` and `:101` — so the
1315
+ * emitted failure arm re-tests the link to pick the message. Both strings are the node's
1316
+ * own `_internal.lastError`, which is the SHORT one; the longer sentence beside it goes to
1317
+ * the outcome channel, not to this port.
1318
+ */
1319
+ errorState?: string;
1320
+ /**
1321
+ * The `const` the failure arm binds the message to (EXP-011 §24), set only where something
1322
+ * inside that arm reads `Error`.
1323
+ *
1324
+ * 🔴 **It is what stops the message being computed twice.** The write above picks between
1325
+ * two strings by re-testing the link; a chain reading `Error` would otherwise have to print
1326
+ * that same ternary again at every sink, and the two copies would be free to disagree.
1327
+ */
1328
+ errorLocal?: string;
1329
+ then: HandlerAction[];
1330
+ failThen: HandlerAction[];
1331
+ } | {
1332
+ kind: 'emit';
1333
+ channelName: string;
1334
+ payload: Array<{
1335
+ key: string;
1336
+ expr: ValueExpr;
1337
+ }>;
1338
+ }
1339
+ /**
1340
+ * EXP-011 §74. `fallback`: the value typed into the Set's own port UNDER the wire `expr` came from, kept only when
1341
+ * `expr` may read `undefined` — the runtime writes the wire's last defined delivery, else the typed-in value
1342
+ * (measured: `exp-011-s96-a-literal-under-a-wire.test.ts`), so the handler prints `expr ?? fallback`. Absent when
1343
+ * nothing is typed in, or when the wire's source always carries a value (the typed-in value is then never written,
1344
+ * in the runtime or here — a note says so).
1345
+ *
1346
+ * P18-S127-SETDONE (EXP-011 §95). `then`: the Set's Done chain, then its Completed chain, as following statements
1347
+ * (`expandActions`), present only when something hangs off them. The runtime writes, notifies every Variable reading
1348
+ * the name, then sends Done and Completed (`setvariablenode.ts` `scheduleStore`), and a Done's target is updated before
1349
+ * the click's next wire (it is flagged dirty with its sender and pulls it first): depth first, as printed.
1350
+ */
1351
+ | {
1352
+ kind: 'store-set';
1353
+ variableName: string;
1354
+ expr: ValueExpr;
1355
+ fallback?: string | number | boolean;
1356
+ then?: HandlerAction[];
1357
+ } | {
1358
+ kind: 'globalstore-set';
1359
+ storeName: string;
1360
+ key: string;
1361
+ expr: ValueExpr;
1362
+ fallback?: string | number | boolean;
1363
+ }
1364
+ /**
1365
+ * `Set Object Properties` on a named Object (EXP-011 §47): one patch over every wired property
1366
+ * the node's list admits — `profile.set({ name: …, city: … })` — then its Done chain as
1367
+ * following statements. No failure arm: with a literal Id the runtime's only failure (no
1368
+ * object bound) is unreachable, because `Model.get` creates the object on read.
1369
+ */
1370
+ | {
1371
+ kind: 'object-set';
1372
+ storeName: string;
1373
+ entries: Array<{
1374
+ key: string;
1375
+ expr: ValueExpr;
1376
+ fallback?: string | number | boolean;
1377
+ }>;
1378
+ then: HandlerAction[];
1379
+ }
1380
+ /**
1381
+ * EXP-011 §60. `Set Component Object Properties` (`parent: false`) — `<local>.set({ … })`, then Done, then Completed, as
1382
+ * following statements (one arm, always taken: the self variant cannot miss its own record). `Set Parent Component
1383
+ * Object Properties` (`parent: true`) — the block form: `if (<local> === undefined) { raise no-ancestor; failThen }
1384
+ * else { <local>.set({ … }); then }` — the runtime's `resolution.id === undefined` fork in base.ts, verbatim.
1385
+ */
1386
+ | {
1387
+ kind: 'component-object-set';
1388
+ nodeId: string;
1389
+ local: string;
1390
+ parent: boolean;
1391
+ entries: Array<{
1392
+ key: string;
1393
+ expr: ValueExpr;
1394
+ }>;
1395
+ then: HandlerAction[];
1396
+ completedThen: HandlerAction[];
1397
+ failThen: HandlerAction[];
1398
+ } | {
1399
+ kind: 'collection-add';
1400
+ collectionName: string;
1401
+ entries: Array<{
1402
+ key: string;
1403
+ expr: ValueExpr;
1404
+ }>;
1405
+ minted?: MintedTarget;
1406
+ }
1407
+ /**
1408
+ * `Clear Array` (EXP-011 Tier 1.1) — `notes.clear()`, plus the two chains the runtime's own
1409
+ * outcome fork owes.
1410
+ *
1411
+ * `collectionnode-clear.ts` measures `wasEmpty` **before** `set([])` and reports `unchanged`
1412
+ * for an array that was already empty, `done` otherwise (ERG-001: "the post-condition already
1413
+ * held"). So a `done` chain emitted unconditionally would fire where the interpreter stays
1414
+ * silent. The emitted form tests the length instead of keeping a local, which is exact rather
1415
+ * than merely close: `Collection.clear()` is itself `if (length === 0) return`, so skipping
1416
+ * the call on the empty arm is the same no-op the guard would have made.
1417
+ */
1418
+ | {
1419
+ kind: 'collection-clear';
1420
+ collectionName: string;
1421
+ then: HandlerAction[];
1422
+ unchangedThen: HandlerAction[];
1423
+ minted?: MintedTarget;
1424
+ }
1425
+ /**
1426
+ * `Remove Object From Array` (EXP-011 §30) — `notes.remove(item)`, inside the row callback of
1427
+ * the repeater named by {@link repeaterId}.
1428
+ *
1429
+ * 🔴 **This action is only meaningful in one place, and the type says so.** The node's Object
1430
+ * Id is a `For Each`'s `itemActionItemId`, which the runtime sets to `model.getId()` of the
1431
+ * row that fired *immediately before* pulsing `itemOutputSignal-<name>` (`foreach.tsx`). Read
1432
+ * anywhere else it is "whichever row fired last", which the emitted list holds no state for —
1433
+ * so `actionsValidIn` refuses this action outside that repeater's own row signal, and the row
1434
+ * the interpreter names becomes the `item` the callback already closes over.
1435
+ *
1436
+ * There is no `unchangedThen` and no failure arm because in this shape neither can fire, and
1437
+ * the compile proves it rather than assuming it: see `compileCollectionRemove`.
1438
+ */
1439
+ | {
1440
+ kind: 'collection-remove';
1441
+ collectionName: string;
1442
+ repeaterId: string;
1443
+ then: HandlerAction[];
1444
+ minted?: MintedTarget;
1445
+ }
1446
+ /**
1447
+ * A Condition in a handler chain: `if (cond) whenTrue; else whenFalse;` (LOGIC-TARGET §3). `flat` (EXP-011 §87): a
1448
+ * Condition whose answer is a literal and which writes its sinks' cells has one side; its `cond` is `true`, its
1449
+ * `whenFalse` empty, and `whenTrue` prints as following statements.
1450
+ */
1451
+ | {
1452
+ kind: 'branch';
1453
+ cond: ValueExpr;
1454
+ whenTrue: HandlerAction[];
1455
+ whenFalse: HandlerAction[];
1456
+ flat?: true;
1457
+ }
1458
+ /** Fires the component's own signal output: `onWaved?.()` (COMPONENT-OUTPUTS-TARGET §4). */
1459
+ | {
1460
+ kind: 'output-signal';
1461
+ prop: string;
1462
+ }
1463
+ /**
1464
+ * Opens a popup slot: `setOpenPopup('AboutDialog')`, then the Show Popup's `done`-chain as
1465
+ * following statements in the same handler (POPUPS-TARGET §3).
1466
+ */
1467
+ | {
1468
+ kind: 'popup-show';
1469
+ slotKey: string;
1470
+ then: HandlerAction[];
1471
+ }
1472
+ /**
1473
+ * Closes the enclosing popup through the reserved prop (POPUPS-TARGET §4): with a
1474
+ * `done`-chain, `if (onClose) { onClose('ok'); …then }`; without one, `onClose?.('ok')`.
1475
+ * `action` undefined is the plain `Close` (the runtime's `Closed` outcome).
1476
+ */
1477
+ | {
1478
+ kind: 'popup-close';
1479
+ action?: string;
1480
+ then: HandlerAction[];
1481
+ }
1482
+ /**
1483
+ * A pure Function/Expression fired from a handler chain (EXP-003 §4 A2h): the actions its
1484
+ * `done` wires described, in wire order. The node's own compute needs no statement — output
1485
+ * reads inside the chain inline the call at their sinks, and a pure body run without reading
1486
+ * its outputs is unobservable. `done` is invocation-only in both runtimes, so this is exact.
1487
+ * `materialize` (CONTROLLED-STATE-TARGET §4f) names the state var the run writes when the
1488
+ * node's outputs also feed render sinks: `setStockCheckOut(stockCheck({ … }))` precedes the
1489
+ * chain, and render reads go through the var.
1490
+ * `ticked` (P18-S119-TICKEDRUN) marks the run a ticked input's new value starts (`TickedRunPlan`): it has no Done, and
1491
+ * its effect has already recorded the values it read.
1492
+ */
1493
+ | {
1494
+ kind: 'jsfun-run';
1495
+ nodeId: string;
1496
+ then: HandlerAction[];
1497
+ materialize?: string;
1498
+ ticked?: true;
1499
+ /**
1500
+ * EXP-019 slice 1b (session 134). The body awaits at its top level: where in `then` the chain the runtime sends after
1501
+ * the run's promise settles begins (Success, then Done), after the chains of the signals the body sent before its
1502
+ * first `await`. Printed whole (`runFunction`, the pulses, `await <run>.settled`, the rest) and awaited like a request,
1503
+ * so §90's `detached` lets the click's later wires run before it settles.
1504
+ */
1505
+ awaitsAt?: number;
1506
+ /** EXP-011 §90, as an api call's. */
1507
+ detached?: true;
1508
+ /**
1509
+ * P18-EXP022-S4-ADDREFUSED. The inputs a record verb's Id feeds (`atRun` on the plan's input), read at this run: the
1510
+ * Id of the verb whose Done chain runs it, which the runtime set just before that Done.
1511
+ */
1512
+ inputsAtRun?: Array<{
1513
+ name: string;
1514
+ expr: ValueExpr;
1515
+ }>;
1516
+ }
1517
+ /**
1518
+ * EXP-011 §86. The chains of a Function's own signals (`Outputs.go()`), first in its run's `then`: one loop over the
1519
+ * pulses the run sent, in the order it sent them, each arm (`jsfun-pulse`) the chain of one signal. Inside an arm the
1520
+ * Function's outputs read as that pulse's targets read them (C7 lockstep, `src/lib/functionRun.ts`).
1521
+ */
1522
+ | {
1523
+ kind: 'jsfun-pulses';
1524
+ nodeId: string;
1525
+ then: HandlerAction[];
1526
+ }
1527
+ /** EXP-011 §86. One signal's chain inside `jsfun-pulses`: the actions its wires describe, in wire order. */
1528
+ | {
1529
+ kind: 'jsfun-pulse';
1530
+ nodeId: string;
1531
+ signal: string;
1532
+ then: HandlerAction[];
1533
+ }
1534
+ /**
1535
+ * A state var write (CONTROLLED-STATE-TARGET §3.3). `op` is a functional update
1536
+ * (`setX(v => !v)`) — immune to closure staleness, which is why Switch's `flip` and
1537
+ * Counter's arithmetic use it, never `expr`.
1538
+ */
1539
+ | {
1540
+ kind: 'state-set';
1541
+ name: string;
1542
+ expr?: ValueExpr;
1543
+ op?: 'toggle' | 'inc' | 'dec';
1544
+ field?: FieldWrite;
1545
+ }
1546
+ /**
1547
+ * P18-S128-FIELDSET (EXP-011 §97). A text field's Focus or Blur: `focus()` / `blur()` on the exported `<input>` through a
1548
+ * ref, as the runtime's focus tracker calls them on the viewer's own (`_focus`, `_blur`). A field that is not on the page
1549
+ * takes nothing, and the Focus is not held (GAM-012 R13): the ref is `null` then.
1550
+ */
1551
+ | {
1552
+ kind: 'field-focus';
1553
+ inputId: string;
1554
+ focus: boolean;
1555
+ }
1556
+ /**
1557
+ * An awaited call into an api stub (RECORD-VERBS-TARGET §4, USER-FAMILY-TARGET §4e): the
1558
+ * vocabulary's asynchronous action. The emitted handler becomes `async`, the call is awaited,
1559
+ * `then` is the `done` chain — which is where `reportOutcomes(…, 'done')` sits in the runtime,
1560
+ * after the service answers — and the catch writes `errorState`, the node's `Error` output,
1561
+ * which the runtime never clears once set.
1562
+ *
1563
+ * ⚠️ **One kind, two families.** The record verbs and the user family emit the *same* action;
1564
+ * they differ only in `fnName` and in the shape of `args`. USER-FAMILY §4e records why this is
1565
+ * a rename of `record-op` rather than a sibling case beside it: a new discriminant recruits
1566
+ * every switch site silently and the wrong ones fail without throwing (s19's dispatcher trap),
1567
+ * whereas a rename is a compile error at each site until it is handled.
1568
+ */
1569
+ | {
1570
+ kind: 'api-call';
1571
+ /**
1572
+ * EXP-011 §90. Set by the planner's last pass where a later action of the same list follows it: printed as
1573
+ * `void (async () => { … })();`, so the click's later wires run before the answer arrives, as the runtime's do
1574
+ * (each node acts after the update; the request answers later). A failure before the first await (the
1575
+ * `Missing Record Id` guard) still runs its chain first, as the runtime's synchronous `setError` does.
1576
+ */
1577
+ detached?: true;
1578
+ nodeId: string;
1579
+ /** What produced the call — for notes and the stub's provenance line. EXP-011 §62: the relation pair rides the same action. */
1580
+ verb: 'create' | 'update' | 'delete' | RelationVerb | UserVerb;
1581
+ /** The api module's exported function: `createPuppy` / `updatePuppy` / `logIn` / `logOut`. */
1582
+ fnName: string;
1583
+ /**
1584
+ * The call's arguments, in position order. `updatePuppy(id, {…})` is `[expr, data]`,
1585
+ * `logIn(u, p)` is `[expr, expr]`, `logOut()` is `[]`.
1586
+ */
1587
+ args: ApiCallArg[];
1588
+ /**
1589
+ * Refuse before calling when the leading id argument is absent or empty, the way the
1590
+ * runtime does (RECORD-VERBS-TARGET §1): `setModelID` reads `undefined`/`null`/`''` as
1591
+ * *clear the binding*, after which every verb answers `setError('Missing Record Id')` and
1592
+ * never reaches the backend. Gate 9 already defers an id that is **statically** absent;
1593
+ * this is its dynamic twin, and the corpus reaches it because every emitted component prop
1594
+ * is optional, so an id wired from one is `string | undefined` at the call.
1595
+ *
1596
+ * False only where the guard is provably dead — a non-empty literal id, and every `create`,
1597
+ * which takes no id at all.
1598
+ */
1599
+ guardId: boolean;
1600
+ /**
1601
+ * EXP-011 §72. The id argument is read from a source typed `unknown` — a Variable nothing
1602
+ * statically writes, or one written by a Function or an HTTP body — and the client's
1603
+ * parameter is `string`: the emitter prints `String(x ?? '')` around the read. The runtime
1604
+ * keys `Model.get(id)` by the value and the client `encodeURIComponent`s it, so the
1605
+ * coercion transcribes both; an empty read still meets `guardId`'s `Missing Record Id`.
1606
+ * Absent for a `string` source — a `string | undefined` narrows on its own.
1607
+ */
1608
+ coerceId?: 'string';
1609
+ /**
1610
+ * EXP-011 §62. The relation verbs' dynamic pre-flight, in the runtime's own order and its own
1611
+ * words (`validateInputs`): a positional argument that is empty at the click throws that
1612
+ * sentence into the catch — *No target record Id (the record to add a relation to) specified*
1613
+ * before *No record Id specified (the record that should get the relation)*, because the
1614
+ * runtime asks about the target first. Printed after `guardId`'s line; emitted only for an
1615
+ * argument that is not a literal (a literal is never empty — the planner refused that).
1616
+ */
1617
+ guards?: Array<{
1618
+ index: number;
1619
+ local: string;
1620
+ message: string;
1621
+ coerce?: 'string';
1622
+ }>;
1623
+ errorState: string;
1624
+ /**
1625
+ * EXP-011 §44 — write `undefined` to the Error row the moment the call answers, before the
1626
+ * done chain: `Request Magic Link` is the one verb whose success clears its Error. Every
1627
+ * other `api-call` keeps the runtime's "never cleared" (RECORD-VERBS-TARGET §1).
1628
+ */
1629
+ clearErrorOnDone?: true;
1630
+ /**
1631
+ * P18-EXP022-S4-ADDREFUSED. Set where the Done chain reads this verb's Id (`verb-id`): the awaited answer is bound to
1632
+ * this `const`, and the read is its `id`. Absent, the answer is dropped as before.
1633
+ */
1634
+ recordLocal?: string;
1635
+ /**
1636
+ * P18-EXP022-S4-ADDREFUSED. A Create's Id row, where a placed component's input reads it (`verb-id`'s row form): written
1637
+ * from `recordLocal` before the Done chain, so a pulse the chain sends that component arrives in the same commit.
1638
+ */
1639
+ idState?: string;
1640
+ then: HandlerAction[];
1641
+ /**
1642
+ * EXP-011 §90. The `failure` chain, printed in the catch after the Error row and the raise: the runtime's
1643
+ * `setError` writes the row, raises, then pulses Failure (`node.ts` `reportOutcome`). A read of this node's Error
1644
+ * inside it is the catch's `<errorState>Message` local (`outcome-error`), never the row.
1645
+ */
1646
+ failThen: HandlerAction[];
1647
+ }
1648
+ /**
1649
+ * An `HTTP Request`'s `Fetch` (EXP-011 Tier 1.2) — the vocabulary's second asynchronous action,
1650
+ * and the first whose *failure* is a chain rather than only a value.
1651
+ *
1652
+ * The emitted shape is the runtime's own three outcomes, not two:
1653
+ *
1654
+ * ```
1655
+ * try {
1656
+ * const answer = await fetchQuote({ … }); // throws only where no answer arrived
1657
+ * setQuoteOut(answer); // response/statusCode/headers/fields — set for ANY answer
1658
+ * if (answer.ok) { …then }
1659
+ * else { const message = answer.error; setQuoteError(message); …failThen }
1660
+ * } catch (error) {
1661
+ * const message = error instanceof Error ? error.message : String(error);
1662
+ * setQuoteError(message); …failThen
1663
+ * }
1664
+ * ```
1665
+ *
1666
+ * 🔴 **A non-2xx still writes the value outputs, and a network error still does not.**
1667
+ * `processResponse` runs before `doFetch` reports `failure`, so a 404's body and status are on
1668
+ * the node's outputs while `Failure` fires; a request that never reached the server leaves
1669
+ * `Response` and `Status Code` holding exactly what they held. Splitting the answer from the
1670
+ * throw is what reproduces both — the record verbs' single try/catch could not, because for
1671
+ * them a refused write has nothing to publish.
1672
+ *
1673
+ * `failThen` is therefore emitted **twice**, once per arm, and the two arms are identical by
1674
+ * construction because both bind `message` first. A join would need a `finally` that can tell
1675
+ * which arm it is in, which is a variable the arms already are.
1676
+ */
1677
+ | {
1678
+ kind: 'http-call';
1679
+ /** EXP-011 §90, as `api-call`'s. */
1680
+ detached?: true;
1681
+ nodeId: string;
1682
+ /** `src/api/http.ts`'s exported function for this node. */
1683
+ fnName: string;
1684
+ /** Wired inputs, in the emitted parameter order — `{ tag: tagText }` at the call. */
1685
+ args: Array<{
1686
+ param: string;
1687
+ expr: ValueExpr;
1688
+ }>;
1689
+ /** The state row the answer is written to, when anything outside the chain reads a value. */
1690
+ materialize?: string;
1691
+ /** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
1692
+ errorState: string;
1693
+ then: HandlerAction[];
1694
+ failThen: HandlerAction[];
1695
+ }
1696
+ /**
1697
+ * A `Cloud Function`'s `Call` (EXP-011 §41) — the request's shape one hop in: the function
1698
+ * throws where the interpreter reports Failure (an answered error or an unreachable backend),
1699
+ * so the two outcome chains are a try/catch's two arms.
1700
+ *
1701
+ * ```
1702
+ * try {
1703
+ * const answer = await callPublishPage({ pageId }); // throws on any failure
1704
+ * setPublishOut(answer); // only where something outside the chain reads a result
1705
+ * …then
1706
+ * } catch (error) {
1707
+ * const message = error instanceof Error ? error.message : String(error);
1708
+ * setPublishError(message); …failThen
1709
+ * }
1710
+ * ```
1711
+ */
1712
+ | {
1713
+ kind: 'cloud-call';
1714
+ /** EXP-011 §90, as `api-call`'s. */
1715
+ detached?: true;
1716
+ nodeId: string;
1717
+ /** `src/api/functions.ts`'s exported function for this node. */
1718
+ fnName: string;
1719
+ /** Wired parameters, in the emitted parameter order — `{ pageId: pageIdValue }` at the call. */
1720
+ args: Array<{
1721
+ param: string;
1722
+ expr: ValueExpr;
1723
+ }>;
1724
+ /** The results row, when anything outside the chain reads a result. */
1725
+ materialize?: string;
1726
+ /** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
1727
+ errorState: string;
1728
+ then: HandlerAction[];
1729
+ failThen: HandlerAction[];
1730
+ }
1731
+ /**
1732
+ * A `Record`'s `Fetch` (EXP-011 §43) — the Cloud Function shape with one argument, the Id:
1733
+ *
1734
+ * ```
1735
+ * try {
1736
+ * const pageRecord = await fetchPageById(pageId.get());
1737
+ * setPageRow(pageRecord); // only where something outside the chain reads a column
1738
+ * …then
1739
+ * } catch (error) {
1740
+ * const message = error instanceof Error ? error.message : String(error);
1741
+ * setPageError(message); …failThen
1742
+ * }
1743
+ * ```
1744
+ * An empty Id throws `Missing Id.` inside the try — the runtime's own sentence, reported as
1745
+ * Failure before any request (`scheduleFetch`) — so it lands in the catch like every other failure.
1746
+ */
1747
+ | {
1748
+ kind: 'record-fetch';
1749
+ /** EXP-011 §90, as `api-call`'s. */
1750
+ detached?: true;
1751
+ nodeId: string;
1752
+ /** The collection module's exported `fetch<Type>ById`. */
1753
+ fnName: string;
1754
+ id: ValueExpr;
1755
+ /** EXP-011 §72. `id` is typed `unknown` — printed `String(id ?? '')`, as `api-call`'s `coerceId`. */
1756
+ coerceId?: 'string';
1757
+ /** The record row, when anything outside the chain reads a column. */
1758
+ materialize?: string;
1759
+ /** The `Error` output's row — written on every failure, never cleared (the runtime's own). */
1760
+ errorState: string;
1761
+ then: HandlerAction[];
1762
+ failThen: HandlerAction[];
1763
+ }
1764
+ /**
1765
+ * `Open File Picker`'s `Open` (EXP-011 §45) — `pickFile()` from `src/lib/util.ts`, which is the
1766
+ * node's `<input type=file>` and its two listeners as one promise: a `File` on `change` with a
1767
+ * file, `undefined` on `change` with none or on `cancel`, a rejection where `click()` threw.
1768
+ *
1769
+ * ```
1770
+ * try {
1771
+ * const picked = await pickFile({ accept: 'image/*' });
1772
+ * if (picked === undefined) { …unchangedThen }
1773
+ * else { setPickedFile(picked); …then }
1774
+ * } catch (error) {
1775
+ * const message = error instanceof Error ? error.message : String(error);
1776
+ * setPickError(message); …failThen
1777
+ * }
1778
+ * ```
1779
+ * The row is written only on Done — Unchanged leaves every output exactly as it was, which is
1780
+ * the port's definition, and Failure writes only the Error (openfilepicker.ts).
1781
+ */
1782
+ | {
1783
+ kind: 'file-pick';
1784
+ /** EXP-011 §90, as `api-call`'s. */
1785
+ detached?: true;
1786
+ nodeId: string;
1787
+ accept?: ValueExpr;
1788
+ capture?: ValueExpr;
1789
+ /** The chosen-file row, when anything outside the Done chain reads the file or its metadata. */
1790
+ materialize?: string;
1791
+ errorState: string;
1792
+ then: HandlerAction[];
1793
+ unchangedThen: HandlerAction[];
1794
+ failThen: HandlerAction[];
1795
+ }
1796
+ /**
1797
+ * `Upload File`'s `Upload` (EXP-011 §45) — the Cloud Function shape with the file as the one
1798
+ * argument: `uploadFile(file, { private })` from `src/api/files.ts`, which is the client's
1799
+ * `POST /files/<name>` with the bytes as the body and `X-NodeGX-File-Private` where asked.
1800
+ * `No file specified` is thrown inside the try where the file is unset — the node's own
1801
+ * sentence, reported as Failure before any request (uploadfile.ts).
1802
+ */
1803
+ | {
1804
+ kind: 'file-upload';
1805
+ /** EXP-011 §90, as `api-call`'s. */
1806
+ detached?: true;
1807
+ nodeId: string;
1808
+ file: ValueExpr;
1809
+ /** `Private` — absent where unset or authored false; a literal `true` or the wired expression otherwise. */
1810
+ isPrivate?: ValueExpr;
1811
+ materialize?: string;
1812
+ errorState: string;
1813
+ then: HandlerAction[];
1814
+ failThen: HandlerAction[];
1815
+ }
1816
+ /**
1817
+ * `Sign File URL`'s `Sign` (EXP-011 §45) — `signFileUrl(file)` from `src/api/files.ts`, the
1818
+ * client's `GET /files/<name>/sign`; `No file specified` thrown for an unset file, as above.
1819
+ */
1820
+ | {
1821
+ kind: 'file-sign';
1822
+ /** EXP-011 §90, as `api-call`'s. */
1823
+ detached?: true;
1824
+ nodeId: string;
1825
+ file: ValueExpr;
1826
+ materialize?: string;
1827
+ errorState: string;
1828
+ then: HandlerAction[];
1829
+ failThen: HandlerAction[];
1830
+ }
1831
+ /**
1832
+ * `Now`'s `Read` (EXP-011 Tier 1.3) — re-read the clock, then run the `done` chain.
1833
+ *
1834
+ * ```
1835
+ * const nowRead = new Date(); // bound, so every read in the chain is the SAME instant
1836
+ * setNow(nowRead); // only where something outside the chain reads it
1837
+ * …then
1838
+ * ```
1839
+ *
1840
+ * Synchronous, so `done` is the statements that follow rather than an arm: `_read` flags the
1841
+ * three outputs dirty and reports `done` in the same call, with no branch where it declines.
1842
+ * There is deliberately no failure arm — the node's own comment says reading the clock cannot
1843
+ * fail, and two Reads in the same millisecond still re-read.
1844
+ *
1845
+ * `materialize` is filled in the verdict sweep, never at compile time: a render read of
1846
+ * `Timestamp` resolves passes later, so asking "does anything read this" here answers *no* for
1847
+ * every render read there is (§8.3's allocation-order rule, third instance).
1848
+ */
1849
+ | {
1850
+ kind: 'date-now-read';
1851
+ nodeId: string;
1852
+ /** The chain-local the instant binds to — what a read inside `then` says. */
1853
+ local: string;
1854
+ /** The state row the instant is written to, where anything outside the chain reads it. */
1855
+ materialize?: string;
1856
+ then: HandlerAction[];
1857
+ } | IdNewAction | CryptoCallAction | StackPushAction | StackPopAction | StackResetAction | ArrayNewAction | LogAction | DelayAction | StatesGoAction | DragSnapAction | ScriptSignalAction | RunTasksAction | QueryFetchAction | StreamAction;
1858
+ /**
1859
+ * A streaming node's action port pulsed (EXP-011 §58) — `<local>.<verb>(<data>)`. `value` is the data port's
1860
+ * expression, read AT THE PULSE (the runtime appends the last delivered Chunk / Data when the pulse lands, and
1861
+ * the chain that pulses may have written it a statement earlier — `runtasks-run`'s rule); absent when the port
1862
+ * has no source, in which case the call takes no argument and the setter never ran. The node's signal outputs
1863
+ * are its listeners ({@link StreamPlan.listeners}), passed once to the hook.
1864
+ */
1865
+ export type StreamAction = {
1866
+ kind: 'stream-action';
1867
+ nodeId: string;
1868
+ local: string;
1869
+ verb: string;
1870
+ value?: ValueExpr;
1871
+ };
1872
+ /**
1873
+ * A `States` node's `Toggle` or `To <state>` (EXP-011 §49) — one call on the `useStates` handle:
1874
+ * `<local>.toggle()` or `<local>.goTo('<state>')`.
1875
+ *
1876
+ * ⚠️ **No chains ride on the action, and that is the node's own shape, not a shortcut.** Done,
1877
+ * Unchanged and Failure are *ports of the node*, pulsed by `reportOutcome` whichever trigger asked
1878
+ * — a chain wired off `done` fires for a Toggle and for every `To <state>` alike — so they are
1879
+ * listeners on the node ({@link StatesPlan.listeners}), compiled once, not arms printed at each
1880
+ * call site. The interpreter drains requests after the pass, so the outcome arrives from a
1881
+ * microtask; the handler that asked has already finished, exactly as it has there.
1882
+ */
1883
+ export type StatesGoAction = {
1884
+ kind: 'states-go';
1885
+ nodeId: string;
1886
+ /** The `useStates` local. */
1887
+ local: string;
1888
+ verb: 'toggle' | 'to';
1889
+ /** The state a `To <state>` names; absent for Toggle. */
1890
+ state?: string;
1891
+ };
1892
+ /**
1893
+ * A `Drag`'s Snap To Position X / Y — Do (EXP-011 §63): one call on the handle, always an expression. Its Done is the
1894
+ * node's listener — `outcomeOnInnerComponent` reports `done` for every snap call whichever port asked — so it is passed
1895
+ * once to the hook, not printed as an arm here; Failure cannot fire in the emitted app and is dropped with a note.
1896
+ */
1897
+ export type DragSnapAction = {
1898
+ kind: 'drag-snap';
1899
+ nodeId: string;
1900
+ /** The `useDrag` local. */
1901
+ local: string;
1902
+ axis: 'x' | 'y';
1903
+ };
1904
+ /**
1905
+ * A `Log`'s `Log` (EXP-011 §39) — `log(level, message, data)` into `src/lib/util.ts`, then the
1906
+ * Done chain as following statements (`popup-show`'s treatment: the write is synchronous and has
1907
+ * one outcome, so the chain is not an arm).
1908
+ *
1909
+ * `message` is `undefined` where nothing is authored or wired — the helper prints `''` for it,
1910
+ * as `_write` does. `data` is absent (not `undefined`) where the port is unset, because the
1911
+ * runtime writes the data argument only when one arrived, and `console.info('x', undefined)`
1912
+ * prints a trailing `undefined` the interpreter never prints.
1913
+ */
1914
+ export type LogAction = {
1915
+ kind: 'log';
1916
+ nodeId: string;
1917
+ level: LogLevel;
1918
+ message: ValueExpr;
1919
+ data?: ValueExpr;
1920
+ then: HandlerAction[];
1921
+ };
1922
+ /**
1923
+ * One of a `Delay`'s three actions (EXP-011 §39) — `startDelay`/`restartDelay`/`stopDelay` from
1924
+ * `src/lib/timer.ts` over a `useRef` the component declares per node.
1925
+ *
1926
+ * Four chains, and which ones an arm prints depends on the verb:
1927
+ *
1928
+ * | verb | `then` (Done) | `unchangedThen` | `startedThen` / `finishedThen` |
1929
+ * |---|---|---|---|
1930
+ * | `start` | began a countdown | one was already running | passed as the two callbacks |
1931
+ * | `restart` | always | never — Restart cannot no-op (timer.ts) | passed as the two callbacks |
1932
+ * | `stop` | abandoned a running countdown | nothing was running | not this verb's — a stopped countdown never fires them |
1933
+ *
1934
+ * `startDelay` and `duration` are read where the handler is, so a `Set Variable` earlier in the
1935
+ * chain reaches them (the snapshot rule). The two callback chains run *later*, in a timeout, and
1936
+ * close over the same handler scope — the shape `http-call`'s continuations already have.
1937
+ */
1938
+ export type DelayAction = {
1939
+ kind: 'delay';
1940
+ nodeId: string;
1941
+ verb: TimerTrigger;
1942
+ /** The `useRef` local this node's timer handle lives in — one per node, shared by its verbs. */
1943
+ ref: string;
1944
+ startDelay: ValueExpr;
1945
+ duration: ValueExpr;
1946
+ then: HandlerAction[];
1947
+ unchangedThen: HandlerAction[];
1948
+ startedThen: HandlerAction[];
1949
+ finishedThen: HandlerAction[];
1950
+ };
1951
+ /**
1952
+ * A `Unique Id`'s or `UUID`'s `New` (EXP-011 §37) — `date-now-read`'s shape, with a failure arm
1953
+ * on one of the two nodes and none on the other.
1954
+ *
1955
+ * 🔴 **One kind for both nodes, discriminated by `fn`.** §9.4 counted eleven sites that enumerate
1956
+ * this union and named the five the compiler does not hold; a second action kind would have paid
1957
+ * that toll twice for two nodes whose only structural difference is whether `failThen` can be
1958
+ * non-empty. `ID_NODES.canFail` is the fact, and it is stated once.
1959
+ *
1960
+ * Like `date-now-read`, `materialize` and `errorMaterialize` are filled in the verdict sweep and
1961
+ * never at compile time — a render read of `Id` resolves passes later, so asking "does anything
1962
+ * read this" here answers *no* for every render read there is (§8.3's allocation-order rule).
1963
+ */
1964
+ /**
1965
+ * EXP-011 §55. A mutator's Array Id fed by a `Create New Array`'s `id` — the handle, never a string.
1966
+ *
1967
+ * `stateName` is the minting component's row; `viaLocal` is set when the mutator sits inside the mint's own
1968
+ * Done chain (the chain-local is the array just made). Outside that chain the handle can be `null` — no Do
1969
+ * has fired — which is the runtime's `<prefix>/no-array` Failure: the emitter prints the guard, the raise on
1970
+ * the error channel (§54) with the node's own message, and `failThen`. `Remove` never guards: its row exists
1971
+ * only because the handle did (§30's gate 3, restated over the handle).
1972
+ */
1973
+ export type MintedTarget = {
1974
+ /** The `Create New Array` node. */
1975
+ nodeId: string;
1976
+ stateName: string;
1977
+ viaLocal?: string;
1978
+ /** The mutator node — the raise's provenance. */
1979
+ sinkId: string;
1980
+ action: 'insert' | 'clear' | 'remove';
1981
+ failThen: HandlerAction[];
1982
+ };
1983
+ /**
1984
+ * EXP-011 §55. `Create New Array`'s Do: `const <local> = collection<T>([...<source>]); set<Row>(<local>);`
1985
+ * then the Done chain. `source` absent is the empty literal (`items` never received). Every Do mints another
1986
+ * array, exactly as `Collection.get()` with no name does.
1987
+ */
1988
+ export type ArrayNewAction = {
1989
+ kind: 'array-new';
1990
+ nodeId: string;
1991
+ local: string;
1992
+ stateName: string;
1993
+ rowType: string;
1994
+ source?: ValueExpr;
1995
+ /** Whether `source` is statically a list; an untyped source is copied when it is an array and empty otherwise. */
1996
+ sourceIsList?: boolean;
1997
+ then: HandlerAction[];
1998
+ };
1999
+ /** EXP-011 §55. One `Create New Array`'s handle row, and the row type the emitter spells it with. */
2000
+ export interface MintedArrayPlan {
2001
+ nodeId: string;
2002
+ stateName: string;
2003
+ /** `any`, or a named array's interface when the snapshot source is that array (or a filter over it) and nothing inserts. */
2004
+ rowType: string;
2005
+ /** The named array whose interface `rowType` names, for the import. */
2006
+ rowCollection?: string;
2007
+ }
2008
+ /**
2009
+ * A `Hash`'s Do or a `Random Bytes`' New (EXP-011 §59) — {@link IdNewAction}'s two-arm shape with a
2010
+ * value row that boots empty, a raise in the Failure arm, and (Hash) an `await`.
2011
+ */
2012
+ export type CryptoCallAction = {
2013
+ kind: 'crypto-call';
2014
+ /** EXP-011 §90, as `api-call`'s. */
2015
+ detached?: true;
2016
+ nodeId: string;
2017
+ node: CryptoNodeKind;
2018
+ fn: 'tryHash' | 'tryRandomBytes' | 'tryExtract';
2019
+ /** The raised code — `hash/failed` / `random-bytes/failed` (the node files). */
2020
+ code: string;
2021
+ /** Whether the call is awaited (`crypto.subtle.digest` is a promise; `getRandomValues` is not). */
2022
+ async: boolean;
2023
+ /**
2024
+ * The verb's arguments in port order. A wire is the expression the handler closes over, a literal
2025
+ * is the literal, and an input nobody set is absent — the lib applies the node's own fallback
2026
+ * there, as `_run` / `_generate` do. `numeric` marks the one argument printed inside `Number(…)`
2027
+ * (Length's setter), and only where a wire delivers it.
2028
+ */
2029
+ inputs: Array<{
2030
+ port: string;
2031
+ expr?: ValueExpr;
2032
+ numeric?: true;
2033
+ }>;
2034
+ /** The chain-local the call binds — the whole result; the Done arm reads `.digest` / `.value`, the Failure arm `.error`. */
2035
+ local: string;
2036
+ /** The value row, where anything outside the chain reads it. Allocated by a read (§14.2's rule). */
2037
+ materialize?: string;
2038
+ /** The Error row, allocated by a read. */
2039
+ errorMaterialize?: string;
2040
+ /**
2041
+ * EXP-011 §76. A `Pattern Extractor`'s rows, one per value output something reads outside the chain —
2042
+ * written from the local's field of the same name BEFORE the arms, on every run, as `doExtract` writes them.
2043
+ */
2044
+ materializeByPort?: Array<{
2045
+ port: string;
2046
+ state: string;
2047
+ }>;
2048
+ then: HandlerAction[];
2049
+ failThen: HandlerAction[];
2050
+ /** EXP-011 §76. The `Found` / `Not Found` chains — printed inside the Done arm, ahead of `then`. Empty for the two verbs. */
2051
+ foundThen: HandlerAction[];
2052
+ notFoundThen: HandlerAction[];
2053
+ };
2054
+ /**
2055
+ * A `Push Component To Stack`'s Navigate (EXP-011 §61) — `navigate.ts` `navigate()`, printed as the runtime's own call:
2056
+ * `pushComponent(name, { target, params, backCallback, hasNavigated, hasUnchanged })` (or `replaceComponent`, which
2057
+ * installs no callback — `replaceAsync` never does). The three failures are excluded statically (a literal Target inside
2058
+ * the stack's Components list, no transitions), so `hasFailed` is never printed and a Failure wire is dropped with a note.
2059
+ */
2060
+ export type StackPushAction = {
2061
+ kind: 'stack-push';
2062
+ nodeId: string;
2063
+ /** The stack's authored name (`'Main'` by default) — the registry key. */
2064
+ stack: string;
2065
+ mode: 'push' | 'replace';
2066
+ /** The target page id, resolved statically against the stack's Components list. */
2067
+ target: string;
2068
+ /** The component the target page shows — the emit resolves each param's prop identifier off its plan. */
2069
+ targetLegacy: string;
2070
+ /** The target's Component Inputs the pusher sets, by port; the prop identifier is the target's own, read at emit. */
2071
+ params: Array<{
2072
+ port: string;
2073
+ expr: ValueExpr;
2074
+ coerce?: 'string';
2075
+ }>;
2076
+ /** The pusher's local stem — the callback's `results` argument is `<local>Results`. */
2077
+ local: string;
2078
+ /** The back-results row, where anything outside the back chains reads a `backResult-<k>`. Allocated by a read. */
2079
+ backResults?: string;
2080
+ /** The `backAction-<a>` chains, by action, in wire order. */
2081
+ backActions: Array<{
2082
+ action: string;
2083
+ then: HandlerAction[];
2084
+ }>;
2085
+ then: HandlerAction[];
2086
+ unchangedThen: HandlerAction[];
2087
+ };
2088
+ /**
2089
+ * A `Pop Component Stack`'s Navigate or one of its `backAction-<a>` inputs (EXP-011 §61) — `navigate-back.ts`
2090
+ * `navigate()`: `const <local> = popComponent(pageStackEntry, { backAction, results })`, the Done arm, and the
2091
+ * Failure arm, which raises `pop-component-stack/no-stack-in-scope` on the channel (the one failure the export can
2092
+ * reach — `transition-in-progress` needs a transition). Always the block form, on the crypto verbs' reason.
2093
+ */
2094
+ export type StackPopAction = {
2095
+ kind: 'stack-pop';
2096
+ nodeId: string;
2097
+ /** The back action this pop was asked under, or none for the plain Navigate. */
2098
+ backAction?: string;
2099
+ /** The `result-<k>` values, read where the setters stored them — the render expression the handler closes over. */
2100
+ results: Array<{
2101
+ key: string;
2102
+ expr: ValueExpr;
2103
+ }>;
2104
+ local: string;
2105
+ then: HandlerAction[];
2106
+ failThen: HandlerAction[];
2107
+ };
2108
+ /** A `Component Stack`'s Reset (EXP-011 §61): `<local>.reset()` then the Done chain — `resetAsync` reports done last. */
2109
+ export type StackResetAction = {
2110
+ kind: 'stack-reset';
2111
+ nodeId: string;
2112
+ local: string;
2113
+ then: HandlerAction[];
2114
+ };
2115
+ export type IdNewAction = {
2116
+ kind: 'id-new';
2117
+ nodeId: string;
2118
+ fn: 'randomId' | 'randomUuid';
2119
+ /**
2120
+ * The chain-local the `New` binds to.
2121
+ *
2122
+ * ⚠️ **It is not the id in both cases.** For `randomId` the local *is* the string; for
2123
+ * `randomUuid` it is the `UuidResult`, and a read of `Id` inside the Done chain is
2124
+ * `<local>.uuid` — which narrows to `string` there and only there. `idLocalReadOf` is the one
2125
+ * place that spelling lives, so the expression side and the action side cannot disagree.
2126
+ */
2127
+ local: string;
2128
+ /** The id row, where anything outside the chain reads it. */
2129
+ materialize?: string;
2130
+ /**
2131
+ * `UUID` only — the `Error` row, allocated by a *read* rather than by the node (§14.2's rule).
2132
+ * A UUID whose Error nobody reads emits no row and no clearing setter.
2133
+ */
2134
+ errorMaterialize?: string;
2135
+ then: HandlerAction[];
2136
+ /** `UUID` only. Always empty for `Unique Id`, which has no Failure port to wire. */
2137
+ failThen: HandlerAction[];
2138
+ };
2139
+ /**
2140
+ * One argument of an {@link HandlerAction} `api-call`: either a plain value, or the object
2141
+ * literal a record verb's `prop-*` set (and a Sign Up's credentials) collapses into.
2142
+ */
2143
+ export type ApiCallArg = {
2144
+ kind: 'expr';
2145
+ expr: ValueExpr;
2146
+ } | {
2147
+ kind: 'data';
2148
+ props: Array<{
2149
+ key: string;
2150
+ expr: ValueExpr;
2151
+ }>;
2152
+ };
2153
+ /**
2154
+ * One re-hosted Function/Expression node (EXP-003-JS-TARGET-OUTPUT §4): the verbatim body plus
2155
+ * everything the wrapper reproduces of the runtime's contract — inputs arriving as a record,
2156
+ * assignments to `Outputs` publishing, script-declared signal outputs callable (as no-ops when
2157
+ * nothing consumes them, exactly as an unwired pulse lands nowhere).
2158
+ */
2159
+ /**
2160
+ * A node whose authored JavaScript did not reach the emitted code, preserved so the repo carries
2161
+ * a record of it. Not a `JsFunctionPlan`: these nodes have no wrapper and no ports mined from the
2162
+ * body — the script is a *parameter* of a node that does something else (a Map Collection's
2163
+ * mapping, a Repeater's template, a Script node's code).
2164
+ */
2165
+ export interface RefusedScriptPlan {
2166
+ nodeId: string;
2167
+ /** The graph type, for the comment's noun — "Map Collection", "Javascript2". */
2168
+ typeName: string;
2169
+ /** The author's label for the node, when it has one. */
2170
+ label?: string;
2171
+ /** Verbatim, from `NodeIR.sourceText`. Never trimmed, never reformatted. */
2172
+ source: string;
2173
+ }
2174
+ export interface JsFunctionPlan {
2175
+ nodeId: string;
2176
+ kind: 'function' | 'expression' | 'visual';
2177
+ /** Module-scope wrapper name — the sanitized node label, deduped per component file. */
2178
+ fnName: string;
2179
+ /** The verbatim body (Function), expression text (Expression) or generated code (Visual). */
2180
+ body: string;
2181
+ /**
2182
+ * The wrapper's input record, in script order: wire-fed inputs carry the resolved source,
2183
+ * literal `in-*` parameters fold, mined-but-unfed inputs stay fields so the body's reads
2184
+ * typecheck (they read `undefined`, exactly as a never-delivered runtime input does).
2185
+ */
2186
+ inputs: Array<{
2187
+ name: string;
2188
+ tsType: string;
2189
+ expr?: ValueExpr;
2190
+ atRun?: true;
2191
+ }>;
2192
+ /** Function only: the Outputs record's value fields (mined + declared + consumed). */
2193
+ outputs: Array<{
2194
+ name: string;
2195
+ tsType: string;
2196
+ }>;
2197
+ /** Function only: signal outputs (mined call syntax / `outtype-*: signal`), seeded no-op. */
2198
+ signals: string[];
2199
+ /** Expression only: preamble Math aliases the expression references (`pi` → `Math.PI`). */
2200
+ mathAliases: string[];
2201
+ /**
2202
+ * Visual only: app-wide Variables the block program reads or writes, from the workspace's
2203
+ * `noodl_get_variable`/`noodl_set_variable` fields — never mined from the generated text.
2204
+ *
2205
+ * These become the `Noodl.Variables` facade the wrapper binds (LOGIC-BUILDER-TARGET §3.4):
2206
+ * the body keeps its verbatim `Noodl.Variables["x"]` reads and writes, and they land on the
2207
+ * export's own variables store. Every name here is registered in `ProjectPlan.variables`,
2208
+ * minted by the block program where no `Variable` node declares it.
2209
+ */
2210
+ variables?: string[];
2211
+ /**
2212
+ * P109 ISL-005. Function only, present when its `uses` lists script files: each file the body
2213
+ * reaches as `Scripts.<alias>`, in the order listed. The wrapper binds `Scripts` from the one
2214
+ * shared registry (`src/lib/projectScriptFiles.ts`); the files are emitted once, never pasted here.
2215
+ */
2216
+ scripts?: ScriptUsePlan[];
2217
+ /**
2218
+ * reactive — no `run` wire: one render local per instance, recomputed per render (grade Q).
2219
+ * invoked — `run` wired: calls inline inside its own handler chain only (grade I).
2220
+ */
2221
+ mode: 'reactive' | 'invoked';
2222
+ /**
2223
+ * EXP-019. Function only: the host traits the body carries ("reads the clock (Date)", "keeps state on `this`", …,
2224
+ * `jsHostTraitsOf`). Present ⇒ the run is `pulsed` (a kept node, one run per trigger, outputs read from the node's
2225
+ * record), because a body with a trait must never run at a read site. A `this` trait binds the body to the node's
2226
+ * `self`. In `reactive` mode (no Run wire, slice 2) the trigger is its inputs' arrival and the load (`TickedRunPlan.atLoad`).
2227
+ */
2228
+ host?: string[];
2229
+ /**
2230
+ * EXP-019 (session 133). Function only: the body may write an output or send a signal after its run has returned, from a
2231
+ * timer or a listener it sets up (`jsCallsBackOf`). Its kept node then hands those to the component (`FunctionNode.late`),
2232
+ * which writes the row and runs the signal's chain, as the runtime's node delivers them when they happen.
2233
+ */
2234
+ late?: true;
2235
+ /**
2236
+ * EXP-019 slice 1b (session 134). Function only: the body awaits at its top level (`jsAwaitsAtTopLevel`). Its wrapper is
2237
+ * `async`, its run hands back the promise (`FunctionRun.settled`), and Success and Done run after it settles, as the
2238
+ * runtime's `runScript` awaits the body before sending them. What it writes after its first `await` arrives `late`.
2239
+ */
2240
+ awaits?: true;
2241
+ /**
2242
+ * EXP-020 (session 134). Function only: the Noodl API members the body reads (`jsNoodlUseOf`), and the Variables it
2243
+ * reads or writes by name. The wrapper binds `Noodl` to `src/lib/noodl.ts`'s object, with a facade over each Variable.
2244
+ */
2245
+ noodl?: {
2246
+ members: string[];
2247
+ variables: string[];
2248
+ };
2249
+ /**
2250
+ * EXP-020 slice 2 (session 135). Function only: the body reads `Component.Object`, its component instance's record. The
2251
+ * component keeps one scope per mount (`src/lib/componentScope.ts`), its kept nodes carry it, and `runFunction` hands it
2252
+ * to the body as the runtime hands it the fourth parameter. Always `pulsed` (it is a host trait).
2253
+ */
2254
+ component?: true;
2255
+ /**
2256
+ * EXP-011 §86. Function only, invoked only: a run of it sends a signal of its own that a chain consumes, or (P18-S121-
2257
+ * FRESHOUTPUTS) it materializes a row a reader reads between runs. The wrapper then takes its `Outputs` record as a
2258
+ * parameter, so `runFunction` can hand it the recording one, and the component keeps the node's last values across
2259
+ * runs (`functionNode`), as the runtime's node does.
2260
+ */
2261
+ pulsed?: true;
2262
+ /**
2263
+ * P18-S110-EVALATLOAD. Expression only, reactive only: present when nothing is sure to arrive at
2264
+ * load — no input has a literal (a parameter is set at load, so it always arrives) and the body
2265
+ * reads at least one port. `unset` names the wire-fed inputs; while every one reads `undefined`
2266
+ * nothing has arrived, because the runtime never delivers `undefined` over a wire.
2267
+ *
2268
+ * That is the state GAM-001's load evaluation runs in (`expression.ts` `_scheduleEvaluateExpression`):
2269
+ * ticked (`evaluate`, the default) it evaluates over the unset inputs but publishes no answer for
2270
+ * a throw or a NaN — Result stays `null`, where an arrival's evaluation answers `0` and `NaN`;
2271
+ * unticked (`wait`) it publishes nothing until an input arrives.
2272
+ */
2273
+ loadGate?: {
2274
+ unset: string[];
2275
+ atLoad: 'evaluate' | 'wait';
2276
+ };
2277
+ }
2278
+ /**
2279
+ * P18-S126-INSIGNAL (EXP-011 §94). A signal input's chain: the actions the Component Inputs port's wires describe, in wire
2280
+ * order, run from an effect keyed on the pulse count the parent passes (`InstancePulsePlan`).
2281
+ */
2282
+ export interface InputPulsePlan {
2283
+ port: string;
2284
+ actions: HandlerAction[];
2285
+ }
2286
+ /**
2287
+ * P18-S126-INSIGNAL. A placed component's signal input the parent pulses: the count (`stateName`, a state row) and the
2288
+ * count the child has acted on (`takenLocal`, a ref the parent keeps, so it outlives the child's unmount). Passed as one
2289
+ * prop, `{ sent, taken }`: the child runs its chain when `sent` passes `taken`, at a mount too (the runtime's instance exists
2290
+ * while it is not drawn and takes the pulse then), and never twice for one pulse (StrictMode's second mount included).
2291
+ */
2292
+ export interface InstancePulsePlan {
2293
+ nodeId: string;
2294
+ port: string;
2295
+ stateName: string;
2296
+ takenLocal: string;
2297
+ /**
2298
+ * P18-S127-SIBLINGVALUE (EXP-011 §96). Set when the placed component also reads an input the parent lifts from another
2299
+ * placed component: the count it is handed (`sent`) is a second row that follows the count from the parent's own
2300
+ * effect, one commit later. A lifted value reaches the parent from the child's push effect, which runs in the commit the
2301
+ * pulse lands in; the parent's effects run after its children's, so the delivered count and the lifted value arrive in
2302
+ * the same render, and the chain inside reads the value sent with the pulse, as the runtime's does.
2303
+ */
2304
+ delivered?: {
2305
+ name: string;
2306
+ setterName: string;
2307
+ };
2308
+ }
2309
+ /** P18-S126-INSIGNAL. The TypeScript type of a signal input's prop, the pair the parent passes. */
2310
+ export declare const INPUT_PULSE_TS_TYPE = "{ sent: number; taken: { current: number } }";
2311
+ /**
2312
+ * P134-HTX004-SELECTREFUSED. A placed component's signal whose chains read a value that same component lifts (todo-list's
2313
+ * Task list: `open` with `taskId`, into Set Variable "Select from the list"). The runtime hands the value before the signal,
2314
+ * so the chain reads the pressed row's id. In the export a lifted value reaches this component from the child's push
2315
+ * effect, after the press, so a chain run in the press would read the value before (the row pressed last time, or
2316
+ * `undefined`). The press adds one to `sent`; an effect hands the count on to `delivered` (the child's push lands in the
2317
+ * same commit, a parent's effects running after its children's); the chain runs from an effect on `delivered`, once per
2318
+ * pulse (`takenLocal`, a ref), reading the value sent with it. P18-S127-SIBLINGVALUE's delivered count, one hop further:
2319
+ * there the count is handed to a placed component, here to this component's own chain.
2320
+ */
2321
+ export interface LiftedPulseChainPlan {
2322
+ nodeId: string;
2323
+ port: string;
2324
+ /** The count the press adds to (a `counter` row). */
2325
+ sent: {
2326
+ name: string;
2327
+ setterName: string;
2328
+ };
2329
+ /** The count as the chain sees it, one commit after `sent` (a `counter` row). */
2330
+ delivered: {
2331
+ name: string;
2332
+ setterName: string;
2333
+ };
2334
+ /** The ref holding the count the chain has acted on. */
2335
+ takenLocal: string;
2336
+ /** Every chain the signal runs, in wire order, as the handler held them. */
2337
+ actions: HandlerAction[];
2338
+ }
2339
+ export interface ReceiverPlan {
2340
+ nodeId: string;
2341
+ channelName: string;
2342
+ /** Actions in trigger-wire source order — statement order in the useSignal handler. */
2343
+ actions: HandlerAction[];
2344
+ }
2345
+ /**
2346
+ * One `useState` row in the component (CONTROLLED-STATE-TARGET §3.1) — the first construct
2347
+ * that materializes state in the emitted component rather than compiling it away. Names live
2348
+ * in the component's one identifier space (props, hooks, wrappers — the stores rule).
2349
+ */
2350
+ export interface StateVarPlan {
2351
+ name: string;
2352
+ setterName: string;
2353
+ /** The useState type parameter, `' | undefined'` included where the boot is undefined. */
2354
+ tsType: string;
2355
+ /** Boot value; null is the `undefined` boot (`useState<T | undefined>()`). */
2356
+ boot: string | number | boolean | null;
2357
+ /**
2358
+ * An initializer expression emitted verbatim, used **instead of** `boot` (EXP-011 Tier 1.3).
2359
+ *
2360
+ * 🔴 The one row that needs it is `Now`'s, and it needs it to be a *lazy* initializer:
2361
+ * `useState(() => new Date())` constructs once per mount, which is what `initialize` does,
2362
+ * where `useState(new Date())` would construct on every render. No other origin has a boot
2363
+ * value that is not a literal, which is why this is an exception rather than the field's
2364
+ * general form.
2365
+ */
2366
+ bootCode?: string;
2367
+ /**
2368
+ * The emitted-library helper {@link bootCode} calls, where it calls one (EXP-011 §37).
2369
+ *
2370
+ * 🔴 **The import is earned from this field and never by reading `bootCode`.** `bootCode` is a
2371
+ * string emitted verbatim, and the two id rows are the first whose initializer calls into
2372
+ * `src/lib/id.ts` — every other earn site in `component.ts` walks *expressions*, and a row's
2373
+ * boot is not an expression it can see. A `Unique Id` with nothing on `New` has a row and no
2374
+ * action at all, so there is nothing else left to derive the import from.
2375
+ */
2376
+ bootHelper?: IdHelper;
2377
+ originNodeId: string;
2378
+ origin: 'switch' | 'counter' | 'control' | 'lifted' | 'jsfun' | 'record-error'
2379
+ /** P18-EXP022-S4-ADDREFUSED — a Create's Id, where a placed component's input reads it. */
2380
+ | 'record-id' | 'variable' | 'http' | 'http-error'
2381
+ /** EXP-011 §41 — a `Cloud Function`'s results row and its Error row. */
2382
+ | 'cloud' | 'cloud-error'
2383
+ /** EXP-011 §43 — a `Record`'s row and its Error row. */
2384
+ | 'record-row' | 'record-row-error'
2385
+ /** EXP-011 §45 — a files node's answer row (a `File`, a `CloudFile`, a `SignedFileUrl`) and its Error row. */
2386
+ | 'file' | 'file-error' | 'external-link-error' | 'navigate-path-error' | 'now'
2387
+ /** EXP-011 §37 — the id a `Unique Id` or a `UUID` holds. */
2388
+ | 'id' | 'array'
2389
+ /** EXP-011 §37 — a `UUID`'s Error. `Unique Id` has no Failure port and never allocates one. */
2390
+ | 'id-error'
2391
+ /** EXP-011 §59 — a `Hash`'s Digest / a `Random Bytes`' Value row, and its Error row. Both allocated by a read. */
2392
+ | 'crypto' | 'crypto-error'
2393
+ /** EXP-011 §61 — a pusher's back-results row (`Record<string, unknown>`, boots `{}`). */
2394
+ | 'stack-back'
2395
+ /** EXP-011 §87 — a Mounted or Visible written by the Evaluate of the unticked Conditions wired into it. */
2396
+ | 'condition'
2397
+ /** EXP-011 §99 — a repeater's relayed row value, written in the rows' relayed-signal callbacks. */
2398
+ | 'relay';
2399
+ /** The provenance comment above the row. */
2400
+ comment: string;
2401
+ }
2402
+ /**
2403
+ * The graph path of a wired control-state input (CONTROLLED-STATE-TARGET §3.4): a useEffect
2404
+ * running the *input setter's* semantics — coercion, abstain guards, clamping, per §1's table —
2405
+ * and never the `Changed` chain (the runtime's own asymmetry).
2406
+ */
2407
+ export interface SyncEffectPlan {
2408
+ stateName: string;
2409
+ source: ValueExpr;
2410
+ /**
2411
+ * The `variable-*` forms are the value Variables (EXP-011 Tier 1.4) reaching the same
2412
+ * construct from the other direction: their `value` input under Run On Value Change is a
2413
+ * graph path that stores without firing `Changed`, exactly as a control's is, so
2414
+ * `variablebase.setValueTo`'s table — abstain on `undefined`, `Treat empty as` on `null`,
2415
+ * `args.cast` otherwise, `NaN` banned — is written here as one more coercion. `variable-none`
2416
+ * is Color's identity cast.
2417
+ */
2418
+ coerce: 'checkbox' | 'slider' | 'dropdown' | 'textinput' | 'variable-string' | 'variable-number' | 'variable-boolean' | 'variable-none';
2419
+ /** Slider only: the literal clamp bounds (a wired min/max defers the node before this). */
2420
+ min?: number;
2421
+ max?: number;
2422
+ /** `variable-*` only: what a `null` arrival stores — the selected `Treat empty as` coercion. */
2423
+ empty?: string | number | boolean | null;
2424
+ }
2425
+ /**
2426
+ * The lifted value output's child side (CONTROLLED-STATE-TARGET §3.5, CO §6 built): a push
2427
+ * effect firing the optional callback prop on change and once at mount — the boot delivery a
2428
+ * parent wire gets from the interpreter, so the mount fire is the faithful part.
2429
+ */
2430
+ export interface PushEffectPlan {
2431
+ prop: string;
2432
+ expr: ValueExpr;
2433
+ /**
2434
+ * P18-S122-TPLBUILD (TS2345). The port's declared type, when the pushed value's static type is not it (an untyped
2435
+ * Variable is `unknown`, an Expression's answer keeps `null` for a NaN at load): the push says the port's type, as a
2436
+ * cast. The runtime's Component Outputs hands the value on as it is, and so does the cast; it changes no value.
2437
+ */
2438
+ castTo?: string;
2439
+ }
2440
+ /**
2441
+ * A reactive Condition (LOGIC-TARGET §10): the box is ticked, so the node re-tests on every
2442
+ * arrival on `condition` and fires exactly one arm — a re-run keyed on the condition, which is
2443
+ * a `useEffect`, not a handler. The Evaluate-only Condition is the other half and compiles to a
2444
+ * `branch` action inside whatever handler drives it (§3).
2445
+ *
2446
+ * `action` is always a `branch`, so every emit-side sweep that already walks branch arms — the
2447
+ * import, navigate and state-reference collectors — reaches these arms for free.
2448
+ */
2449
+ export interface BranchEffectPlan {
2450
+ nodeId: string;
2451
+ action: HandlerAction;
2452
+ /** The provenance comment above the effect — the node's authored label when it has one. */
2453
+ comment: string;
2454
+ }
2455
+ /**
2456
+ * A `Value Changed` (EXP-011 §39): the node fires whenever its Input becomes a different value,
2457
+ * the first arrival included, and compares by identity — `useEffect` over the value, with the
2458
+ * last value seen in a `useRef` so a re-render that delivers the same value fires nothing.
2459
+ *
2460
+ * ⚠️ React's dependency compare is `Object.is` and the node's is `===`; they differ only on
2461
+ * `NaN` (the node fires on every `NaN` arrival, the effect never re-runs for one). Recorded in
2462
+ * the ledger rather than reproduced — a value that is `NaN` on every arrival is not a change
2463
+ * an author is watching for.
2464
+ */
2465
+ export interface ValueChangedEffectPlan {
2466
+ nodeId: string;
2467
+ /** The Input, as the render-mode expression the effect watches. */
2468
+ watch: ValueExpr;
2469
+ /** The `useRef` local holding the last value the effect saw. */
2470
+ lastLocal: string;
2471
+ actions: HandlerAction[];
2472
+ comment: string;
2473
+ }
2474
+ /**
2475
+ * P18-S119-TICKEDRUN. A Function or Expression whose Run is wired also runs when one of its value inputs whose Run On Value
2476
+ * Change box is ticked (or unset, which is ticked) gets a new value (NDA-017 §2, `setScriptInputValue`). A wired value
2477
+ * arrives when its source first has one and whenever it changes after; a literal arrives once, at load. Such a run sends
2478
+ * the Function's own signals and writes its outputs, but never Done (no Run sent it). Two arrivals, or an arrival and a
2479
+ * Run, in one update make one run.
2480
+ *
2481
+ * Printed as a `useEffect` keyed on the watched values' sources, beside the Run chains: it asks `tickedArrival` whether
2482
+ * a value arrived (not `undefined`, not the one held), and runs `action` if so. A Run records what it read the same way,
2483
+ * so a click that sets a watched value and sends Run makes one run, the Run's, with its Done (measured, s121 drive S4).
2484
+ * Measured in the viewer (s121 drive S0): at load the arrival's run comes first, then a Did Mount's Run, two runs.
2485
+ */
2486
+ export interface TickedRunPlan {
2487
+ nodeId: string;
2488
+ /** The ticked inputs the runtime delivers, in input order: a wired one as the value it reads, a literal as itself. */
2489
+ watch: ValueExpr[];
2490
+ /** The run without its Done: the body, its signals' chains, its §4f write. `ticked: true`. */
2491
+ action: Extract<HandlerAction, {
2492
+ kind: 'jsfun-run';
2493
+ }>;
2494
+ /**
2495
+ * 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
2496
+ * (`simplejavascript.ts`, `if (!this.isInputConnected('run')) this.scheduleRun()`), whatever has arrived, inputs or none.
2497
+ * The effect then runs on its first pass even when no watched value arrived (a ref, so StrictMode's second mount does
2498
+ * not run it twice).
2499
+ */
2500
+ atLoad?: true;
2501
+ comment: string;
2502
+ }
2503
+ /**
2504
+ * A `CSS Definition`'s stylesheet (EXP-011 §48): a module constant holding the authored CSS
2505
+ * verbatim, and a mount effect that appends it to `document.head` in a `<style>` and removes it
2506
+ * on unmount — the node's own `updateStyle`/`removeStyleDeclaration`, per instance.
2507
+ *
2508
+ * ⚠️ One difference, invisible on the page: the runtime keys the element by *node id* and
2509
+ * ref-counts it, so N mounted instances of one component share one `<style>`; the export appends
2510
+ * one per instance. Identical text N times has the same cascade as once.
2511
+ */
2512
+ export interface StyleSheetPlan {
2513
+ nodeId: string;
2514
+ /** `DESK_STYLES` — SCREAMING_SNAKE of the authored label, deduped like a Static Data constant. */
2515
+ constName: string;
2516
+ /** The authored CSS, verbatim — never trimmed, never reformatted. */
2517
+ css: string;
2518
+ comment: string;
2519
+ }
2520
+ /** One `transitiondef-<state>` / `transition-<state>-<value>` — the node's `curve` port shape. */
2521
+ export interface StatesCurve {
2522
+ curve: number[];
2523
+ dur: number;
2524
+ delay: number;
2525
+ }
2526
+ /** One authored value of a States node — its `type-<name>` and its `value-<state>-<name>` per state. */
2527
+ export interface StatesValueDef {
2528
+ name: string;
2529
+ type: StatesValueType;
2530
+ /** The declared TypeScript type of the read: `number`, `string`, `boolean`; a colour is a `string`. */
2531
+ tsType: string;
2532
+ byState: Record<string, string | number | boolean>;
2533
+ transitions: Record<string, StatesCurve>;
2534
+ }
2535
+ /**
2536
+ * A `States` node (EXP-011 §49): a definition constant above the component (`defineStates({…})`),
2537
+ * a `useStates` handle inside it, the listeners its signal outputs compiled into, and the wired
2538
+ * State input as an effect.
2539
+ *
2540
+ * The values are held by the emitted hook, not by state rows: every frame of a tween publishes a
2541
+ * new snapshot, and a row per value would be five setters per frame for the same re-render.
2542
+ */
2543
+ export interface StatesPlan {
2544
+ nodeId: string;
2545
+ /** The `useStates` local — `panel`. */
2546
+ local: string;
2547
+ /** The definition constant — `PANEL_STATES`. */
2548
+ constName: string;
2549
+ states: string[];
2550
+ values: StatesValueDef[];
2551
+ /** `transitiondef-<state>`, by state, where authored. */
2552
+ transitions: Record<string, StatesCurve>;
2553
+ useTransitions: boolean;
2554
+ /** The authored State, when it names a state; the node starts in the first state otherwise. */
2555
+ start: string | undefined;
2556
+ /** The wired State input — `useEffect(() => { local.follow(expr) }, [deps])`. */
2557
+ follow?: ValueExpr;
2558
+ /** The signal outputs, as the chains wired off them. `reached` is by state. */
2559
+ listeners: {
2560
+ stateChanged?: HandlerAction[];
2561
+ reached: Record<string, HandlerAction[]>;
2562
+ done?: HandlerAction[];
2563
+ unchanged?: HandlerAction[];
2564
+ failure?: HandlerAction[];
2565
+ };
2566
+ comment: string;
2567
+ }
2568
+ /**
2569
+ * A `Script` node (EXP-011 §52): `const <local> = useScript(<defName>, { <inputs> }, { <listeners> })`,
2570
+ * with the node's code in its own file under `src/scripts/`, verbatim and unchecked, exported as
2571
+ * `<defName>`. The port set is the one on disk (`scriptPortsOf`); the input record carries only the
2572
+ * inputs the graph feeds, typed by what is delivered.
2573
+ */
2574
+ export interface ScriptPlan {
2575
+ nodeId: string;
2576
+ /** The authored label, or `Script`. */
2577
+ label: string;
2578
+ /** The `useScript` local — `ticker`. */
2579
+ local: string;
2580
+ /** The exported definition constant and the file's base name — `tickerScript`. */
2581
+ defName: string;
2582
+ /** `src/scripts/<dir>/<fileBase>/<defName>.ts` — the file the code lives in. */
2583
+ file: string;
2584
+ /** The author's code, verbatim (`sourceText`'s contract: never trimmed, never reformatted). */
2585
+ code: string;
2586
+ /** Value inputs, in port order: wire-fed carry the resolved source, literals fold, unfed are absent from the record. */
2587
+ inputs: Array<{
2588
+ name: string;
2589
+ tsType: string;
2590
+ expr?: ValueExpr;
2591
+ }>;
2592
+ signalInputs: string[];
2593
+ /** Value outputs, in port order, typed by the declared port type. */
2594
+ outputs: Array<{
2595
+ name: string;
2596
+ tsType: string;
2597
+ }>;
2598
+ signalOutputs: string[];
2599
+ /** The ports as `defineScript` registers them — every port on disk, typed as the editor spelled it. */
2600
+ ports: {
2601
+ inputs: Record<string, string>;
2602
+ outputs: Record<string, string>;
2603
+ };
2604
+ /** The signal outputs, as the chains wired off them. */
2605
+ listeners: Record<string, HandlerAction[]>;
2606
+ comment: string;
2607
+ }
2608
+ /**
2609
+ * A `Run Tasks` node (EXP-011 §53): `const <local> = useRunTasks(<label>, { maxRunningTasks, stopOnFailure },
2610
+ * { <listeners> })` in the host, and the active task instances rendered as the last child of the host's
2611
+ * root container — `{<local>.tasks.map((task) => <Template key={task.key} <inputs from task.item>
2612
+ * onSuccess={task.succeed} onFailure={task.fail} />)}`. The template is a project component with no
2613
+ * visual root, emitted as a null-rendering component whose start-input chain runs on mount
2614
+ * ({@link TaskPlan}). The contract between the two is by NAME, as the runtime's is (`runtasks.ts`
2615
+ * matches the template's outputs by string; there is no wire between host and template).
2616
+ */
2617
+ export interface RunTasksPlan {
2618
+ nodeId: string;
2619
+ /** The authored label, or `Run Tasks`. */
2620
+ label: string;
2621
+ /** The `useRunTasks` local — `sendAll`. */
2622
+ local: string;
2623
+ /** The template's legacy path — `/Notify`. The symbol and file are read off its plan at emit. */
2624
+ templateLegacy: string;
2625
+ /** The resolved contract names (defaults applied). */
2626
+ contract: {
2627
+ start: string;
2628
+ success: string;
2629
+ failure: string;
2630
+ error: string;
2631
+ };
2632
+ /** The list, read at the pulse. */
2633
+ items: ValueExpr;
2634
+ /** Literal or wired (a render read); the hook reads both live, as the runtime reads `_internal`. */
2635
+ maxRunningTasks: ValueExpr;
2636
+ stopOnFailure: ValueExpr;
2637
+ /**
2638
+ * The template's value inputs the host binds from the item, by identity (`runtasks.ts`:
2639
+ * `for (const inputKey in itemNode._inputs) if (model.data[inputKey] !== undefined) setInputValue`).
2640
+ * `prop` is the template's prop identifier for the port; `field` the item key (the port name).
2641
+ */
2642
+ inputs: Array<{
2643
+ port: string;
2644
+ field: string;
2645
+ }>;
2646
+ /** The template's `Id` / `id` inputs, fed the task's model id (`item.id`, else a generated one). */
2647
+ idInputs: string[];
2648
+ /** The template's callback props for the contract's success and failure outputs. */
2649
+ onSuccess?: string;
2650
+ onFailure?: string;
2651
+ /** The signal outputs, as the chains wired off them. */
2652
+ listeners: Partial<Record<'done' | 'failure' | 'unchanged' | 'completed' | 'aborted', HandlerAction[]>>;
2653
+ comment: string;
2654
+ }
2655
+ /**
2656
+ * A component that a `Run Tasks` names as its template (EXP-011 §53) — logic-only, emitted anyway,
2657
+ * rendering `null`; the chain wired off its start input runs once in a mount effect, which is exactly
2658
+ * when `startTask` pulses it (right after `createNode`). Every other signal input stays as before
2659
+ * (declared, unconsumed — COMPONENT-OUTPUTS-TARGET §6). Set only when every host names the same
2660
+ * start input and the chain compiled; otherwise the component keeps no file and
2661
+ * {@link ComponentPlan.taskRefusal} says why, for the hosts to repeat.
2662
+ */
2663
+ export interface TaskPlan {
2664
+ startPort: string;
2665
+ actions: HandlerAction[];
2666
+ /** The Run Tasks nodes that name this component, for the doc comment. */
2667
+ hosts: Array<{
2668
+ componentPath: string;
2669
+ nodeId: string;
2670
+ }>;
2671
+ comment: string;
2672
+ }
2673
+ /**
2674
+ * A `Repeater Item` (EXP-011 §57) whose `Added` drives a chain: once, on mount, guarded against StrictMode's
2675
+ * second mount — `signalAdded` is called synchronously inside `addItem` after `createNode`, once per row
2676
+ * (`foreach.tsx`). Its Item Id, when read, is a row prop ({@link ComponentPlan.rowProps}) rather than anything
2677
+ * here — the parent binds it from `item.id`.
2678
+ */
2679
+ export interface RepeaterItemPlan {
2680
+ nodeId: string;
2681
+ /** The authored label, or `Repeater Item`. */
2682
+ label: string;
2683
+ /** The Added chain, snapped for render context. */
2684
+ actions: HandlerAction[];
2685
+ comment: string;
2686
+ }
2687
+ /**
2688
+ * An `On App Error` node (EXP-011 §54): `const <local> = useAppError({ filter }, () => { …the Error chain… })`.
2689
+ * The boundary subscribes to the app's error channel (`src/lib/errors.ts`) for the life of the component
2690
+ * that hosts it — a page's boundary hears errors while the page is mounted, the root component's for the
2691
+ * app's life (the router shell keeps a file for it, {@link ComponentPlan.shell}) — and every instance
2692
+ * fires (onapperror.ts: no claiming, no consumption). Its value outputs read off the handle's `last`.
2693
+ */
2694
+ export interface AppErrorPlan {
2695
+ nodeId: string;
2696
+ /** The authored label, or `On App Error`. */
2697
+ label: string;
2698
+ /** The `useAppError` local — `catchAll`. */
2699
+ local: string;
2700
+ /** The Filter — a literal, or a render read where wired; absent means every error. Read live by the hook. */
2701
+ filter?: ValueExpr;
2702
+ /** The chain wired off `Error`, when any. */
2703
+ listener?: HandlerAction[];
2704
+ comment: string;
2705
+ }
2706
+ /**
2707
+ * A streaming node (EXP-011 §58): `const <local> = use<Hook>({ label, nodeId, componentName }, { <config> },
2708
+ * { <listeners> })`. The data port's expression rides every action that takes it; the config is the options
2709
+ * object the hook re-reads every render (a literal, or a render read where wired); the signal outputs are the
2710
+ * chains wired off them.
2711
+ */
2712
+ export interface StreamPlan {
2713
+ nodeId: string;
2714
+ type: string;
2715
+ kind: StreamKind;
2716
+ /** The authored label, or the display name. */
2717
+ label: string;
2718
+ /** The hook local — `parser`. */
2719
+ local: string;
2720
+ /** The data port's source — a wire's expression or an authored literal; absent when the port has none. */
2721
+ data?: ValueExpr;
2722
+ /** The config ports that are authored or wired, by port name; an absent port keeps the runtime's initialize() value. */
2723
+ config: Array<{
2724
+ port: string;
2725
+ expr: ValueExpr;
2726
+ }>;
2727
+ /** The signal outputs, as the chains wired off them. */
2728
+ listeners: Record<string, HandlerAction[]>;
2729
+ /** EXP-011 §78. The store a history-family node names — printed as its store module (`store: app`) and imported. */
2730
+ store?: string;
2731
+ comment: string;
2732
+ }
2733
+ /**
2734
+ * A `Screen Resolution` node (EXP-011 §59): `const <local> = useScreenResolution()`. Registered on first
2735
+ * read, the boundary's shape without a listener; its three outputs read off the handle. A node nobody
2736
+ * reads is never registered — the hook would be a listener nothing observes.
2737
+ */
2738
+ export interface ScreenResolutionPlan {
2739
+ nodeId: string;
2740
+ label: string;
2741
+ local: string;
2742
+ comment: string;
2743
+ }
2744
+ /**
2745
+ * EXP-011 §60. A `Component Object` node in **record** mode — its component hosts a `Set Component Object Properties`,
2746
+ * or a descendant reaches the record through the parent pair: `const <local> = useComponentObject<<typeName>>()`,
2747
+ * one mirror effect per wired `value-*` input (`useEffect(() => { <local>.set({ key: src }); }, [deps])`), and the
2748
+ * component's root wrapped in `ParentComponentObjectContext.Provider` — the transcription of "this component owns a
2749
+ * Component Object", which is what `findAncestorWithComponentObject` tests. Alias mode (no writer, no descendant
2750
+ * reach) is COMPONENT-OBJECT-TARGET §3 unchanged and registers nothing here.
2751
+ */
2752
+ export interface ComponentObjectRecordPlan {
2753
+ nodeId: string;
2754
+ label: string;
2755
+ local: string;
2756
+ /** The record's type alias, printed at module level: `type <typeName> = { key?: tsType; … }`. */
2757
+ typeName: string;
2758
+ keys: Array<{
2759
+ key: string;
2760
+ tsType: string;
2761
+ }>;
2762
+ mirrors: Array<{
2763
+ key: string;
2764
+ source: ValueExpr;
2765
+ wireKey: string;
2766
+ }>;
2767
+ comment: string;
2768
+ }
2769
+ /**
2770
+ * A `Component Stack` that renders (EXP-011 §61): `const <local> = usePageStack({ name, pages, startPage })`, and a
2771
+ * visual row that renders the TOP entry only — one `&&` line per page, the entry's params spread as the page
2772
+ * component's props, the reserved `pageStackEntry` prop where the page keeps a Pop.
2773
+ */
2774
+ export interface PageStackPlan {
2775
+ nodeId: string;
2776
+ label: string;
2777
+ local: string;
2778
+ /** The registry key — the authored Name, `Main` by default. */
2779
+ name: string;
2780
+ /** The Components list in authored order, each resolved to the component it shows. */
2781
+ pages: Array<{
2782
+ id: string;
2783
+ label: string;
2784
+ componentLegacy: string;
2785
+ }>;
2786
+ /** The authored Start Page, or the first page's id. */
2787
+ startPage: string;
2788
+ comment: string;
2789
+ }
2790
+ /**
2791
+ * EXP-011 §60. The nearest ancestor's record, read from below — one hook per component, shared by every
2792
+ * `Parent Component Object` and `Set Parent Component Object Properties` in it (each resolves the same nearest
2793
+ * owner): `const <local> = useParentComponentObject<<typeName>>([readers])`. `readers` are the reader nodes that
2794
+ * collapsed — each raises `parent-component-object/no-ancestor` once at mount when the hook answers undefined.
2795
+ */
2796
+ export interface ParentComponentObjectPlan {
2797
+ local: string;
2798
+ typeName: string;
2799
+ keys: Array<{
2800
+ key: string;
2801
+ tsType: string;
2802
+ }>;
2803
+ readers: Array<{
2804
+ nodeId: string;
2805
+ label: string;
2806
+ }>;
2807
+ /** Whether a parent Set prints — the hook prints when a reader collapsed or a writer attached. */
2808
+ comment: string;
2809
+ }
2810
+ /**
2811
+ * A `Drag` node (EXP-011 §63): `const <local> = useDrag({ label, nodeId, componentName }, { …options }, { …listeners })`,
2812
+ * and a wrapper `<div ref style {...handlers}>` where the node sits. Registered for every rendered Drag — the wrapper
2813
+ * needs the hook whether or not anything reads the node.
2814
+ */
2815
+ export interface DragPlan {
2816
+ nodeId: string;
2817
+ label: string;
2818
+ local: string;
2819
+ comment: string;
2820
+ /**
2821
+ * The options that print, in `DRAG_OPTION_PORTS` order — a wire is the render expression the hook reads on arrival,
2822
+ * an authored value a literal, a port nothing set absent (the lib applies the declared default there).
2823
+ */
2824
+ options: Array<{
2825
+ key: string;
2826
+ expr: ValueExpr;
2827
+ }>;
2828
+ /** The listener chains that translated. `done` fires after every snap call; Failure never (dropped with a note). */
2829
+ listeners: {
2830
+ onStart?: HandlerAction[];
2831
+ onMove?: HandlerAction[];
2832
+ onEnd?: HandlerAction[];
2833
+ done?: HandlerAction[];
2834
+ };
2835
+ /** Wires dropped by name — printed as in-code markers on the wrapper, counted by the pre-flight. */
2836
+ drops: Array<{
2837
+ subject: string;
2838
+ reason: string;
2839
+ }>;
2840
+ }
2841
+ /**
2842
+ * An `Animate To Value` node (EXP-011 §49): `const <local> = useAnimatedValue(target, { duration,
2843
+ * delay, ease }, onArrive)`. `duration` and `delay` are read where the hook is — a wired one is
2844
+ * the render expression, an authored one a literal — and `ease` is the authored enum, which a
2845
+ * wire cannot deliver (the node indexes `EaseCurves` by it and an unknown name throws).
2846
+ */
2847
+ export interface AnimationPlan {
2848
+ nodeId: string;
2849
+ local: string;
2850
+ target: ValueExpr;
2851
+ duration: ValueExpr;
2852
+ delay: ValueExpr;
2853
+ /**
2854
+ * P18-S122-TPLBUILD (TS2322). The inputs whose wired value is not statically a number (an untyped Variable is `unknown`):
2855
+ * the runtime's Duration setter stores the value as it is, and the hook's option says `number`, so the option is a cast.
2856
+ */
2857
+ castToNumber?: Array<'duration' | 'delay'>;
2858
+ ease: AnimateEaseName;
2859
+ /** The At Target Value chain, when wired. */
2860
+ arrive?: HandlerAction[];
2861
+ comment: string;
2862
+ }
2863
+ export interface PropPlan {
2864
+ name: string;
2865
+ tsType: string;
2866
+ }
2867
+ export interface QueryPlan {
2868
+ nodeId: string;
2869
+ collectionName: string;
2870
+ /** `puppies` / `setPuppies` / `puppy` / `fetchPuppies` / `Puppy` / api module base `puppies`. */
2871
+ stateName: string;
2872
+ setterName: string;
2873
+ itemName: string;
2874
+ fetchName: string;
2875
+ typeName: string;
2876
+ moduleBase: string;
2877
+ /**
2878
+ * P18-S115-QUERYFILTER (EXP-011 §82). The `where` / `order` / `limit` / `skip` the runtime sends for this node, its
2879
+ * filter translated by the runtime's own translator (`querySettings.ts`). Absent when it sends none of them, so an
2880
+ * unfiltered query's call is the bare `fetch<Plural>()` it always was.
2881
+ */
2882
+ params?: QueryParamsPlan;
2883
+ /**
2884
+ * P18-S115-QUERYPARAMS. With `params.wired`: each wired filter value as the render read its wire resolves to, in
2885
+ * `params.wired.params` order. The query re-runs whenever one changes (`setQueryParameter`, its box ticked).
2886
+ */
2887
+ wiredValues?: Array<{
2888
+ name: string;
2889
+ expr: ValueExpr;
2890
+ }>;
2891
+ /** The project declares a backend, so `src/api/client.ts` exists to put each value on the wire (`filterValue`). */
2892
+ wiredClient?: boolean;
2893
+ /**
2894
+ * P18-S116-QUERYFETCH. Its Fetch is wired, so the query asks when Fetch is sent (`query-fetch` in the sender's
2895
+ * chain). `mount`: it also asks as the node is made, its Class box ticked or its Query settings box ticked with a
2896
+ * setting authored (a wired Fetch settles both unticked, NDA-017; an author can tick them again). `onValues`: the
2897
+ * wired filter values' boxes are ticked, so a new value asks too (the effect, mount included).
2898
+ */
2899
+ fetchWired?: {
2900
+ mount: boolean;
2901
+ onValues: boolean;
2902
+ };
2903
+ }
2904
+ /**
2905
+ * One translated record verb (RECORD-VERBS-TARGET §4d): what the api stub module has to export
2906
+ * for it. The naming is a pure function of the class name, identical to {@link QueryPlan}'s, so a
2907
+ * project that both reads and writes one collection gets a single module.
2908
+ */
2909
+ export interface MutationPlan {
2910
+ nodeId: string;
2911
+ /** EXP-011 §62: `add-relation` / `remove-relation` join the class's module beside the three record verbs. */
2912
+ verb: 'create' | 'update' | 'delete' | RelationVerb;
2913
+ collectionName: string;
2914
+ /** `createPuppy` / `updatePuppy` / `deletePuppy`. */
2915
+ fnName: string;
2916
+ typeName: string;
2917
+ moduleBase: string;
2918
+ /**
2919
+ * The columns this verb actually writes, with the type its argument resolves to — the graph's
2920
+ * own evidence of the record's shape (RECORD-VERBS-TARGET §4d).
2921
+ *
2922
+ * The interface is minted from the project's collection **schema snapshot**, and 32 of the 39
2923
+ * corpus projects carry none at all: `metadata.dbCollections` is absent, so every collection
2924
+ * types as `{ id: string }` and *any* `create`/`update` that writes a property emits a call
2925
+ * that does not compile. The schema is the authority where it exists and silent where it does
2926
+ * not; a `prop-` the graph writes is direct evidence of a column, so the two union. Empty for
2927
+ * `delete`, which writes nothing.
2928
+ */
2929
+ writes: Array<{
2930
+ name: string;
2931
+ tsType: string;
2932
+ }>;
2933
+ }
2934
+ /**
2935
+ * One translated user-family node (USER-FAMILY-TARGET §4d): what `src/api/session.ts` has to
2936
+ * export for it. Unlike {@link MutationPlan} there is nothing to key on — a project has one
2937
+ * session — so the three writes and the read all land in a single module.
2938
+ */
2939
+ export interface SessionCallPlan {
2940
+ nodeId: string;
2941
+ verb: UserVerb | 'read';
2942
+ /** `logIn` / `logOut` / `signUp` / `useSession` / `setUserProperties` / `requestMagicLink`. */
2943
+ fnName: string;
2944
+ /**
2945
+ * EXP-011 §44 — the `_User` columns a `Set User Properties` writes (its `prop-*` inputs, the
2946
+ * prefix stripped), typed the record verbs' way: the wire's type where it is one of the three
2947
+ * JSON scalars, else `unknown`. The session module's `UserProperties` interface is the union
2948
+ * over every site — a project has one `_User` class, as it has one session.
2949
+ */
2950
+ writes?: Array<{
2951
+ name: string;
2952
+ tsType: string;
2953
+ }>;
2954
+ }
2955
+ /**
2956
+ * One value an `HTTP Request` sends (EXP-011 Tier 1.2): a path segment, a query parameter, a
2957
+ * header, a body field or a credential.
2958
+ *
2959
+ * `from` is the whole difference between configuration and data. An authored parameter is folded
2960
+ * into the emitted module as a literal — it is what the interpreter would have read out of
2961
+ * `inputValues` on every fetch, and nothing can change it. A wired one becomes a function
2962
+ * parameter the call site fills. An input that is *neither* never appears: the runtime reads
2963
+ * `undefined` for it, and `undefined` omits the header, the query parameter and the body field
2964
+ * alike, so emitting the guard would be emitting a branch that cannot be taken.
2965
+ */
2966
+ export interface HttpValuePlan {
2967
+ /** The wire name — the header, query parameter, path placeholder or field, verbatim. */
2968
+ name: string;
2969
+ from: {
2970
+ kind: 'literal';
2971
+ value: string | number | boolean;
2972
+ } | {
2973
+ kind: 'param';
2974
+ param: string;
2975
+ tsType: string;
2976
+ };
2977
+ }
2978
+ /**
2979
+ * One translated `HTTP Request` (EXP-011 Tier 1.2) — everything `src/api/http.ts` needs to
2980
+ * rebuild the request the node would have sent.
2981
+ *
2982
+ * The node's configuration is `allowEditOnly` almost throughout (method, body type, the four
2983
+ * string lists, the auth preset), which is why this is a plan and not a solver: what the request
2984
+ * *is* was decided in the editor and cannot change at runtime. What can change is the values, and
2985
+ * those are {@link HttpValuePlan}s.
2986
+ */
2987
+ export interface HttpCallPlan {
2988
+ nodeId: string;
2989
+ /** `fetchQuote` — from the node's authored label, deduped across the project's HTTP nodes. */
2990
+ fnName: string;
2991
+ /** `QuoteAnswer` — the exported result type the component's state row is typed by. */
2992
+ typeName: string;
2993
+ /**
2994
+ * The `const answer = await …` local, and the `const message = …` both failure arms bind.
2995
+ *
2996
+ * They live here rather than on the action because the *expression* side needs them too — a
2997
+ * chain-local read is emitted from a `http-out` that knows only its node — and two places
2998
+ * deriving the same name independently is the divergence FINDINGS B-iv is about.
2999
+ */
3000
+ answerLocal: string;
3001
+ messageLocal: string;
3002
+ /** The authored URL, `{placeholders}` included; `''` when only a wire gives one. */
3003
+ url: string;
3004
+ /**
3005
+ * P134-S14-URLWIRED. The URL a wire gives, read when the request is sent (`buildUrl` over `_internal.url`); it wins
3006
+ * over {@link url}, whose placeholders still name the path inputs (`updatePorts` makes them from the authored one).
3007
+ */
3008
+ urlParam?: HttpValuePlan;
3009
+ method: string;
3010
+ /** Milliseconds; `timeout || 30000`, the runtime's own defaulting, already applied. */
3011
+ timeout: number;
3012
+ /**
3013
+ * P18-S104-RESPONSETYPE — how the body is read (FED-001 §3.2). `setResponseType` stores `value || 'auto'` and the
3014
+ * fetch tests for `'text'` and `'json'` only, so any other value reads as Auto; that defaulting is applied here.
3015
+ */
3016
+ responseType: 'auto' | 'text' | 'json';
3017
+ pathParams: HttpValuePlan[];
3018
+ queryParams: HttpValuePlan[];
3019
+ headers: HttpValuePlan[];
3020
+ /** Null for GET/HEAD/OPTIONS, which send none — `buildBody` returns undefined for them. */
3021
+ body: {
3022
+ type: 'json' | 'form' | 'urlencoded';
3023
+ fields: HttpValuePlan[];
3024
+ } | {
3025
+ type: 'raw';
3026
+ raw: HttpValuePlan;
3027
+ } | null;
3028
+ auth: {
3029
+ kind: 'bearer';
3030
+ token: HttpValuePlan;
3031
+ } | {
3032
+ kind: 'basic';
3033
+ username: HttpValuePlan;
3034
+ password: HttpValuePlan;
3035
+ } | {
3036
+ kind: 'apiKey';
3037
+ name: HttpValuePlan;
3038
+ value: HttpValuePlan;
3039
+ location: 'header' | 'query';
3040
+ } | null;
3041
+ /**
3042
+ * The Response Mapping, compiled (`$.data.items[0].name` → `?.data?.items?.[0]?.name`).
3043
+ *
3044
+ * `steps` is empty for the default path `$`, which is the whole body — and `null` for a path
3045
+ * `extractByPath` can never resolve (one that does not start with `$`), where the runtime
3046
+ * publishes `undefined` on every answer and so does the emitted module.
3047
+ */
3048
+ fields: Array<{
3049
+ name: string;
3050
+ steps: Array<string | number> | null;
3051
+ }>;
3052
+ }
3053
+ /** One `Cloud Function` node's call (EXP-011 §41) — what `src/api/functions.ts` emits for it. */
3054
+ export interface CloudCallPlan {
3055
+ nodeId: string;
3056
+ /** The backend function's name, verbatim — the `function` parameter. */
3057
+ functionName: string;
3058
+ /** `callPublishPage` — from the node's authored label, else the function name. */
3059
+ fnName: string;
3060
+ /** `PublishPageResults` — the exported result type the component's state row is typed by. */
3061
+ typeName: string;
3062
+ answerLocal: string;
3063
+ messageLocal: string;
3064
+ /** The `in-*` parameters — a folded literal, or the parameter the call site fills. */
3065
+ params: HttpValuePlan[];
3066
+ /** The `out-*` result names the node declares, in port order. */
3067
+ results: string[];
3068
+ }
3069
+ /** One `Record` node's read (EXP-011 §43) — what its collection module has to export for it. */
3070
+ export interface RecordReadPlan {
3071
+ nodeId: string;
3072
+ collectionName: string;
3073
+ /** `Page` — the collection module's interface, the row's type. */
3074
+ typeName: string;
3075
+ moduleBase: string;
3076
+ /** `fetchPageById`. */
3077
+ fnName: string;
3078
+ answerLocal: string;
3079
+ messageLocal: string;
3080
+ /** The Id, bound before the guard so the emitted `Missing Id.` test and the call read one value. */
3081
+ idLocal: string;
3082
+ /** Columns the graph reads off this node that the schema snapshot does not carry — `unknown` in the interface. */
3083
+ reads: Array<{
3084
+ name: string;
3085
+ tsType: string;
3086
+ }>;
3087
+ /**
3088
+ * P18-S111-RECORDLIVE. "Record properties" ticked under Run On Value Change, the default: the node shows the app's one
3089
+ * copy of its record, so a write anywhere in the app reaches its outputs (dbmodelnode2.ts `onModelChangedCallback`).
3090
+ * Unticked, the callback returns early and the outputs move only on the node's own read, which is the row as it was.
3091
+ * False where the project declares no backend: the read is a stub that throws and no client is emitted.
3092
+ */
3093
+ live: boolean;
3094
+ /**
3095
+ * P18-S113-IDBINDREAD. Fetch unwired: the node never reads, its Id only binds the row to the client's copy of the
3096
+ * record (`RecordEffectPlan`). Its class's module carries no `fetch<Type>ById` for it, and the client holds records.
3097
+ */
3098
+ binds?: true;
3099
+ }
3100
+ /**
3101
+ * One files node that translated (EXP-011 §45) — what `src/api/files.ts` must export (`upload` and
3102
+ * `sign`; a `pick` needs only `pickFile` from the util library) and the chain-locals its two arms
3103
+ * bind, read off the plan so the expression side and the action side cannot disagree about a name.
3104
+ */
3105
+ export interface FileOpPlan {
3106
+ nodeId: string;
3107
+ family: 'pick' | 'upload' | 'sign';
3108
+ answerLocal: string;
3109
+ messageLocal: string;
3110
+ }
3111
+ /**
3112
+ * A `Variable` node carrying an authored `Value` (EXP-011 §67). The runtime's `value` input setter
3113
+ * runs at node creation with the parameter and schedules a store (variablenode2.ts), so the node
3114
+ * rewrites the variable on EVERY mount of the component that holds it — navigating back to the
3115
+ * page resets the variable there. The translation is therefore a mount effect in the host — never
3116
+ * a module-level seed, which would boot once, never reset, and run for a component nothing mounted.
3117
+ */
3118
+ export interface VariableSeedPlan {
3119
+ nodeId: string;
3120
+ variableName: string;
3121
+ value: string | number | boolean;
3122
+ comment: string;
3123
+ }
3124
+ /** The seed as the action it prints — read off the plan so the import sweep and the emitter cannot disagree. */
3125
+ export declare function variableSeedAction(seed: VariableSeedPlan): HandlerAction;
3126
+ /**
3127
+ * EXP-011 §71. An `Object` node whose OWN `prop-<key>` inputs carry typed-in values. The runtime
3128
+ * registers any `prop-*` input on demand and queues every authored parameter at node creation
3129
+ * (nodescope.ts); `userInputSetter` marks the key dirty and `scheduleStore` writes it into the
3130
+ * record in the same pass (modelnode2.ts) — so the node rewrites those keys on EVERY mount of the
3131
+ * component that holds it, and a Set's Do lands over them afterwards (measured, session 94). §67's
3132
+ * shape one construct over: a mount effect in the host, one patch per node, never a module-level
3133
+ * seed (which would boot once, never reset, and run for a component nothing mounted).
3134
+ */
3135
+ export interface ObjectSeedPlan {
3136
+ nodeId: string;
3137
+ storeName: string;
3138
+ /** Parameter order — the order the runtime writes `dirtyValues` in. */
3139
+ entries: Array<{
3140
+ key: string;
3141
+ value: string | number | boolean;
3142
+ }>;
3143
+ comment: string;
3144
+ }
3145
+ /**
3146
+ * P18-S116-FIELDMOUNT. A rendered text input whose Value reaches a chain the export translated (its `onChange`). The
3147
+ * runtime's field announces its Value as it mounts: `componentDidMount` calls `setText(startValue)`, which sends
3148
+ * `onTextChanged` with `outwardValueForFieldType(type, text)` and, through the output's `onChange`, Value Changed
3149
+ * (TextInput.tsx, text-input.ts). So every sink of the Value, and every chain on Value Changed, runs once as the field
3150
+ * appears, with the start value or `""`. The export runs the field's own change chain once from a mount effect, with
3151
+ * the announced value where the chain reads the field.
3152
+ */
3153
+ export interface FieldMountPlan {
3154
+ inputId: string;
3155
+ /** The text the field starts from with nothing wired, as the control's boot computes it (`String(startValue ?? '')`). */
3156
+ start: string;
3157
+ /**
3158
+ * The wired Value's source, where a sync effect carries it. The runtime delivers it at node creation, before the
3159
+ * mount, so the field announces it: `undefined` is never delivered (the start value stands), `null` clears.
3160
+ */
3161
+ source?: ValueExpr;
3162
+ /**
3163
+ * P18 session 126 (EXP-011 §93). The field and the elements above it whose Mounted a value decides: the field is not there
3164
+ * as the component mounts, so it announces the first time those values all say mounted (its later mounts announce only a
3165
+ * real change, GAM-009 R10, and the field keeps what it holds). Absent or empty, it announces as the component mounts.
3166
+ */
3167
+ gates?: string[];
3168
+ comment: string;
3169
+ }
3170
+ /**
3171
+ * P18-S128-FIELDSET and P18-S127-CLEAROUT (EXP-011 §97). A text field's Set or Clear, carried on the `state-set` of the
3172
+ * field's own row, so every walker that reads a state write reads this one; the emitter prints the rest.
3173
+ *
3174
+ * The runtime (`text-input.ts`, `TextInput.tsx`): **Set** writes `_internal.text`, the Value as last delivered (`undefined`
3175
+ * is never delivered, `null` is `''`) or `''` once a Clear has run, and **nothing while the field has focus**; **Clear**
3176
+ * writes `''`, focused or not. Both then call the field's own `setText`, which sends the text to every sink of the Value and
3177
+ * fires Value Changed, changed or not. So the exported write is followed by the field's own change chain, with the text
3178
+ * written where the chain reads the field, as the mount announcement prints it (P18-S116-FIELDMOUNT).
3179
+ *
3180
+ * 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
3181
+ * Value never delivered leaves (the typed-in Value, or `''`); the emitter writes `start` for `undefined`, `''` for `null`,
3182
+ * else `String(value)`. On a Clear, `expr` is the literal `''`.
3183
+ */
3184
+ export interface FieldWrite {
3185
+ inputId: string;
3186
+ port: 'set' | 'clear';
3187
+ start?: string;
3188
+ }
3189
+ /** The seed as the action it prints — one patch, read off the plan so the import sweep and the emitter cannot disagree. */
3190
+ export declare function objectSeedAction(seed: ObjectSeedPlan): HandlerAction;
3191
+ /**
3192
+ * A `Record` whose `Fetch` is unwired (EXP-011 §43, P18-S113-IDBINDREAD): the Id binds the row to the client's copy of
3193
+ * that record, from an effect keyed on the Id. `action` is the node's `record-fetch` with empty chains (a Done or a
3194
+ * Failure wired is refused, as neither fires without a Fetch); it carries the Id and the row the planner fills, and
3195
+ * the emitter prints the bind from it, never the read.
3196
+ */
3197
+ export interface RecordEffectPlan {
3198
+ nodeId: string;
3199
+ action: HandlerAction;
3200
+ comment: string;
3201
+ /** The project declares a backend, so the client holds records; with none, nothing is held and the row stays unset. */
3202
+ held: boolean;
3203
+ /**
3204
+ * "Record properties" ticked (the default): the row follows every later write to the copy (`onModelChangedCallback`).
3205
+ * Unticked, the bind flags the outputs once, so the row is the copy as it was at the bind.
3206
+ */
3207
+ follows: boolean;
3208
+ }
3209
+ export interface RepeaterPlan {
3210
+ nodeId: string;
3211
+ /** Legacy component path of the template ("/Components/PuppyCard"), or null when unset. */
3212
+ templatePath: string | null;
3213
+ /** The DbCollection2 node wired into `items`, or null when nothing statically known feeds it. */
3214
+ itemsQueryId: string | null;
3215
+ /** The named client-side array wired into `items` (Collection2.items), or null. */
3216
+ itemsCollectionName: string | null;
3217
+ /**
3218
+ * The identity mapping parsed from the effective mapping script (authored parameter, else the
3219
+ * declared port's default — the fixture's trap). **No script at all is 'template-inputs'**:
3220
+ * the runtime then identity-maps item properties onto same-named component inputs by itself
3221
+ * (foreach.tsx), so the mapping is the template's input names, resolved at emit. Null when
3222
+ * the script is anything beyond a static string→string `map({...})` literal; that repeater
3223
+ * defers to EXP-003.
3224
+ */
3225
+ mapping: Array<{
3226
+ input: string;
3227
+ field: string;
3228
+ }> | 'template-inputs' | null;
3229
+ /**
3230
+ * A list-typed vocabulary source wired into `items` (CONTROLLED-STATE-TARGET §4e) — a
3231
+ * prop-fed or state-fed plain list. Emitted `(expr ?? []).map(…)`: `?? []` is foreach.tsx's
3232
+ * own "empty arrival clears the list", rows key by index (no identity column, and the
3233
+ * runtime re-renders on array identity change anyway — grade Q).
3234
+ */
3235
+ itemsExpr?: ValueExpr;
3236
+ /**
3237
+ * P18 session 138 (EXP-011 §108). The items are a placed component's lifted value output: known only once the child's plan
3238
+ * exists, so `liftInto` turns this into `itemsExpr` (the lifted row) or takes it back with the row relays. Until then the
3239
+ * repeater counts as fed, so the first plan keeps its relays.
3240
+ */
3241
+ itemsLifted?: {
3242
+ connectionKey: string;
3243
+ instanceId: string;
3244
+ port: string;
3245
+ };
3246
+ /**
3247
+ * A Static Data node whose parsed rows are hoisted to a module constant (STATIC-DATA §3).
3248
+ * Deliberately not `itemsExpr`: that path's contract is "no statically-known item shape, so
3249
+ * fields read as `any` and every mapped input is kept". Static Data's shape *is* known, so it
3250
+ * takes the `collection` treatment instead — derived type, `allowedFields`, a real key.
3251
+ */
3252
+ itemsStaticId?: string;
3253
+ }
3254
+ /** EXP-011 §99. One relayed row value: the repeater, the row's output, the row's input it passes through, its state row. */
3255
+ export interface RowRelayPlan {
3256
+ repeaterId: string;
3257
+ output: string;
3258
+ input: string;
3259
+ stateName: string;
3260
+ }
3261
+ /**
3262
+ * One Static Data node hoisted to a frozen module constant (STATIC-DATA-TARGET §3).
3263
+ *
3264
+ * The node's three inputs are all `allowEditOnly` (staticdata.ts), so no wire can feed them and
3265
+ * the rows are knowable by construction rather than by a solver that happened to succeed. Only
3266
+ * nodes that pass every §4 gate get a plan; the rest defer with their reason named.
3267
+ */
3268
+ export interface StaticDataPlan {
3269
+ nodeId: string;
3270
+ /** `PRODUCTS_DATA` — SCREAMING_SNAKE of the authored label, deduped against reserved names. */
3271
+ constName: string;
3272
+ /** `FeaturedProduct` — the row type alias. */
3273
+ typeName: string;
3274
+ /** Nested object/array aliases this row type references, declaration order (§3.1). */
3275
+ nestedTypes: Array<{
3276
+ name: string;
3277
+ decl: string;
3278
+ }>;
3279
+ /** The row type's own field list, source order. */
3280
+ fields: Array<{
3281
+ name: string;
3282
+ tsType: string;
3283
+ optional: boolean;
3284
+ }>;
3285
+ /** The parsed rows, verbatim — emitted as a frozen literal. */
3286
+ rows: Array<Record<string, unknown>>;
3287
+ /**
3288
+ * `id` when every row carries a unique, primitive, non-null one — which mirrors the runtime's
3289
+ * own identity notion, since `Collection.set` mints each row into a Model and treats `id` as
3290
+ * the record's identity rather than ordinary data (collection.ts:542). Null ⇒ key by index.
3291
+ */
3292
+ keyField: string | null;
3293
+ }
3294
+ /**
3295
+ * One conditional popup render in the hosting component (POPUPS-TARGET §2, §5). Show Popup
3296
+ * nodes opening the same target with identical literal params share a slot — safe because a
3297
+ * node with any consumed close outcome defers, so shared keys never conflate observable
3298
+ * behaviour.
3299
+ */
3300
+ export interface PopupSlotPlan {
3301
+ /** The slot's string literal (`'AboutDialog'`), from the target path's last segment. */
3302
+ slotKey: string;
3303
+ /** Legacy component path of the popup component ("/Components/AboutDialog"). */
3304
+ targetLegacy: string;
3305
+ /** Literal `popupParam-*` values, keyed by the target's input port name — props at emit. */
3306
+ params: Array<{
3307
+ input: string;
3308
+ value: string | number | boolean;
3309
+ }>;
3310
+ /**
3311
+ * HLT-014 — Show Popup's `Close On Escape`. The emitted `PopupDialog` closes on Escape unless this is
3312
+ * `false`, matching the runtime's default-on.
3313
+ */
3314
+ closeOnEscape: boolean;
3315
+ /** HLT-014 — Show Popup's `Accessible Name`; absent means "named by the popup's first heading". */
3316
+ label?: string;
3317
+ /** HLT-014 — Show Popup's `Modal`. `false` renders the plain overlay: not a dialog (a toast). */
3318
+ modal: boolean;
3319
+ }
3320
+ export interface ComponentFilePlan {
3321
+ dir: 'pages' | 'components';
3322
+ /** "ThankYou" — file base name, deduplicated per directory. */
3323
+ fileBase: string;
3324
+ /** "ThankYouPage" for pages, "PuppyCard" for components. */
3325
+ symbol: string;
3326
+ }
3327
+ export interface ComponentPlan {
3328
+ path: string;
3329
+ legacyPath: string;
3330
+ role: 'page' | 'component';
3331
+ /** Null when nothing is emitted for this component (no visual root, or the router shell). */
3332
+ file: ComponentFilePlan | null;
3333
+ /** Why file is null, for the report. */
3334
+ skipReason?: string;
3335
+ /**
3336
+ * Which *kind* of skip {@link skipReason} describes — EXP-004's report leads with what worked,
3337
+ * and these two do not belong on the same side of that line.
3338
+ *
3339
+ * 🔴 **A discriminator rather than a read of the sentence.** There are exactly two skips: the
3340
+ * router shell, which the scaffold emits as `src/App.tsx` and is therefore *done*, and a
3341
+ * logic-only component, which is *not*. Both arrive as one `notes` string prefixed with the
3342
+ * component path, and a report that grouped them by matching the prose would call a working
3343
+ * export broken the first time either sentence was reworded. Set at the two sites that know.
3344
+ */
3345
+ skipKind?: 'scaffolded' | 'deferred';
3346
+ /** The node the JSX root renders (the Page node for pages). */
3347
+ rootId: string | null;
3348
+ /** A page's sole Group child merged into the page div (TARGET-OUTPUT §2's shape). */
3349
+ collapsedGroupId?: string;
3350
+ head?: {
3351
+ title?: string;
3352
+ description?: string;
3353
+ };
3354
+ /** Root node's authored label — the component's doc comment. */
3355
+ docComment?: string;
3356
+ props: PropPlan[];
3357
+ /**
3358
+ * Props that carry the enclosing repeater's row (EXP-011 Tier 1.1) — an `Object` node in
3359
+ * "From repeater" mode, compiled away into the component's interface
3360
+ * (EXP-002-MODEL2-TARGET-OUTPUT §4). Minting order.
3361
+ *
3362
+ * 🔴 `field` is carried separately from `prop` and is **not** decoration. The prop name is
3363
+ * deduplicated against everything else the component declares, so an `Object` reading `mood`
3364
+ * in a component that already has a `mood` input becomes the prop `mood2` — and the parent
3365
+ * must still bind it from `item.mood`. Deriving the field from the prop name would silently
3366
+ * read a field no row has, exactly where the collision made it hardest to notice.
3367
+ */
3368
+ rowProps: Array<{
3369
+ prop: string;
3370
+ field: string;
3371
+ /**
3372
+ * EXP-011 §57. Set when the row prop is a `Repeater Item`'s Item Id (field `id`, typed `string` on this
3373
+ * side): the parent then names the node and the port when a feed cannot supply it, rather than the
3374
+ * generic "no row carries this field".
3375
+ */
3376
+ repeaterItem?: string;
3377
+ }>;
3378
+ /**
3379
+ * Declared signal outputs as callback props (`onWaved?: () => void`), declaration order —
3380
+ * the parent side reads the same list off the target's plan (COMPONENT-OUTPUTS-TARGET §2).
3381
+ */
3382
+ outputProps: Array<{
3383
+ port: string;
3384
+ prop: string;
3385
+ }>;
3386
+ /** Render children per node, collapse applied, logic nodes filtered out. */
3387
+ childrenOf: Record<string, string[]>;
3388
+ roleOf: Record<string, RenderRole>;
3389
+ /**
3390
+ * EXP-011 §51. The `Component Children` the runtime honours (chooseChildSlot), when one exists —
3391
+ * the component then declares a `children` prop, and every instance passes its placed children.
3392
+ * Decided by the pure rule on BOTH sides so they cannot disagree; whether the marker also
3393
+ * RENDERS (`roleOf[childSlot] === 'slot'`) is the walk's answer — a marker below a node that did
3394
+ * not draw declares the prop and renders nothing, which is what the running app does.
3395
+ */
3396
+ childSlot?: string;
3397
+ /**
3398
+ * EXP-010. The kit node definition behind each rendered `'custom'` node, plus the module it came
3399
+ * from. Carried on the plan rather than looked up again at emit time so there is one answer to
3400
+ * "which kit owns this node" — the emitted wrapper's import path is derived from it.
3401
+ */
3402
+ customNodes: Record<string, {
3403
+ moduleDir: string;
3404
+ def: KitNodeIR;
3405
+ }>;
3406
+ /**
3407
+ * EXP-010. A custom node's value outputs that something in this component reads, as local state:
3408
+ * the wrapper calls `onXChanged`, the setter writes the row, and the sink binds to it.
3409
+ *
3410
+ * ⚠️ Local, unlike {@link instanceLifted}, and that is not an oversight. A component instance's
3411
+ * value output has to wait for `planProject`'s second phase because whether the *child* lifted
3412
+ * the port is a fact about another plan. A kit node publishes its own outputs from its own
3413
+ * definition, which this component already has — there is nothing to wait for.
3414
+ */
3415
+ customLifted: Record<string, Array<{
3416
+ port: string;
3417
+ setterName: string;
3418
+ }>>;
3419
+ /**
3420
+ * EXP-010 AC3. Visual children of a rendered node that the export could not identify at all,
3421
+ * per parent id, in child order — the emitter prints a comment where each one sat.
3422
+ *
3423
+ * 🔴 **Unidentifiable, not merely undrawn**, and the difference is the whole criterion. A
3424
+ * `Masonry` that defers is a built-in with a reason the report can state and a slice that will
3425
+ * one day handle it. A node whose type is in no catalog and no loaded kit is *the author's own
3426
+ * node*, and before EXP-010 it left the emitted JSX with a gap and nothing in the file to say a
3427
+ * node had ever been there. That silence is the defect this task exists to end, and it is not
3428
+ * ended by a kit that loads — it is ended by the case where the kit does **not**.
3429
+ */
3430
+ droppedChildren: Record<string, Array<{
3431
+ nodeId: string;
3432
+ type: string;
3433
+ reason: string;
3434
+ }>>;
3435
+ /**
3436
+ * Every wire this plan refused, with the endpoints kept as *values* (EXP-004's in-code markers).
3437
+ *
3438
+ * 🔴 **The emitter cannot re-derive this, and must not try.** "A wire whose sink has no binding"
3439
+ * looks like the same population and is not: a signal folded into a handler chain, a control's
3440
+ * state merged into its `onChange`, a `mounted` wire riding the render wrapper and a wire into a
3441
+ * collapsed group all leave no binding behind while having translated perfectly. Guessing from
3442
+ * absence would put `TODO(export)` markers on working code — a worse failure than the missing
3443
+ * marker this closes, in the one file whose job is not misleading its reader.
3444
+ *
3445
+ * So each refusal records itself here, beside the note it files, and `wireNote` is what keeps
3446
+ * the two from drifting: it writes the row and returns the sentence.
3447
+ */
3448
+ droppedWires: DroppedWire[];
3449
+ /** nodeId → toProperty → source, for value wires landing on rendered nodes. */
3450
+ bindings: Record<string, Record<string, BindingSource>>;
3451
+ /** nodeId → source signal port → actions, for signal wires resolved to handler statements. */
3452
+ handlers: Record<string, Record<string, HandlerAction[]>>;
3453
+ /** Rendered text input id → actions its onChange performs (the wired-onTextChanged rule). */
3454
+ changeHandlers: Record<string, HandlerAction[]>;
3455
+ /** Event Receivers this component hosts as useSignal subscriptions. */
3456
+ receivers: ReceiverPlan[];
3457
+ /** P18-S126-INSIGNAL. One effect per signal input whose wires run a chain. */
3458
+ inputPulses: InputPulsePlan[];
3459
+ /** P18-S126-INSIGNAL. One count per placed component's signal input a sender here attached to. */
3460
+ instancePulses: InstancePulsePlan[];
3461
+ /** P134-HTX004-SELECTREFUSED. One per placed component's signal whose chains read a value it lifts: run one commit late. */
3462
+ liftedPulseChains: LiftedPulseChainPlan[];
3463
+ /** Popup slots this component renders, registration order — earned by attachment (§2). */
3464
+ popups: PopupSlotPlan[];
3465
+ /** True when a translated Close Popup attached — the component declares `onClose` (§4). */
3466
+ closesPopup: boolean;
3467
+ /**
3468
+ * P134-S13-CLICKBUBBLES. The drawn nodes where the running app keeps a click (`pointerlisteners.ts`
3469
+ * `stopsClickPropagation`, FH-015 slice 2): Block Pointer Events on; else Click Bubbling "never"; else, on its default
3470
+ * "auto", a Click port with a connection (any, translated or not: the runtime asks `hasConnections()`). "always" lets
3471
+ * it through. Each printer stops the click there, before the node's own chain, so a wired ancestor's Click does not run.
3472
+ */
3473
+ clickStops: string[];
3474
+ queries: QueryPlan[];
3475
+ /** Record verbs translated in this component (RECORD-VERBS-TARGET §4), compile order. */
3476
+ mutations: MutationPlan[];
3477
+ /** The user-family nodes that translated — what `src/api/session.ts` must export. */
3478
+ sessionCalls: SessionCallPlan[];
3479
+ /**
3480
+ * HTTP Requests translated in this component (EXP-011 Tier 1.2), compile order — what
3481
+ * `src/api/http.ts` must export. Earned by attachment, exactly as the record verbs are.
3482
+ */
3483
+ httpCalls: HttpCallPlan[];
3484
+ /** EXP-011 §41. */
3485
+ cloudCalls: CloudCallPlan[];
3486
+ /** EXP-011 §43. */
3487
+ recordReads: RecordReadPlan[];
3488
+ /** EXP-011 §45. The files nodes that translated, compile order — earned by attachment. */
3489
+ fileOps: FileOpPlan[];
3490
+ repeaters: Record<string, RepeaterPlan>;
3491
+ /** Static Data nodes hoisted to module constants (STATIC-DATA-TARGET §3), resolution order. */
3492
+ staticData: StaticDataPlan[];
3493
+ /**
3494
+ * Re-host wrapper definitions by node id (EXP-003 §4), registered the moment a read of the
3495
+ * node resolves — the emit layer prints exactly the wrappers that surviving expressions
3496
+ * reference, so a definition nothing kept costs nothing. Insertion order is resolution order
3497
+ * and therefore deterministic.
3498
+ */
3499
+ jsFunctions: Record<string, JsFunctionPlan>;
3500
+ /**
3501
+ * Nodes carrying author-written JavaScript that this component did **not** translate, and that
3502
+ * are not Function/Expression/Visual Function — those three are `jsFunctions`, and the emit
3503
+ * layer already preserves a refused one's body as a comment.
3504
+ *
3505
+ * 🔴 This is the same loss one node-family over, and it was silent. A `Map Collection` whose
3506
+ * Script does more than name properties is deferred with a reason and an in-code marker naming
3507
+ * the node — and its script text appeared **nowhere in the exported repo**, which is exactly
3508
+ * the case where that text is the only statement of what the developer now has to write
3509
+ * (EXP-004, and §20's argument for the Function case verbatim).
3510
+ *
3511
+ * Node order, so the output is deterministic.
3512
+ */
3513
+ refusedScripts: RefusedScriptPlan[];
3514
+ /**
3515
+ * State rows (CONTROLLED-STATE-TARGET §3), registered the moment a use resolves — the emit
3516
+ * layer prints exactly the vars that surviving actions/expressions/effects reference.
3517
+ */
3518
+ stateVars: StateVarPlan[];
3519
+ /** Sync effects (§3.4), registration order — one per translated wired control-state input. */
3520
+ syncEffects: SyncEffectPlan[];
3521
+ /** Push effects (§3.5), registration order — the lifted value outputs' child side. */
3522
+ pushEffects: PushEffectPlan[];
3523
+ /**
3524
+ * EXP-011 §99. A repeater's relayed row values (`itemOutput-<output>`) this component lifts: a state row per value,
3525
+ * written in every row's relayed-signal callback from what the parent hands that row for its input `input`, before
3526
+ * the signal's chain, as the runtime's `itemOutputSignalTriggered` copies the row's outputs and then sends the signal.
3527
+ */
3528
+ rowRelays: RowRelayPlan[];
3529
+ /** Reactive Conditions (LOGIC-TARGET §10), compile order — one useEffect each. */
3530
+ branchEffects: BranchEffectPlan[];
3531
+ /** Value Changed effects (EXP-011 §39), compile order — one useEffect + one useRef each. */
3532
+ valueChangedEffects: ValueChangedEffectPlan[];
3533
+ /** P18-S119-TICKEDRUN. Run-wired Functions a ticked input also runs, node order — one useEffect + one useRef each. */
3534
+ tickedRuns: TickedRunPlan[];
3535
+ /** EXP-011 §43. Records whose Fetch is unwired, compile order — one useEffect keyed on the Id each. */
3536
+ recordEffects: RecordEffectPlan[];
3537
+ /** EXP-011 §67. Variables with an authored Value, node order — one mount effect each. */
3538
+ variableSeeds: VariableSeedPlan[];
3539
+ /** EXP-011 §71. Object nodes with values typed into their own property inputs, node order — one mount effect each. */
3540
+ objectSeeds: ObjectSeedPlan[];
3541
+ /** P18-S116-FIELDMOUNT. Text inputs that announce their Value as they mount, node order — one mount effect each. */
3542
+ fieldMounts: FieldMountPlan[];
3543
+ /** EXP-011 §48. CSS Definitions, node order — one module constant + one mount effect each. */
3544
+ styleSheets: StyleSheetPlan[];
3545
+ /** EXP-011 §49. The States nodes that translated, registration order. */
3546
+ statesMachines: StatesPlan[];
3547
+ /** EXP-011 §49. The Animate To Value nodes that translated, registration order. */
3548
+ animations: AnimationPlan[];
3549
+ /** EXP-011 §52. The Script nodes that translated, registration order. */
3550
+ scripts: ScriptPlan[];
3551
+ /** EXP-011 §53. The Run Tasks nodes hosted here, registration order. */
3552
+ runTasks: RunTasksPlan[];
3553
+ /** EXP-011 §53. Set when this component is a Run Tasks template whose start chain translated. */
3554
+ task?: TaskPlan;
3555
+ /** EXP-011 §57. Repeater Items whose Added chain translated — a once-on-mount effect each. */
3556
+ repeaterItems: RepeaterItemPlan[];
3557
+ /** EXP-011 §53. Why a component named as a template is not one — repeated by every host that names it. */
3558
+ taskRefusal?: string;
3559
+ /** EXP-011 §54. The On App Error boundaries hosted here, registration order. */
3560
+ appErrors: AppErrorPlan[];
3561
+ /** EXP-011 §58. */
3562
+ streams: StreamPlan[];
3563
+ /** EXP-011 §59. The Screen Resolution hooks this component reads, registration order. */
3564
+ screenResolutions: ScreenResolutionPlan[];
3565
+ /** EXP-011 §60. The Component Object record this component materializes (record mode), if any. */
3566
+ componentObject?: ComponentObjectRecordPlan;
3567
+ /** EXP-011 §60. The nearest ancestor's record this component reads or writes through the parent pair, if any. */
3568
+ parentObject?: ParentComponentObjectPlan;
3569
+ /** EXP-011 §63. The Drag nodes rendered here, registration order. */
3570
+ drags: DragPlan[];
3571
+ /** EXP-011 §61. The Component Stacks this component renders, registration order — one hook line each. */
3572
+ pageStacks: PageStackPlan[];
3573
+ /**
3574
+ * EXP-011 §61. Set when a Pop Component Stack attached here: the interface then declares the reserved prop
3575
+ * `pageStackEntry?: PageStackEntryHandle`, and every stack row that shows this component passes it.
3576
+ */
3577
+ popsStack: boolean;
3578
+ /** EXP-011 §55. The `Create New Array` handles this component holds, allocation order. */
3579
+ mintedArrays: MintedArrayPlan[];
3580
+ /**
3581
+ * EXP-011 §54. Set when this is the router shell kept as a file for the logic beside its Router — a
3582
+ * null-rendering component the scaffold's `App.tsx` renders inside the router, for the app's life. The
3583
+ * Router itself is still `collapsed into src/App.tsx`.
3584
+ */
3585
+ shell?: true;
3586
+ /**
3587
+ * EXP-021 slice 1 (P18 session 136). Set when this is a logic component — no node in it draws, and a component places
3588
+ * it — exported as a hook, `use<Name>(props)`, with the props interface a visual component has: the placement's inputs,
3589
+ * its signal inputs' pulses, its signal outputs' callbacks, its lifted value outputs. Its placements are called, not drawn
3590
+ * (`hookCalls`). The runtime keeps a component instance whether or not anything draws it, as a hook call does.
3591
+ */
3592
+ hook?: true;
3593
+ /** EXP-021 slice 1. The placements of logic components in this component, in source order: one hook call each. */
3594
+ hookCalls: string[];
3595
+ /**
3596
+ * Value output ports this component lifts (§4d child side) — the parent side consults this
3597
+ * list off the target's plan, so parent and child agree by construction (the s10 rule).
3598
+ */
3599
+ liftedOutputProps: Array<{
3600
+ port: string;
3601
+ prop: string;
3602
+ tsType: string;
3603
+ }>;
3604
+ /** Instance id → lifted callbacks the parent passes (`onXChanged={setX}`) (§4d parent side). */
3605
+ instanceLifted: Record<string, Array<{
3606
+ prop: string;
3607
+ setterName: string;
3608
+ /**
3609
+ * P18-S141-PASSLATE. `setterName` is this component's own output callback, not a row's setter: the placement's value
3610
+ * is handed straight on (`onAppChanged={onAppChanged}`), typed `tsType`, cast to `castTo` where the output's type
3611
+ * differs (then through one stable callback).
3612
+ */
3613
+ forward?: {
3614
+ tsType: string;
3615
+ castTo?: string;
3616
+ };
3617
+ }>>;
3618
+ /**
3619
+ * Parent-side lifted wires awaiting the target's plan (planProject's second phase): a
3620
+ * consumed instance value output binds only when the child actually lifted the port —
3621
+ * otherwise the parent would pass a prop the child's emitted interface does not declare.
3622
+ */
3623
+ pendingLifted: Array<{
3624
+ connectionKey: string;
3625
+ instanceId: string;
3626
+ targetLegacy: string;
3627
+ port: string;
3628
+ toNodeId: string;
3629
+ toProperty: string;
3630
+ /** P18-S127-SIBLINGVALUE. The sink is another placed component's input: a lift that fails is named on its marker. */
3631
+ intoInstance?: true;
3632
+ /** EXP-011 §108. The sink is a For Each's items: the lifted row becomes its `itemsExpr`. */
3633
+ intoItems?: true;
3634
+ /**
3635
+ * EXP-021 AC4 (session 139). The sink is this component's own Component Outputs value port (the Editor's `items`, from
3636
+ * the Parse question set it places): the lifted row is pushed on, a pass-through lift, decided once the child's lift is.
3637
+ */
3638
+ intoOutputs?: {
3639
+ prop: string;
3640
+ tsType: string;
3641
+ };
3642
+ /**
3643
+ * P134-HTX004-SELECTREFUSED. The sink is a read in a chain (a Set Variable's Value): the row was allocated when the
3644
+ * chain compiled (`row`), and only the child's callback is owed. A child that does not lift it refuses the read.
3645
+ */
3646
+ intoRead?: {
3647
+ row: string;
3648
+ };
3649
+ }>;
3650
+ dispositions: Record<string, Disposition>;
3651
+ /** Dropped wires, unhandled constructs — EXP-004's report feed. Nothing silently dropped. */
3652
+ notes: string[];
3653
+ /**
3654
+ * EXP-013. Every node `dispositions` refused, as a row with the refused node that starves it.
3655
+ * Built from `dispositions` at each exit, after the sweeps — see {@link RefusedNode}.
3656
+ */
3657
+ refusals: RefusedNode[];
3658
+ }
3659
+ export interface ProjectPlan {
3660
+ plans: ComponentPlan[];
3661
+ byLegacyPath: Map<string, ComponentPlan>;
3662
+ /**
3663
+ * Legacy component path → the scaffold's route-table row for it.
3664
+ *
3665
+ * 🔴 **The whole row, not the url string it used to hold.** A parameterised page has three
3666
+ * paths — the authored `/product/{id}`, the react-router pattern `/product/:id` and the
3667
+ * concrete url a Navigate builds — and a `Map<string, string>` can only carry one of them.
3668
+ * Carrying the row means the route table and the navigations into it cannot disagree.
3669
+ */
3670
+ pageByLegacy: Map<string, ScaffoldPage>;
3671
+ /** Collections needing an api stub module, in first-use order. */
3672
+ stubCollections: string[];
3673
+ /** App-wide Variables, discovery order — src/stores/variables.ts when non-empty. */
3674
+ variables: VariablePlan[];
3675
+ /** Event channels, discovery order — src/events.ts when non-empty. */
3676
+ channels: ChannelPlan[];
3677
+ /** Named Global Stores, discovery order — one src/stores/<exportName>.ts each. */
3678
+ stores: StorePlan[];
3679
+ /** Named client-side arrays, discovery order — one src/collections/<exportName>.ts each. */
3680
+ collections: CollectionPlan[];
3681
+ /**
3682
+ * Which components a route can reach (reach.ts). Not a disposition and never a reason to emit
3683
+ * less — every component is still emitted. It is what lets the report say whether a deferral is
3684
+ * work the app is missing or a node in a component nothing renders, which over this corpus is
3685
+ * the difference between 85.00% and 93.38%.
3686
+ */
3687
+ reachability: Reachability;
3688
+ /**
3689
+ * EXP-010. Node types more than one kit registers, as `type (kit that lost)`.
3690
+ *
3691
+ * 🔴 A collision, not a warning about one: `registerModule` refuses the second registration, so
3692
+ * the running app shows the *first* kit's node. The export follows that rule and says which kit
3693
+ * it therefore ignored — silently taking either one would emit an app that does not match the
3694
+ * one the author is looking at.
3695
+ */
3696
+ kitDuplicates: string[];
3697
+ }
3698
+ /** P134-S13-CLICKBUBBLES. {@link ComponentPlan.clickStops}, by the runtime's rule. */
3699
+ export declare function clickStopsOf(component: ComponentIR, roleOf: Record<string, RenderRole>): string[];
3700
+ /**
3701
+ * P134-S14-CLICKNOISE (Richard's R10, 2026-11-04: print the stop "only where it matters"). {@link clickStopsOf} is where
3702
+ * the running app keeps a click; a stop printed where nothing above listens for a click changes nothing, and it was 790
3703
+ * of the corpus's 910 wired Clicks. So a stop is kept only where something above could hear the click:
3704
+ *
3705
+ * - a node above it in its own component whose Click is wired;
3706
+ * - a placed component's slot it sits in (its own parent is an instance), when that component wires any Click;
3707
+ * - its component placed under such a node anywhere it is placed: an instance, a For Each's row, a Router's page, a
3708
+ * Page Stack's page;
3709
+ * followed up through every placement, a cycle answering no. A popup is drawn at the top: nothing above it listens.
3710
+ *
3711
+ * And kept everywhere, to stay safe, where the plan cannot see what listens: an app that draws a kit (its React
3712
+ * component may listen on the document), and a component a node names in a way not listed here.
3713
+ */
3714
+ export declare function narrowClickStops(ir: ExportIR, plans: ComponentPlan[]): void;
3715
+ export declare function planProject(ir: ExportIR, catalog: CatalogIndex): ProjectPlan;
3716
+ /**
3717
+ * P18-S122-DEADROW, P18-S123-UNDRAWNTRIGGER. What a component's next plan refuses before it attaches anything: chains on
3718
+ * an element the export never draws (`owners`), Did Mount chains on an element a value mounts (`mounts`, which the
3719
+ * emitter does not run), and the render reads of a Function's row that nothing the export runs ever writes (`rows`, by
3720
+ * the Function's id). Each maps a node id to the sentence the refusal says.
3721
+ *
3722
+ * 🔴 **A row's other readers are kept, and said.** A Function input or a Set Variable that reads a dead row reads what
3723
+ * HEAD's export read (nothing yet); refusing the reader would take a chain that works for every other input with it
3724
+ * (the older Site Builder copies' "The slug to show" reads the URL's slug, and the never-written home slug only as a
3725
+ * fallback). They are named in a note instead.
3726
+ */
3727
+ /** P18 session 126 (EXP-011 §93). The words a refusal under a page's own wired Group starts with; the emitter says the same. */
3728
+ export declare const UNCARRIED_PAGE_GROUP = "the page's own Group,";
3729
+ export interface PlanExclusions {
3730
+ owners: Map<string, string>;
3731
+ mounts: Map<string, string>;
3732
+ rows: Map<string, string>;
3733
+ /**
3734
+ * EXP-021 AC4 (P18 session 139). A placed component's pulses into another placed component whose value it also hands
3735
+ * that component, where the lift pass dropped the value: by `<sender id>\u0000<target id>`. The runtime delivers the
3736
+ * value before the pulse (rocket-school's `app` then `write`); the export would run the pulse on a stale value.
3737
+ */
3738
+ pulses: Map<string, string>;
3739
+ /**
3740
+ * P134-HTX004-SELECTREFUSED. A placed component's value output a chain here reads, where that component does not lift
3741
+ * it: by `<instance id>\u0000<port>`. The read is refused by name in the next plan, as a feed of the same output is.
3742
+ */
3743
+ reads: Map<string, string>;
3744
+ }
3745
+ /**
3746
+ * Why an element of a planned component is never drawn, or `null` when it may be. The emitter's own rule (`render` in
3747
+ * `component.ts`): an element, or an element above it, whose Mounted is bound to a statically false value, or not bound
3748
+ * and authored false, is removed from the tree. A Mounted bound to anything else draws it when that value says.
3749
+ */
3750
+ export declare function undrawnReasonOf(plan: ComponentPlan, component: ComponentIR, id: string): string | null;
3751
+ export declare function rowlessReasonOf(plan: ComponentPlan, repeaterId: string): string | null;
3752
+ /**
3753
+ * The triggers of one planned component that the emitted app never runs (see {@link PlanExclusions}). Read off the
3754
+ * finished plan: its handlers, its bindings after the lift phase, and every action list it carries.
3755
+ */
3756
+ export declare function unreachedTriggersOf(plan: ComponentPlan, component: ComponentIR): PlanExclusions;
3757
+ /**
3758
+ * A component's output interface, from its Component Outputs nodes' declarations — the union
3759
+ * across nodes, first declaration of a name winning (the runtime merges the port sets the same
3760
+ * way). Signal ports become callback props (COMPONENT-OUTPUTS-TARGET §2): a name that is
3761
+ * already an `onX` identifier is kept verbatim, anything else becomes `on` + PascalCase. A
3762
+ * port that cannot become a prop — no identifier material, or a collision with an input prop
3763
+ * or another output — fails with a reason rather than being silently renamed. Deterministic
3764
+ * over the ComponentIR alone, so the child's plan and every parent's plan agree.
3765
+ */
3766
+ export interface OutputInterface {
3767
+ props: Array<{
3768
+ port: string;
3769
+ prop: string;
3770
+ }>;
3771
+ failed: Array<{
3772
+ port: string;
3773
+ reason: string;
3774
+ }>;
3775
+ /** Declared value-kind output ports — the lifted-state candidates (CONTROLLED-STATE §4d). */
3776
+ valuePorts: string[];
3777
+ /**
3778
+ * Value ports named as lifted callback props (`on` + PascalCase + `Changed` — the s10 name
3779
+ * source; collisions fail the port, never silently rename). Whether a port actually lifts is
3780
+ * the owning component's plan's to decide (its feed must resolve); the name is decided here,
3781
+ * off the ComponentIR alone, so the child's plan and every parent's plan agree.
3782
+ */
3783
+ valueProps: Array<{
3784
+ port: string;
3785
+ prop: string;
3786
+ tsType: string;
3787
+ }>;
3788
+ }
3789
+ export declare function signalInputPortsOf(component: ComponentIR | undefined): Set<string>;
3790
+ export declare function componentOutputInterface(component: ComponentIR): OutputInterface;
3791
+ /** node type → the kit node that registers it, and which module folder that kit is. */
3792
+ export type KitIndex = Map<string, {
3793
+ moduleDir: string;
3794
+ def: KitNodeIR;
3795
+ }>;
3796
+ /**
3797
+ * Every node type the project's kits register (EXP-010).
3798
+ *
3799
+ * ⚠️ **First registration wins, and a duplicate is not silently discarded** — the running app has
3800
+ * the same collision (`registerModule` refuses the second) so the export must agree with it rather
3801
+ * than pick the later one and render a different node than the app does. The caller reports it.
3802
+ */
3803
+ export declare function indexKitNodes(modules: ModuleIR[]): {
3804
+ index: KitIndex;
3805
+ duplicates: string[];
3806
+ };
3807
+ /**
3808
+ * EXP-013 AC4 — is losing this node losing a **pathway** rather than a feature?
3809
+ *
3810
+ * Three families, from Richard's ruling (EXP-011 §50): a backend verb (the record verbs, the
3811
+ * relation verbs, `Record`, `Query Records`, the user verbs, `Cloud Function`, `HTTP Request`,
3812
+ * the files), a navigation (`Navigate`, `Navigate To Path`, `External Link`), and `On App Error`,
3813
+ * whose refusal removes the app's whole error pathway. A `Set Variable` behind a refused trigger
3814
+ * is a missing feature; a `Cloud Function` behind one is the app no longer talking to its backend
3815
+ * — the verdict in the pre-flight turns on this distinction, so it lives beside the tables that
3816
+ * define the families rather than in a second list the report would keep.
3817
+ */
3818
+ /** EXP-011 §58. Which of a streaming node's value outputs read undefined before anything happened — the table's answer, shared with component.ts. */
3819
+ export declare function streamFieldMaybeUndefined(node: StreamKind, field: string): boolean;
3820
+ export declare function isPathwayType(type: string): boolean;
3821
+ /** EXP-011 §56. Every wired expression a filter tree reads, for the walkers that follow an expression's reads. */
3822
+ export declare function whereExprs(where: RecordWhere | null): ValueExpr[];
3823
+ /**
3824
+ * Parses a For Each mapping script as far as "is this a static object literal of
3825
+ * string→string" (TARGET-OUTPUT §2). Anything cleverer returns null and defers to EXP-003.
3826
+ */
3827
+ export declare function parseIdentityMapping(script: string): Array<{
3828
+ input: string;
3829
+ field: string;
3830
+ }> | null;
3831
+ /**
3832
+ * §15 — a component prop's type is what the port *declares*, and `any` when it declares nothing.
3833
+ *
3834
+ * A port type is **free text** (`AiAssistant/authoring/plan.ts`: *"names are what bind"*), so
3835
+ * there is no closed vocabulary to fall through to a default with. The editor's own PortEditor
3836
+ * panel opens a new Component Inputs port as `type: { name: '*' }` (`componentinputs.ts`), and
3837
+ * `*` is normatively the untyped wildcard — the Port Type Contract describes "an untyped Function
3838
+ * output defaulted to `*` and connected anywhere". It is two thirds of the corpus's component
3839
+ * ports (471 of 714).
3840
+ *
3841
+ * Mapping that to `string` was the emitter claiming a type the graph refused to make, and it is
3842
+ * the same disagreement §12 refused in the other direction: the declared type is a claim about
3843
+ * the port, not a check on what reaches it. Nothing coerces on delivery — `componentinputs.ts`
3844
+ * registers each output as a bare getter over `_internal.inputValues`, and the one cast on a
3845
+ * connection (`node.ts` `_setValueFromConnection`) is `object`/`array` → `string` only.
3846
+ *
3847
+ * `any` rather than `unknown` for the reason the `array` case already gives, and the reason the
3848
+ * two sibling mappers in this file — `jsOutputTsType` and `valueTsTypeOf` — both already default
3849
+ * to `any`: strict tsc rejects an `unknown` read at the sites that consume these values.
3850
+ *
3851
+ * ⚠️ `string` must stay an explicit case. It is 224 ports, and it reached its type through the
3852
+ * old default — folding it into this one would widen every genuinely-typed prop in the corpus.
3853
+ */
3854
+ /** One row of a Component Stack's Components list, resolved (EXP-011 §61). */
3855
+ interface StackPageInfo {
3856
+ id: string;
3857
+ label: string;
3858
+ /** The `pageComp-<id>` parameter — undefined when unset (the stack then refuses). */
3859
+ componentLegacy: string | undefined;
3860
+ }
3861
+ /** A `Page Stack` anywhere in the project, read off the IR once (EXP-011 §61). */
3862
+ export interface StackInfo {
3863
+ nodeId: string;
3864
+ componentPath: string;
3865
+ label: string;
3866
+ /** The authored Name, `Main` by default — `navigation-handler.ts` keys its registry on `name || 'Main'`. */
3867
+ name: string;
3868
+ pages: StackPageInfo[];
3869
+ /** The authored Start Page, or the first page's id (`resetAsync`). */
3870
+ startPage: string;
3871
+ /** Why the stack does not translate, or null. Decided from the IR alone, so a pusher in any component can ask. */
3872
+ refusal: string | null;
3873
+ }
3874
+ export interface StackIndex {
3875
+ byNode: Map<string, StackInfo>;
3876
+ byName: Map<string, StackInfo[]>;
3877
+ /** The stacks listing a component (by legacy path) among their pages — the Pop's gate. */
3878
+ hostsOf: Map<string, StackInfo[]>;
3879
+ }
3880
+ /** The stringlist parameters of a Pop (`results`, `backActions`) — split as the runtime splits them, no trimming. */
3881
+ export declare function stackStringList(value: unknown): string[];
3882
+ /**
3883
+ * EXP-011 §61. Every `Page Stack` in the project with its refusal, computed once per IR — `popupTargetLegacies`'
3884
+ * shape, because a pusher is planned in one component and its stack sits in another, and the plan runs
3885
+ * component by component. Every check here is a fact the IR states outright (an authored value, a wire into
3886
+ * the stack, a page's existence and root), never a plan-time one.
3887
+ */
3888
+ export declare function indexPageStacks(ir: ExportIR, catalog: CatalogIndex, kits: KitIndex): StackIndex;
3889
+ /**
3890
+ * The api module's names for one class, a pure function of the class name (RECORD-VERBS-TARGET
3891
+ * §4d). Queries and mutations both derive from this, so a project that reads *and* writes one
3892
+ * collection lands both in a single module rather than two that disagree about the type name.
3893
+ */
3894
+ /**
3895
+ * A schema column's declared type as TS (EXP-002 §10a) — the one mapping, read by the collection
3896
+ * module's interface and by a `Record`'s column reads (EXP-011 §43), so the two cannot disagree.
3897
+ */
3898
+ export declare function tsColumnType(columnType: string): string;
3899
+ export declare function collectionModuleNames(collectionName: string): {
3900
+ typeName: string;
3901
+ plural: string;
3902
+ moduleBase: string;
3903
+ fetchName: string;
3904
+ };