@w6w/ui 0.2.11 → 0.2.55

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 (138) hide show
  1. package/CHANGELOG.md +139 -2
  2. package/README.md +28 -10
  3. package/dist/AppPicker.d.ts +12 -1
  4. package/dist/CanvasContextMenu.d.ts +19 -0
  5. package/dist/{CodeBlock-BqXDA35P.js → CodeBlock-C-oIIYfC.js} +1436 -555
  6. package/dist/CodeBlock-C-oIIYfC.js.map +1 -0
  7. package/dist/CodeBlock-CymlfyeA.cjs +15 -0
  8. package/dist/CodeBlock-CymlfyeA.cjs.map +1 -0
  9. package/dist/CodeEditor.d.ts +4 -1
  10. package/dist/ConditionEditor.d.ts +11 -0
  11. package/dist/JsonEditor.d.ts +12 -0
  12. package/dist/ParamCustomization.d.ts +5 -0
  13. package/dist/ParamsForm.d.ts +11 -3
  14. package/dist/RouterEditor.d.ts +5 -0
  15. package/dist/StepBuilderModal.d.ts +83 -13
  16. package/dist/StepNameEditor.d.ts +11 -0
  17. package/dist/StopEditor.d.ts +5 -0
  18. package/dist/TriggerFillForm.d.ts +14 -1
  19. package/dist/WebhookListenPanel.d.ts +28 -0
  20. package/dist/WorkflowConnections.d.ts +29 -0
  21. package/dist/WorkflowConnectionsPanel.d.ts +5 -0
  22. package/dist/WorkflowFlowEditor.d.ts +67 -8
  23. package/dist/assets/script-completion.worker-DDJKkj5M.js +8290 -0
  24. package/dist/assets/script-completion.worker-DDJKkj5M.js.map +1 -0
  25. package/dist/canvas-context-menu.d.ts +27 -0
  26. package/dist/code.cjs +1 -1
  27. package/dist/code.mjs +3 -4
  28. package/dist/code.mjs.map +1 -1
  29. package/dist/components/AnchoredFlyout.d.ts +7 -0
  30. package/dist/components/ColumnsEditor.d.ts +27 -0
  31. package/dist/components/Combobox.d.ts +12 -1
  32. package/dist/components/ConfirmModal.d.ts +3 -1
  33. package/dist/components/CsvPanel.d.ts +21 -0
  34. package/dist/components/DataEntryEditor.d.ts +38 -0
  35. package/dist/components/DataEntryList.d.ts +33 -0
  36. package/dist/components/DataStoreList.d.ts +33 -0
  37. package/dist/components/DataTableGrid.d.ts +46 -0
  38. package/dist/components/EventbriteTriggerPicker.d.ts +7 -0
  39. package/dist/components/ExecutionDetail.d.ts +17 -1
  40. package/dist/components/ExecutionList.d.ts +19 -1
  41. package/dist/components/ExecutionLogPanel.d.ts +61 -3
  42. package/dist/components/ExpressionEditorModal.d.ts +33 -10
  43. package/dist/components/ExpressionOptions.d.ts +101 -15
  44. package/dist/components/HtmlPreview.d.ts +7 -0
  45. package/dist/components/JsonTree.d.ts +32 -0
  46. package/dist/components/ResolvedParams.d.ts +6 -1
  47. package/dist/components/ResultViewer.d.ts +46 -0
  48. package/dist/components/ScriptBindingsNotice.d.ts +31 -0
  49. package/dist/components/ScriptCodeField.d.ts +13 -0
  50. package/dist/components/StepStatusPill.d.ts +7 -0
  51. package/dist/components/expression-authoring.d.ts +13 -0
  52. package/dist/components/expression-dom.d.ts +17 -2
  53. package/dist/components/expression-template.d.ts +25 -3
  54. package/dist/components/icons.d.ts +1 -1
  55. package/dist/components/json-compose.d.ts +53 -0
  56. package/dist/components/result-format.d.ts +13 -0
  57. package/dist/condition-editor.d.ts +44 -0
  58. package/dist/connection-app-meta-BEXlKBgV.cjs +22 -0
  59. package/dist/connection-app-meta-BEXlKBgV.cjs.map +1 -0
  60. package/dist/connection-app-meta-ZCPakGjD.js +12422 -0
  61. package/dist/connection-app-meta-ZCPakGjD.js.map +1 -0
  62. package/dist/data-stores/data-store-view.d.ts +371 -0
  63. package/dist/eventbrite-trigger-selection.d.ts +13 -0
  64. package/dist/flow-connect.d.ts +22 -16
  65. package/dist/flow-delete.d.ts +16 -0
  66. package/dist/flow-history.d.ts +75 -0
  67. package/dist/flow-layout.d.ts +13 -0
  68. package/dist/flow-loop-colors.d.ts +39 -0
  69. package/dist/flow-loop.d.ts +98 -0
  70. package/dist/flow-node-flags.d.ts +14 -0
  71. package/dist/flow-problems.d.ts +69 -0
  72. package/dist/flow-state.d.ts +20 -0
  73. package/dist/flow-types.d.ts +137 -9
  74. package/dist/flow-utils.d.ts +27 -13
  75. package/dist/flow.cjs +4 -2
  76. package/dist/flow.cjs.map +1 -1
  77. package/dist/flow.d.ts +9 -3
  78. package/dist/flow.mjs +4028 -1585
  79. package/dist/flow.mjs.map +1 -1
  80. package/dist/index.cjs +2 -1
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.d.ts +30 -4
  83. package/dist/index.mjs +2559 -1287
  84. package/dist/index.mjs.map +1 -1
  85. package/dist/node-samples.d.ts +80 -0
  86. package/dist/output-shape.d.ts +30 -14
  87. package/dist/param-customization.d.ts +7 -0
  88. package/dist/provider.d.ts +119 -7
  89. package/dist/resolve-params.d.ts +45 -11
  90. package/dist/run-visuals.d.ts +14 -21
  91. package/dist/script-bindings.d.ts +63 -0
  92. package/dist/script-completion-service.d.ts +9 -0
  93. package/dist/script-completion.worker.d.ts +1 -0
  94. package/dist/script-completions.d.ts +2 -0
  95. package/dist/state-shape.d.ts +38 -0
  96. package/dist/step-preview-state.d.ts +155 -6
  97. package/dist/step-problems.d.ts +42 -12
  98. package/dist/test-inputs.d.ts +25 -0
  99. package/dist/trigger-registration.d.ts +28 -0
  100. package/dist/types.d.ts +70 -8
  101. package/dist/use-app-action-defs.d.ts +9 -1
  102. package/dist/use-test-input.d.ts +18 -0
  103. package/dist/workflow-connections.d.ts +78 -0
  104. package/package.json +16 -7
  105. package/src/code.css +60 -6
  106. package/src/styles/_canvas.scss +35 -0
  107. package/src/styles/_code-editor.scss +35 -0
  108. package/src/styles/_code.scss +6 -5
  109. package/src/styles/_connections.scss +64 -0
  110. package/src/styles/_copyable.scss +24 -0
  111. package/src/styles/_data-stores.scss +138 -0
  112. package/src/styles/_data-vars.scss +17 -6
  113. package/src/styles/_execution-history.scss +73 -0
  114. package/src/styles/_execution-log.scss +145 -1
  115. package/src/styles/_expression-modal.scss +106 -16
  116. package/src/styles/_fields.scss +11 -0
  117. package/src/styles/_flow.scss +166 -0
  118. package/src/styles/_fx.scss +3 -1
  119. package/src/styles/_incoming-state.scss +33 -0
  120. package/src/styles/_modal.scss +12 -0
  121. package/src/styles/_multiselect.scss +35 -0
  122. package/src/styles/_sections.scss +36 -0
  123. package/src/styles/_step-builder.scss +145 -0
  124. package/src/styles/_tester.scss +56 -0
  125. package/src/styles/_tokens.scss +27 -1
  126. package/src/styles.css +1347 -41
  127. package/src/styles.scss +2 -0
  128. package/dist/CodeBlock-BqXDA35P.js.map +0 -1
  129. package/dist/CodeBlock-CulfbnZD.cjs +0 -15
  130. package/dist/CodeBlock-CulfbnZD.cjs.map +0 -1
  131. package/dist/ConfirmModal-Ln99S9NT.js +0 -7919
  132. package/dist/ConfirmModal-Ln99S9NT.js.map +0 -1
  133. package/dist/ConfirmModal-fjHW5rN-.cjs +0 -14
  134. package/dist/ConfirmModal-fjHW5rN-.cjs.map +0 -1
  135. package/dist/Copyable-DA6eiU7w.cjs +0 -2
  136. package/dist/Copyable-DA6eiU7w.cjs.map +0 -1
  137. package/dist/Copyable-F4NTaG77.js +0 -847
  138. package/dist/Copyable-F4NTaG77.js.map +0 -1
@@ -0,0 +1,98 @@
1
+ import { Edge } from '@xyflow/react';
2
+ import { FlowEdge, FlowStep, FlowWorkflow } from './flow-types.ts';
3
+ import { StepNode } from './flow-utils.ts';
4
+ type UsesLike = {
5
+ uses?: {
6
+ app?: string;
7
+ action?: string;
8
+ };
9
+ };
10
+ /** A step is a foreach iff it is `@w6w/control` · `foreach` (P1). */
11
+ export declare function isForeachStep(step: UsesLike | undefined): boolean;
12
+ /**
13
+ * The step ids a control step owns — engine `controlOwnedIds`, arm for arm
14
+ * (foreach `body`, parallel `branches`, if `then`/`else`; strings at any array depth).
15
+ */
16
+ export declare function controlOwnedIds(step: FlowStep): string[];
17
+ /** Every step id owned by some control step (engine `ownedByControl`). */
18
+ export declare function ownedStepIds(steps: readonly FlowStep[]): Set<string>;
19
+ /** A canvas each-lane edge in the ONE shape load, bridge and `applyConnect` share (P3). */
20
+ export declare function eachLaneEdge(from: string, to: string, fromIsForeach: boolean, route?: string): Edge;
21
+ /**
22
+ * The body drawn from `foreachId`: every step reachable from its Each-item port
23
+ * over each-lane edges, as a topological order (Kahn over the body's own each-lane
24
+ * edges, ties broken by discovery order) so the Each-item target is first and every
25
+ * edge goes from an earlier id to a later one. A plain chain comes out exactly as
26
+ * the chain. Never includes `foreachId` or a repeat — so it terminates on a cycle
27
+ * (steps stuck in one are appended in discovery order). `claimed` (internal) stops
28
+ * the walk BEFORE a step an earlier foreach already owns.
29
+ */
30
+ export declare function loopBody(foreachId: string, nodes: readonly StepNode[], edges: readonly Edge[], claimed?: ReadonlySet<string>): string[];
31
+ /** The each-lane edges with both ends inside `body` (the For each → body[0] link is not one). */
32
+ export declare function bodyInternalEdges(body: readonly string[], edges: readonly Edge[]): Edge[];
33
+ /**
34
+ * A body is plain iff it is a chain the ordered `with.body` list already says: every
35
+ * body node has at most one outgoing each-lane edge and none carries a route. Plain
36
+ * bodies serialize as the list alone; anything else also emits its body edges.
37
+ */
38
+ export declare function isPlainBody(body: readonly string[], edges: readonly Edge[]): boolean;
39
+ /** The definition edges a canvas's non-plain loop bodies need (empty for plain chains). */
40
+ export declare function loopBodyDefinitionEdges(nodes: readonly StepNode[], edges: readonly Edge[]): FlowEdge[];
41
+ /** Every foreach's body, handled in `nodes` order so emitted bodies are disjoint (P6). */
42
+ export declare function loopBodies(nodes: readonly StepNode[], edges: readonly Edge[]): Map<string, string[]>;
43
+ /** `bodyStepId → foreachId` over the whole canvas. */
44
+ export declare function loopMembership(nodes: readonly StepNode[], edges: readonly Edge[]): Record<string, string>;
45
+ /**
46
+ * The edge set after deleting step `id` (D-A4). A body step with exactly one inbound
47
+ * and one outbound each-lane edge is bridged (`prev → next`, each lane, keeping the
48
+ * inbound edge's route when `prev` is an If/Router, `out-each` iff `prev` is the
49
+ * foreach); any other body step just loses its incident edges; deleting the foreach
50
+ * also removes the each-lane edges of its whole body (the body steps stay, as
51
+ * ordinary unconnected nodes). A non-body step is the plain incident-edge filter.
52
+ */
53
+ export declare function bridgeDeletedBodyStep(id: string, nodes: readonly StepNode[], edges: readonly Edge[]): Edge[];
54
+ /** `with.body` is the derived view of the each lane — drop it from a foreach (P1); other steps untouched. */
55
+ export declare function stripBody<T extends UsesLike & {
56
+ with?: Record<string, unknown>;
57
+ }>(step: T): T;
58
+ /** Why a connection would break the v1 linear-body rule (exhaustive; see {@link loopRefusal}). */
59
+ export type LoopRefusalReason = "error_lane_in_body" | "outside_inbound" | "loop_cycle" | "already_in_loop" | "control_in_body" | "body_branch" | "body_exit";
60
+ /**
61
+ * The loop rule for drawing `source → target` in `lane` (already promoted: a success
62
+ * connect from a body node arrives as `"each"`). First match wins. Pure; run on the
63
+ * INPUT edges, never a post-eviction set.
64
+ */
65
+ export declare function loopRefusal(source: string, target: string, lane: "success" | "error" | "each", nodes: StepNode[], edges: Edge[]): LoopRefusalReason | null;
66
+ export declare function loopRefusalMessage(reason: LoopRefusalReason): string;
67
+ /**
68
+ * Set or clear `data.loopOf` from {@link loopMembership}. Same array reference when
69
+ * nothing changed, and the same node object for every unchanged node.
70
+ */
71
+ export declare function stampLoopOf(nodes: StepNode[], edges: Edge[]): StepNode[];
72
+ /** The delete-confirm text. Byte-identical to the plain message unless a For each owns a body. */
73
+ export declare function deleteStepMessage(id: string, nodes: StepNode[], edges: Edge[]): string;
74
+ export type LoopInvalidReason = "body_not_literal" | "body_unknown_step" | "body_self" | "body_duplicate" | "body_claimed_twice" | "body_contains_control" | "body_step_has_edges" | "legacy_not_linear";
75
+ export type LoopInvalid = {
76
+ reason: LoopInvalidReason;
77
+ stepId?: string;
78
+ };
79
+ export type LoopResult = {
80
+ body: string[];
81
+ source: "body" | "legacy";
82
+ edges?: FlowEdge[];
83
+ } | {
84
+ invalid: LoopInvalid;
85
+ };
86
+ /** The edges the engine would run: declared, else the implicit chain over main-graph (non-owned) steps. */
87
+ export declare function effectiveEdges(wf: FlowWorkflow): FlowEdge[];
88
+ /** A saved For each → `body[0]` link ({@link FOREACH_EACH_ROUTE}); the canvas draws it from `with.body`. */
89
+ export declare const isEachLink: (e: FlowEdge) => boolean;
90
+ /**
91
+ * What each foreach's loop is, in a stored definition — the ONE place that knows
92
+ * the legacy-chain rule (D-A3). A foreach carrying a `body` key is judged on it;
93
+ * one without is a legacy loop iff its success-lane successors form a valid linear
94
+ * chain (every node a non-control step owned by no control, whose only inbound edge
95
+ * is from its predecessor and whose only outbound edge is a success edge onward).
96
+ */
97
+ export declare function definitionLoops(wf: FlowWorkflow): Record<string, LoopResult>;
98
+ export {};
@@ -0,0 +1,14 @@
1
+ import { FlowStep } from './flow-types.ts';
2
+ export declare const PAUSED_LABEL = "Paused \u2014 test runs stop before this node";
3
+ export declare const DISABLED_LABEL = "Disabled \u2014 skipped in every run";
4
+ export declare function isPausedStep(step: FlowStep): boolean;
5
+ export declare function isDisabledStep(step: FlowStep): boolean;
6
+ /** `step` with `ui.paused` set (on) or removed (off); an emptied `ui` is dropped. */
7
+ export declare function withPaused(step: FlowStep, on: boolean): FlowStep;
8
+ /** `step` with `disabled: true` (on) or without the key (off). */
9
+ export declare function withDisabled(step: FlowStep, on: boolean): FlowStep;
10
+ /**
11
+ * The `stopAt` of a run started from `startId`'s ▶: every paused node except the start itself
12
+ * (pressing ▶ on a paused node runs it — the pause stops runs that ARRIVE at it).
13
+ */
14
+ export declare function pausedStopAt(steps: readonly FlowStep[], startId: string): string[];
@@ -0,0 +1,69 @@
1
+ import { Edge } from '@xyflow/react';
2
+ import { LoopRefusalReason } from './flow-loop.ts';
3
+ import { CanvasLane, FlowStep, FlowWorkflowInput } from './flow-types.ts';
4
+ import { StepNode } from './flow-utils.ts';
5
+ import { StepProblem, StepProblems } from './step-problems.ts';
6
+ /**
7
+ * THE matcher: is `ref` a reference to foreach `foreachId`'s output, and what
8
+ * does it become inside the loop. `R.F.output` → `foreach.item`, `R.F.output.<rest>`
9
+ * → `foreach.item.<rest>`, `R.F.output[<rest>` → `foreach.item[<rest>`. Segment
10
+ * boundary only: `…outputs` and `for_each_10` never match.
11
+ */
12
+ export declare function loopOutputRef(ref: string, foreachId: string): string | null;
13
+ /**
14
+ * The loop problems of a stored definition, keyed by step id. Never throws and
15
+ * never mutates: any input `workflowToFlow` would reject gives `{}` (it runs in a
16
+ * host memo with no error boundary).
17
+ */
18
+ export declare function findLoopProblems(input: FlowWorkflowInput): StepProblems;
19
+ /** One editor-side parallel-write warning — the engine's `StateWriteWarning`, this code only. */
20
+ export interface StateParallelWrite {
21
+ code: "state_parallel_write";
22
+ nodes: string[];
23
+ path: string[];
24
+ }
25
+ /**
26
+ * The editor mirror of the engine's plan-time parallel-write warning (R16, plan
27
+ * §2.3; proven equal on `core/packages/expr/tests/fixtures/state-write-conflicts.json`).
28
+ * Two writers are concurrent iff they sit in different branches of one
29
+ * `@w6w/control` `parallel`; else never when they share an anchor (outermost
30
+ * owning control, else the step) or one anchor reaches the other; else iff one
31
+ * `fanOut: "parallel"` step has two distinct outgoing edges in the SAME lane
32
+ * whose targets reach the two anchors. Each overlapping pair of static write
33
+ * paths gives one warning on the shorter path; deduped, sorted by the engine's
34
+ * order (`code`, then `nodes`, then `path`). Never throws (→ `[]`), never mutates.
35
+ */
36
+ export declare function stateWriteConflicts(input: FlowWorkflowInput): StateParallelWrite[];
37
+ /**
38
+ * `stateWriteConflicts` as canvas problems: each warning lands on both of its
39
+ * steps, naming the other one (`with`). Never throws (→ `{}`), never mutates.
40
+ */
41
+ export declare function findStateWriteProblems(input: FlowWorkflowInput): StepProblems;
42
+ /**
43
+ * Apply a problem's fix to `step`, returning a new step. Re-scans the step it is
44
+ * given (never trusts the problem's `refs` snapshot); the SAME reference comes
45
+ * back for a problem with no fix or nothing left to fix. Never mutates.
46
+ */
47
+ export declare function applyStepProblemFix(step: FlowStep, problem: StepProblem): FlowStep;
48
+ /**
49
+ * The lane of a drag, read from the handle the pointer went down on. xyflow's
50
+ * `connectionState.fromHandle` resolves an id-less handle (the Done port) to the
51
+ * node's FIRST handle (`out-each`), so it cannot tell Done from Each item;
52
+ * `onConnectStart` reports the real `handleId` (null for Done). Falls back to
53
+ * `fromHandle.id` when no matching start was recorded.
54
+ */
55
+ export declare function dragLane(start: {
56
+ nodeId: string | null;
57
+ handleId: string | null;
58
+ handleType: string | null;
59
+ } | null, fromNodeId: string, fromHandle: {
60
+ id?: string | null;
61
+ type?: string;
62
+ } | null | undefined): CanvasLane;
63
+ /**
64
+ * The loop reason a source-handle drag dropped ON an existing node is refused for
65
+ * (`null` when the loop rule has nothing to say). `onConnectEnd` shows it as the
66
+ * node-anchored notice instead of opening the step builder. The lane is promoted the
67
+ * way `planConnect` does: a success drag from a body node extends the body.
68
+ */
69
+ export declare function connectDropRefusal(source: string, target: string, lane: "success" | "error" | "each", nodes: StepNode[], edges: Edge[]): LoopRefusalReason | null;
@@ -0,0 +1,20 @@
1
+ import { StateWriteConfig } from '@w6w/expr';
2
+ import { FlowStep } from './flow-types.ts';
3
+ export type StateMode = StateWriteConfig["mode"];
4
+ /** The five modes, in the order the control lists them. */
5
+ export declare const STATE_MODES: readonly StateMode[];
6
+ export declare const STATE_MODE_LABELS: Record<StateMode, string>;
7
+ type StepLike = Pick<FlowStep, "id" | "uses" | "state">;
8
+ /** What the step writes, the default applied (R14: Set data merges at root, every other step is Off). */
9
+ export declare function stateWriteFor(step: StepLike): StateWriteConfig;
10
+ /**
11
+ * The step with `config` as its state write. The default is the key's ABSENCE
12
+ * (Off on an ordinary step, Merge at root on Set data), so writing the default
13
+ * removes `state`. Never mutates; every other key passes through.
14
+ */
15
+ export declare function setStateWrite<S extends StepLike>(step: S, config: StateWriteConfig): S;
16
+ /** The config written when the author picks `mode`: named after the step so it is valid at once. */
17
+ export declare function seedStateWrite(step: Pick<FlowStep, "id">, mode: StateMode): StateWriteConfig;
18
+ /** The control shows for app steps and triggers; every control-flow step (If, For each, Parallel, Race) hides it. */
19
+ export declare function stateWriteVisible(step: Pick<FlowStep, "uses">): boolean;
20
+ export {};
@@ -1,3 +1,4 @@
1
+ import { StateWriteConfig } from '@w6w/expr';
1
2
  import { FlowCanvas } from './canvas-types.ts';
2
3
  import { EdgeRunState } from './run-visuals.ts';
3
4
  import { ActionParam } from './types.ts';
@@ -7,13 +8,16 @@ export interface FlowStep {
7
8
  app: string;
8
9
  action: string;
9
10
  connection?: string | null;
11
+ connectionRef?: string;
12
+ /** An app TRIGGER node (`action` is the trigger key): behaves like a built-in trigger on the canvas. */
13
+ trigger?: true;
10
14
  };
11
15
  /**
12
16
  * Declared connection-port cardinality for this step (see core
13
17
  * `rfcs/node-types.md` · Ports & cardinality). Omitted fields fall through field-wise to the catalog action, the internal def, then `{ in: 1, out: "many" }`.
14
- * A persisted value wins over the node's default — `in > 1` opts the step into
15
- * accepting multiple inbound edges (e.g. a flow-control aggregator joining
16
- * several upstream branches).
18
+ * A persisted value wins over the node's default. Only `in: 0` binds (no entry
19
+ * port); any `in >= 1` accepts any number of inbound edges (core node-types ·
20
+ * Alternate entries).
17
21
  */
18
22
  ports?: PortsDecl;
19
23
  with?: Record<string, unknown>;
@@ -37,6 +41,8 @@ export interface FlowStep {
37
41
  * runs them one after another.
38
42
  */
39
43
  fanOut?: "sequential" | "parallel";
44
+ /** Write to state (`Node.state`, core/rfcs/workflow.md). Absent = the default: Off, or Merge at root for `@w6w/data` set. */
45
+ state?: StateWriteConfig;
40
46
  /**
41
47
  * Authoring-time canvas coordinate for this step, in this editor's own
42
48
  * coordinate space (core `rfcs/workflow.md` · "Amendment — 2026-07-29:
@@ -60,10 +66,26 @@ export interface FlowStep {
60
66
  x: number;
61
67
  y: number;
62
68
  };
69
+ /**
70
+ * Disabled: skipped in EVERY run, published and triggered included — the node does not
71
+ * execute and its outgoing edges are skipped, so a node that depends only on it does not run
72
+ * either. An engine semantic (mirrors `Node.disabled` in `@w6w/workflow-types`). Written as
73
+ * `true` or absent, never `false`.
74
+ */
75
+ disabled?: true;
76
+ /**
77
+ * Editor-only metadata; the engine ignores it. `paused`: a test run started from a node's ▶
78
+ * stops BEFORE this node (it does not execute). Never affects a published or triggered run.
79
+ * Absent when empty, never `{}`.
80
+ */
81
+ ui?: {
82
+ paused?: true;
83
+ };
63
84
  }
64
85
  export interface FlowEdge {
65
86
  from: string;
66
87
  to: string;
88
+ route?: string;
67
89
  /**
68
90
  * Which outcome of the `from` step this edge carries (core
69
91
  * `rfcs/workflow.md` · "Amendment — 2026-07-29: failure-conditioned edges
@@ -111,7 +133,7 @@ export interface FlowEdge {
111
133
  * leaves the class in place, so `setEdgeWhen` assigns both keys explicitly
112
134
  * rather than spreading.
113
135
  */
114
- export declare function edgeVisuals(when: "success" | "error" | undefined, runState?: EdgeRunState): {
136
+ export declare function edgeVisuals(when: CanvasLane | undefined, runState?: EdgeRunState): {
115
137
  className?: string;
116
138
  label?: string;
117
139
  };
@@ -121,6 +143,25 @@ export declare function edgeVisuals(when: "success" | "error" | undefined, runSt
121
143
  * (D-P2), since no consumer outside this package needs it.
122
144
  */
123
145
  export declare const ERROR_SOURCE_HANDLE = "out-error";
146
+ /**
147
+ * The handle id of a For each's **Each item** exit port. The `each` lane derives
148
+ * `with.body` on save; its link to the first body step is also saved as a
149
+ * {@link FOREACH_EACH_ROUTE} edge so a reader sees which port it leaves.
150
+ */
151
+ export declare const EACH_SOURCE_HANDLE = "out-each";
152
+ /**
153
+ * `Edge.route` of a For each's **Done** port (the engine's `FOREACH_DONE_ROUTE`): the
154
+ * success edge that runs once, after every iteration. An unrouted For each success
155
+ * edge — the shape saved before the port had an id — is read as Done.
156
+ */
157
+ export declare const FOREACH_DONE_ROUTE = "done";
158
+ /** `Edge.route` of a For each's Each item link to `with.body[0]` (engine `FOREACH_EACH_ROUTE`). */
159
+ export declare const FOREACH_EACH_ROUTE = "each";
160
+ /**
161
+ * A lane on the CANVAS. `"each"` is canvas-only (a For each → body-step edge);
162
+ * the persisted {@link FlowEdge}`["when"]` deliberately stays `"success" | "error"`.
163
+ */
164
+ export type CanvasLane = "success" | "error" | "each";
124
165
  /**
125
166
  * The `sourceHandle` a minted/re-laned/loaded edge carries for a given lane —
126
167
  * the inverse of {@link laneForSourceHandle}. `undefined` for `"success"`/
@@ -129,7 +170,7 @@ export declare const ERROR_SOURCE_HANDLE = "out-error";
129
170
  * already-saved workflow's edges implicitly use. An empty string would not do
130
171
  * this — an empty string is a real, if degenerate, handle id.
131
172
  */
132
- export declare function sourceHandleForLane(when: "success" | "error" | undefined): string | undefined;
173
+ export declare function sourceHandleForLane(when: CanvasLane | undefined): string | undefined;
133
174
  /**
134
175
  * The lane a drag started from, read off the handle id React Flow reports.
135
176
  * **The one canonicalisation point** every minting/re-laning site calls
@@ -139,7 +180,7 @@ export declare function sourceHandleForLane(when: "success" | "error" | undefine
139
180
  * `"error"`; everything else — a near-miss id, `null`, `undefined` — is
140
181
  * `"success"`.
141
182
  */
142
- export declare function laneForSourceHandle(handleId: string | null | undefined): "success" | "error";
183
+ export declare function laneForSourceHandle(handleId: string | null | undefined): CanvasLane;
143
184
  export interface FlowWorkflow {
144
185
  manifestVersion: string;
145
186
  id: string;
@@ -152,7 +193,13 @@ export interface FlowWorkflow {
152
193
  required?: boolean;
153
194
  default?: unknown;
154
195
  }>;
196
+ /** Starting data (`Workflow.initialState`): a literal JSON object that seeds the run's state tree. */
197
+ initialState?: Record<string, unknown>;
155
198
  nodes: FlowStep[];
199
+ connections?: Record<string, {
200
+ app: string;
201
+ connection: string | null;
202
+ }>;
156
203
  edges?: FlowEdge[];
157
204
  canvas?: FlowCanvas;
158
205
  /**
@@ -259,7 +306,7 @@ export declare const DOCUMENT_APP = "@w6w/document";
259
306
  */
260
307
  export declare const MODEL_APP = "@w6w/model";
261
308
  /**
262
- * Compile a Handlebars `template` string against a resolved `values` object
309
+ * Render Handlebars text or JSON string values against a resolved `values` object
263
310
  * (core rfcs/node-types.md · F-3 amendment, "the `@w6w/template` host node").
264
311
  * Host node: `runInternalNode` short-circuits this id before the registry
265
312
  * loads a module — it needs no privileged host resource (no DB, no
@@ -267,6 +314,74 @@ export declare const MODEL_APP = "@w6w/model";
267
314
  * compiles to a JS function, the same risk shape `@w6w/script` has.
268
315
  */
269
316
  export declare const TEMPLATE_APP = "@w6w/template";
317
+ /** Host-run regular-expression operations. */
318
+ export declare const REGEX_APP = "@w6w/regex";
319
+ /**
320
+ * Read, write, count, list and query the entries of a named data store (core
321
+ * rfcs/node-types.md, the `@w6w/store` amendment). Host node, like
322
+ * {@link DOCUMENT_APP}: the run's own account and project are bound host-side, so
323
+ * there is no scope param — a store is addressed by NAME. The params mirror the
324
+ * `@w6w/store` internal-app manifest (pinned by `store-node-parity.test.ts`).
325
+ */
326
+ export declare const STORE_APP = "@w6w/store";
327
+ /** End the whole workflow successfully when its optional condition matches. */
328
+ export declare const STOP_APP = "@w6w/stop";
329
+ export declare const ROUTER_APP = "@w6w/router";
330
+ export declare const RACE_APP = "@w6w/race";
331
+ export declare const RACE_DESCRIPTION = "Continue once with the first arriving path\u2019s output. Later arrivals are ignored for this run. Other paths keep running.";
332
+ export interface RouterRoute {
333
+ id: string;
334
+ label?: string;
335
+ condition?: unknown;
336
+ default?: boolean;
337
+ }
338
+ export declare function routesForStep(step: FlowStep): RouterRoute[];
339
+ export declare const routeHandle: (id: string) => string;
340
+ export declare const routeFromHandle: (handle: string | null | undefined) => string | undefined;
341
+ /** Route ports the Router card's right edge holds at its natural height. */
342
+ export declare const ROUTER_PORTS_AT_BASE_HEIGHT = 3;
343
+ /** Card height (px, border-box) added per route port past the third. */
344
+ export declare const ROUTER_PORT_STEP = 16;
345
+ export interface RouterPort {
346
+ id: string;
347
+ /** Absent for an If's True port: the unnamed handle a route-less edge anchors to. */
348
+ handleId?: string;
349
+ label: string;
350
+ /** Handle position along its edge, as a CSS `top` (right-edge ports only). */
351
+ top?: string;
352
+ }
353
+ /**
354
+ * Where a Router's ports sit (human report, 2026-10-31: "they need to be
355
+ * attached to the router"). Condition routes go on the card's right edge,
356
+ * evenly spaced; the fallback route takes the bottom edge, the slot every
357
+ * other node gives its error port. Up to three right-edge ports fit the
358
+ * card's natural height; each one past that adds a fixed step, so five
359
+ * routes make a slightly taller card. Handle ids stay `route:<id>`, so
360
+ * saved edges keep resolving.
361
+ */
362
+ export interface PortLayout {
363
+ right: RouterPort[];
364
+ bottom?: RouterPort;
365
+ minHeight?: number;
366
+ }
367
+ export declare function routerPortLayout(routes: RouterRoute[]): PortLayout;
368
+ /**
369
+ * `Edge.route` of an If's false branch (the engine's `IF_FALSE_ROUTE`). The true
370
+ * branch is an edge with no route, which is what every If edge saved before the
371
+ * False port existed already is.
372
+ */
373
+ export declare const IF_FALSE_ROUTE = "false";
374
+ export declare function isIfStep(step: FlowStep): boolean;
375
+ /**
376
+ * An If's ports, laid out the way {@link routerPortLayout} lays out a Router's
377
+ * (human report, 2026-10-31: "should not have error, it should have: true port
378
+ * and false port"). True takes the right edge; False takes the bottom edge, the
379
+ * slot every other node gives its error port, so an If has no error port. True
380
+ * keeps the unnamed handle, so saved If edges stay on it; False is `route:false`.
381
+ */
382
+ export declare function ifPortLayout(): PortLayout & {
383
+ bottom: RouterPort;
384
+ };
270
385
  /** True when `app` is a reserved internal pseudo-app id (`@w6w/*`). */
271
386
  export declare function isInternalApp(app: string): boolean;
272
387
  /** True when a node is an engine-native flow-control node (can't run standalone). */
@@ -278,6 +393,16 @@ export declare function isControlApp(app: string): boolean;
278
393
  * and send the filled values as `{ input }` rather than running the raw config.
279
394
  */
280
395
  export declare function isTriggerApp(app: string): boolean;
396
+ /**
397
+ * Whether a step is a trigger NODE: one of the built-in triggers, or an app
398
+ * trigger node (`uses.trigger === true`). Canvas chrome, wiring and editing key
399
+ * on this; {@link isTriggerApp} stays for readers that mean the built-in
400
+ * `@w6w/*` triggers only (a callable workflow's input fields).
401
+ */
402
+ export declare function isTriggerStep(uses: {
403
+ app: string;
404
+ trigger?: true;
405
+ }): boolean;
281
406
  /** A palette entry for an internal node: its id, label, group, and config schema. */
282
407
  export interface InternalNodeDef {
283
408
  app: string;
@@ -289,6 +414,8 @@ export interface InternalNodeDef {
289
414
  * does. Shown wherever an app's name would be.
290
415
  */
291
416
  displayName: string;
417
+ /** Show one app entry in the palette and choose its action in configuration. */
418
+ groupActions?: boolean;
292
419
  group: "trigger" | "control" | "compute" | "request";
293
420
  /**
294
421
  * Inline SVG *inner* markup (paths / circles / polylines) for this primitive's
@@ -303,8 +430,9 @@ export interface InternalNodeDef {
303
430
  */
304
431
  hidden?: boolean;
305
432
  /**
306
- * Connection ports: how many inbound (entry) and outbound (exit) connections
307
- * this node accepts. A port is the ability to receive/emit a connection —
433
+ * Connection ports: whether this node has an entry (`in >= 1` takes any number
434
+ * of inbound edges; `in: 0` takes none) and how many outbound (exit) connections
435
+ * it allows. A port is the ability to receive/emit a connection —
308
436
  * rendered as a React Flow Handle. Defaults to one of each (`{ in: 1, out: 1 }`)
309
437
  * when omitted; a trigger overrides to `{ in: 0, out: 1 }` (nothing flows into
310
438
  * the entry node). Fixed for now — not user-editable.
@@ -1,5 +1,5 @@
1
1
  import { Edge, Node } from '@xyflow/react';
2
- import { FlowEdge, FlowStep, FlowWorkflow, FlowWorkflowInput } from './flow-types.ts';
2
+ import { CanvasLane, FlowStep, FlowWorkflow, FlowWorkflowInput } from './flow-types.ts';
3
3
  import { RunStatus, StepVisualState } from './run-visuals.ts';
4
4
  import { StepProblem } from './step-problems.ts';
5
5
  export interface StepNodeData extends Record<string, unknown> {
@@ -23,11 +23,18 @@ export interface StepNodeData extends Record<string, unknown> {
23
23
  */
24
24
  runStatus?: RunStatus;
25
25
  /**
26
- * A host-supplied problem with this step (e.g. a dangling connection).
26
+ * Host-supplied problems with this step (a dangling connection, loop problems).
27
27
  * Patched in by `WorkflowFlowEditor.tsx`'s `stepProblems` effect — never set
28
- * here and never read by {@link flowToWorkflow} (not persisted).
28
+ * here, absent (never `[]`) when there are none, and never read by {@link flowToWorkflow} (not persisted).
29
29
  */
30
- stepProblem?: StepProblem;
30
+ stepProblems?: StepProblem[];
31
+ /**
32
+ * The For each this step is a loop-body member of (canvas-only; derived on load
33
+ * and by the editor, never read by {@link flowToWorkflow}).
34
+ */
35
+ loopOf?: string;
36
+ /** This For each was converted from a legacy (no-`body`) loop on load. Canvas-only. */
37
+ legacyLoop?: boolean;
31
38
  }
32
39
  export type StepNode = Node<StepNodeData>;
33
40
  /**
@@ -46,13 +53,19 @@ export type StepNode = Node<StepNodeData>;
46
53
  * rewrite) in `flow-connect.ts`. A second hand-rolled copy of the template is
47
54
  * exactly how the rename path came to drop the qualifier.
48
55
  */
49
- export declare function flowEdgeId(from: string, to: string, when?: FlowEdge["when"]): string;
56
+ export declare function flowEdgeId(from: string, to: string, when?: CanvasLane, route?: string): string;
50
57
  /**
51
58
  * Why a step id may not be saved from the editor, or `null` when it may. The one
52
59
  * rule here is "no `.`" (a dotted id cannot be addressed by `steps.<id>.output`);
53
60
  * duplicates and the edge-time `->` / `:error` checks are NOT this function's job.
54
61
  */
55
62
  export declare function stepIdError(id: string): string | null;
63
+ /**
64
+ * {@link stepIdError} plus uniqueness: why `id` may not name a node of a graph whose other
65
+ * nodes are `taken`, or `null`. The rename in the step editor and the name in the add flow
66
+ * both validate through this.
67
+ */
68
+ export declare function stepNameError(id: string, taken: Iterable<string>): string | null;
56
69
  /**
57
70
  * The one wording for a refused edge id, shared by both routes into the collision —
58
71
  * drawing a wire whose minted id is taken (`applyConnect` / `connectConflict`) and
@@ -108,6 +121,8 @@ export declare function stepIdError(id: string): string | null;
108
121
  * vague-but-true message beats a confident false one.
109
122
  */
110
123
  export declare function idClashMessage(lead: string, id: string, involved: readonly (string | null | undefined)[]): string;
124
+ /** Read a definition in either shape into the canonical `nodes[]` form. */
125
+ export declare function normalizeInput(input: FlowWorkflowInput): FlowWorkflow;
111
126
  /**
112
127
  * Turn a Workflow into (nodes, edges) with an auto-layout when no positions are
113
128
  * known.
@@ -117,11 +132,8 @@ export declare function idClashMessage(lead: string, id: string, involved: reado
117
132
  * positioned graph mixes the two: positioned steps sit where they were left,
118
133
  * unpositioned ones take their computed slot.
119
134
  *
120
- * `computeLayers` runs over the **whole** graph either way, and the per-column
121
- * row counter advances for every step including positioned ones — so an
122
- * unpositioned step's computed coordinate is a pure function of the graph and
123
- * does not shift depending on which of its neighbours happen to carry a
124
- * coordinate.
135
+ * Layout runs over the whole graph, including positioned steps, so a fallback
136
+ * coordinate does not depend on which neighbours already have saved positions.
125
137
  */
126
138
  export declare function workflowToFlow(input: FlowWorkflowInput): {
127
139
  nodes: StepNode[];
@@ -135,12 +147,12 @@ export declare function workflowToFlow(input: FlowWorkflowInput): {
135
147
  * button acts on **what is currently on the canvas**, including steps added and
136
148
  * wires drawn since the last save.
137
149
  *
138
- * Same {@link computeLayers} and same {@link slotPosition} as {@link
150
+ * Same {@link computePositions} as {@link
139
151
  * workflowToFlow}, so a graph with no stored positions lays out byte-identically
140
152
  * either way (pinned by `flow-utils.test.ts` — "auto-layout agrees with first
141
153
  * open"). The **only** difference is that a stored coordinate does not win here:
142
154
  * overriding hand placement is precisely what the author asked for by clicking.
143
- * That is also why the caller confirms first — there is no undo in studio.
155
+ * The editor records this as one undoable layout change.
144
156
  *
145
157
  * Layering is keyed by **`node.id`**, not `node.data.step.id`: React Flow's
146
158
  * `edge.source`/`edge.target` name node ids, so keying by anything else would
@@ -287,5 +299,7 @@ export declare function stepToJson(step: StepLike): string;
287
299
  * writable: the params-only `JsonEditor`'s `onValidChange` feeds straight back
288
300
  * into `with`/`withValues`, same as it always has.
289
301
  */
290
- export declare function paramsToJson(step: Pick<StepLike, "with">): string;
302
+ export declare function paramsToJson(step: Pick<StepLike, "with"> & {
303
+ uses?: StepLike["uses"];
304
+ }): string;
291
305
  export {};