@theclearsky/react-blender-nodes 0.0.10 → 0.0.13

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,619 @@
1
+ # Changelog
2
+
3
+ ## 0.0.13 — 2026-09-05
4
+
5
+ > Versions 0.0.9 through 0.0.11 throw an import-time `ReferenceError` in both
6
+ > bundles and are deprecated on the registry; 0.0.12 was never published. This
7
+ > release is the first working build since 0.0.8.
8
+
9
+ ### Changed — ESM-only package (BREAKING for `require()` on Node < 20.19 / 22.12)
10
+
11
+ - The UMD/CommonJS bundle (`react-blender-nodes.umd.cjs`) is gone. `main`,
12
+ `module` and `exports["."]` all name the ES module; the `default` export
13
+ condition serves both `import` and `require`, so Node ≥ 20.19 / 22.12
14
+ `require()`s the package natively and every bundler resolves it as before.
15
+ Older Node fails fast with `ERR_REQUIRE_ESM`. No working consumer of the old
16
+ CJS path existed (0.0.9–0.0.11 threw on import in both formats, and 0.0.11's
17
+ manifest named a CJS file the build never emitted).
18
+ - The `/contract` subpath is now a second entry of the one `vite build` (ES
19
+ only, `exports["./contract"].default`) instead of a separate build; the
20
+ modules the two entries share are emitted as chunks. `check-dist-loads`
21
+ asserts on every build that the contract entry and its chunks import no React.
22
+
23
+ ### Added — public compiler surface
24
+
25
+ - `compile(state, functionImplementations, options?)` and
26
+ `serializeExecutionPlan(plan)` are exported from the package root, together
27
+ with the `SerializedExecutionPlan` / `SerializedExecutionStep` /
28
+ `SerializedLoopExecutionBlock` / `SerializedSwitchExecutionBlock` /
29
+ `SerializedGroupExecutionScope` types and `DEFAULT_MAX_LOOP_ITERATIONS`.
30
+ Downstream tooling can compile a graph and inspect the resulting
31
+ `ExecutionPlan` through public API. Call `compile` with three arguments: its
32
+ trailing `depth` parameter is `@internal` (the recursion counter the
33
+ sub-compilers thread) and must not be passed.
34
+ - `makeFunctionImplementationsWithAutoInfer` is exported from the root (the
35
+ README documented it, but it was only reachable from an internal path).
36
+ - `Zone` and `ZoneIndex` are exported, so the parameters of
37
+ `setCurrentZonesToState` / `setCurrentUserZonesToState` are nameable.
38
+
39
+ ### Changed — this library no longer depends on the codegen plugin
40
+
41
+ - The `file:` devDependency on `@theclearsky/react-blender-nodes-codegen`, the
42
+ CodegenStudio stories and the host-contract tests moved out of this repo to
43
+ the plugin, which owns its own Storybook. The dependency is strictly one-way
44
+ (plugin → this library); no AGPL code is bundled into this package or its
45
+ Storybook.
46
+
47
+ ### Changed — `Select` re-implemented without Radix (BREAKING)
48
+
49
+ - `SelectScrollUpButton`, `SelectScrollDownButton`, `ContextAwareOpenButton` and
50
+ `ReactFlowAwareOpenButton` (with their `Props` types) were removed, and
51
+ Radix-only props (`asChild`, `onOpenChange`, `side`, …) are no longer accepted
52
+ by the `Select` family.
53
+
54
+ ### Fixed — packaging
55
+
56
+ - `husky` and `lint-staged` moved from `dependencies` to `devDependencies`;
57
+ consumers no longer install them.
58
+ - The `/contract` bundle no longer carries a runtime `import "zod"` (zod was
59
+ only ever used there as types).
60
+ - `CHANGELOG.md` ships in the package.
61
+
62
+ ### Changed — ExecutionRecorder scope/loop methods (BREAKING for hand-built records)
63
+
64
+ - The recorder's ambient loop-nesting stack and scope stack are GONE, replaced
65
+ by explicit identity: every structure begin/complete call takes an
66
+ `ownerInstancePath` (the owning group instance path, `[]` at root), nested
67
+ loops declare their parent via an explicit `StructureParentContext`, and group
68
+ scopes are handled through single-use branded `RecorderScopeToken`s. This
69
+ fixes cross-contaminated group `innerRecord`s and vanishing sibling
70
+ `LoopRecord`s under concurrent execution.
71
+
72
+ - **BREAKING — structure-record map keys changed shape.** `loopRecords`,
73
+ `switchRecords`, `groupRecords`, `iterations[].nestedLoopRecords` and the
74
+ scoped `innerRecord` copies are now keyed by the structure's full path,
75
+ serialized as a JSON array:
76
+
77
+ ```
78
+ root loop L → ["L"]
79
+ loop L inside instance g2 → ["g2","L"]
80
+ loop L inside g2 → subgroup s1 → ["g2","s1","L"] (any depth)
81
+ group instance g2 itself → ["g2"]
82
+ ```
83
+
84
+ A structure id is a NODE id, and every instance of a node group shares its
85
+ template's node ids — so a bare-id key made two instances of one group
86
+ collide. One format now applies at every depth, in every map, top-level and
87
+ scoped alike.
88
+
89
+ Do not build these by hand and never parse one:
90
+
91
+ ```ts
92
+ import {
93
+ structureRecordKey,
94
+ resolveStructureRecord,
95
+ } from '@theclearsky/react-blender-nodes';
96
+
97
+ // before
98
+ record.loopRecords.get(step.loopStructureId);
99
+ // after (preferred — also finds salvage duplicates and pre-v3 exports)
100
+ resolveStructureRecord(
101
+ record.loopRecords,
102
+ step.loopStructureId,
103
+ step.instancePath,
104
+ )?.record;
105
+ ```
106
+
107
+ Recordings exported before this change still import and still resolve, and
108
+ import validation now reports their key format once per map as a warning.
109
+
110
+ - **`LoopRecord`, `SwitchRecord` and `GroupRecord` gain a required
111
+ `ownerInstancePath: readonly string[]`**, so identity is readable structurally
112
+ rather than by parsing a key, and survives export/import. For a group record
113
+ it is the PARENT path (matching the group's own wrapper step); append
114
+ `groupNodeId` for that instance's own path.
115
+
116
+ - **BREAKING — `ownerInstancePath` is now REQUIRED** on `beginLoopStructure` /
117
+ `beginLoopIteration` / `completeLoopIteration` / `completeLoopStructure` /
118
+ `beginSwitchStructure` / `completeSwitchStructure` / `completeGroup`. It was
119
+ briefly optional-with-a-default; omitting it silently filed the record at root
120
+ scope, which is exactly the mis-attribution this release exists to remove, so
121
+ omission is now a compile error.
122
+
123
+ - **Migration for run-target authors who hand-build records** (the audience
124
+ documented in `docs/runner/runTargetsDoc.md`):
125
+
126
+ ```ts
127
+ // before
128
+ recorder.beginScope();
129
+ const inner = recorder.endScope('completed', values);
130
+ // after
131
+ const token = recorder.beginScope(ownerInstancePath); // [] at root
132
+ const inner = recorder.endScope(token, 'completed', values);
133
+ ```
134
+
135
+ - New: `finalize()` now runs a full-sweep salvage backstop — API misuse
136
+ (unclosed structures/scopes, a step begun but never completed) is promoted
137
+ into the record (never overwriting healthy data; a colliding salvage is filed
138
+ under its identity plus a numeric ordinal) and reported via the new
139
+ `onRecorderWarning` callback (`new ExecutionRecorder({ onRecorderWarning })`,
140
+ threaded through the executor's `execute(..., { onRecorderWarning })` option,
141
+ `useNodeRunner`'s `options`, and a new `<FullGraph onRecorderWarning={…} />`
142
+ prop); without a callback it dev-`console.warn`s. Warnings are bookkeeping
143
+ diagnostics — they never enter `record.errors`, and a healthy run emits none.
144
+ - New exports from the package root: `structureRecordKey`,
145
+ `resolveStructureRecord`, `recorderWarningKinds` (values) and
146
+ `RecorderScopeToken`, `StructureParentContext`, `RecorderWarning`,
147
+ `RecorderWarningKind`, `ExecutionRecorderOptions` (types).
148
+
149
+ ### Fixed — the package now actually loads (import-time crash + broken CJS entry)
150
+
151
+ - **`require('@theclearsky/react-blender-nodes')` resolves again** on Node ≥
152
+ 20.19 / 22.12. 0.0.11's manifest declared `main` / `exports["."].require` as
153
+ `dist/react-blender-nodes.umd.cjs`, but the build emitted
154
+ `react-blender-nodes.umd.js` — every CJS consumer got `ERR_MODULE_NOT_FOUND`.
155
+ Rather than rename the file, the CJS bundle was dropped altogether (see
156
+ "ESM-only package" above): `exports["."].default` points every resolver,
157
+ `import` and `require` alike, at the one ES module, and `check-dist-loads` now
158
+ fails the build if a manifest target does not exist.
159
+ - **Both bundles no longer throw at import time.** `ConnectionMiniMap` imported
160
+ the ROOT components barrel from inside `src/components`, creating a module
161
+ cycle that surfaced as
162
+ `ReferenceError: Cannot access '<symbol>' before initialization` when
163
+ evaluating either dist bundle. The import is now a deep sibling import, and
164
+ lint rules make the pattern unwritable across all of `src/**`: a
165
+ `no-restricted-imports` path + regex pair (covering `@/components`,
166
+ `@/components/index` and the root `@/index` barrel, type-only imports still
167
+ allowed) plus two `no-restricted-syntax` selectors for the forms
168
+ `no-restricted-imports` cannot see — dynamic `import('@/components')` and
169
+ `export … from '@/components'`.
170
+ - **New build gate: `scripts/check-dist-loads.ts`.** Every build now verifies
171
+ the manifest's file targets exist, that `main`/`module` cohere with `exports`,
172
+ and EXECUTES all four entry bundles (root + `/contract`, CJS + ESM) in
173
+ isolated child processes with export sentinels — so both failure classes above
174
+ can never ship silently again.
175
+ - For script-tag/CDN consumers loading `dist/` files by path: there is no UMD
176
+ file any more (see "ESM-only package" above) — load
177
+ `dist/react-blender-nodes.es.js` as a module. No working consumer of the old
178
+ path existed — both previous bundles threw at import time (IN-41).
179
+
180
+ ### Changed — codegen extracted to a separate plugin (BREAKING)
181
+
182
+ - The codegen subsystem moved OUT of this library into a new standalone package,
183
+ `@theclearsky/react-blender-nodes-codegen`. This library no longer exports the
184
+ codegen public API — `emitJs`, `makeCodegenRunTarget`, `codegenJsRunTarget`,
185
+ `codegenTsRunTarget`, `CodegenRunTargetOptions`, `EmitJsOptions`,
186
+ `CodegenMetadata`, `NodeCodegenMetadata`, or `CodegenEmitContext`.
187
+ (`emitGraph`, previously an internal entry point the studio deep-imported, is
188
+ now a public export of the plugin.) Install the plugin and pass its
189
+ `codegenJsRunTarget` / `codegenTsRunTarget` to `<FullGraph runTargets={…} />`
190
+ — see `docs/runner/runTargetsDoc.md`.
191
+ - `typescript` and `prettier` are no longer runtime `dependencies` (moved to
192
+ `devDependencies`); they were used only by codegen, so consumers no longer
193
+ pull the ~8 MB compiler.
194
+ - Added a SECOND, React-free entry point,
195
+ `@theclearsky/react-blender-nodes/contract`, re-exporting the runner IR /
196
+ graph-state types plus the pure executor helpers (`getDataHandleIds`,
197
+ `findConditionInputId`, `qualifiedId`, `flattenInputs`, `readInput`,
198
+ `downloadTextArtifact`). The codegen plugin consumes this subpath (peer
199
+ dependency), so it carries no React at runtime.
200
+ - NOTE (net state): the other unreleased codegen entries below — "codegen v2"
201
+ and "self-contained codegen artifact" — describe that subsystem's development
202
+ earlier on this branch. It now lives in the plugin; those APIs
203
+ (`emitJs`/`emitGraph`/`makeCodegenRunTarget`/…) are no longer exported here.
204
+
205
+ ### Added — first-class input defaults (`TypeOfInput.defaultValue`)
206
+
207
+ - Node-type input definitions may now declare a `defaultValue`
208
+ (`number`/`string`/`boolean`); `constructNodeOfType` seeds it onto a fresh
209
+ node's input handle `value` at construction (when the runtime type matches),
210
+ so a new node's inline inputs are populated immediately without the consumer
211
+ dispatching `UPDATE_INPUT_VALUE` after every add. The SDF Shape Studio
212
+ exemplar now uses this and drops its ~90-line post-add seeding scan.
213
+
214
+ ### Fixed — custom names inside loop/switch bodies
215
+
216
+ - The loop/switch sub-compilers omitted `customName` from body/branch steps (the
217
+ top-level path sets it), so a custom-named node inside a loop body or switch
218
+ branch lost its name in records, errors, and codegen comments. Both sites now
219
+ carry it.
220
+
221
+ ### Fixed — pre-existing pipeline landmines (surfaced by a multi-agent review)
222
+
223
+ - **Multi-edge delete no longer resurrects edges.** `UPDATE_EDGES_BY_REACT_FLOW`
224
+ built every removal step against the same original snapshot, and `applyPlan`
225
+ applied them by per-step overwrite — so a ReactFlow batch of ≥2 `remove`
226
+ changes (multi-select-delete, or deleting a node with several edges) removed
227
+ only the last and resurrected the rest (leaving dangling edges). The validator
228
+ now accumulates the view across removals.
229
+ - **Zone membership recompute is now scope-correct.** After an edge change,
230
+ membership was gated on ROOT `draft.zones` but read/wrote the current scope,
231
+ so a loop/switch inside an open group never recomputed `zone.nodeIds` (stale
232
+ frames + wrong pre/post-stop and true/false attribution). Both apply sites now
233
+ gate on the scoped view's zones.
234
+ - **Running while a node group is open now compiles AND executes the subtree.**
235
+ The loop/switch structure resolvers and `buildNodeInfoMap` read root
236
+ `state.nodes`/`state.edges` directly, so a subtree run silently dropped every
237
+ loop/switch from the plan and then failed per-node with "node not found". The
238
+ compiler now hands the sub-compilers a scope-projected state, and the executor
239
+ reads the current scope.
240
+ - **`applyPlan` exceptions are now observable.** A throw during apply used to
241
+ unwind through `produce`/`dispatch` with no event and no toast; the store now
242
+ catches it, keeps state unchanged, and emits an `action:rejected` event with a
243
+ new `APPLY_EXCEPTION` code.
244
+ - **Runner mode-switch guard.** Switching a `<FullGraph>` between controlled and
245
+ uncontrolled `executionRecord` at runtime (e.g. `record ?? undefined`) leaves
246
+ the runner's derived state incoherent; it now logs a dev `console.error`
247
+ (React-controlled-input style). Loading an external record mid-run now aborts
248
+ the in-flight execution first (it previously reported "completed" while still
249
+ running, then silently overwrote the loaded record).
250
+ - **Complex-type sameness unified.** The complex-compatibility check and the
251
+ conversion check answered "are these the same type?" differently, so merely
252
+ supplying a conversion table (even `{}`) flipped an aliased complex pair (two
253
+ ids sharing one schema) from valid to `CONVERSION_NOT_ALLOWED`. Both now share
254
+ one `areComplexTypesSame` rule.
255
+ - **Imported group subtrees rehydrate their zones.** `REPLACE_STATE` rebuilt
256
+ derived zones for the root only, so an imported group's inner loops/switches
257
+ had no zones (no frames; zone-guarded validation fell back to BFS). Subtree
258
+ zones are now rehydrated per group.
259
+
260
+ ### Removed
261
+
262
+ - Dropped the unused `lodash` runtime dependency (and `@types/lodash`) — the
263
+ last import was replaced by `cloneDeepPreservingNonPlainObjects`. It remains
264
+ only as a transitive dev-tooling dependency; consumers no longer install it.
265
+
266
+ ### Fixed — complex data types × loops/switches/groups (edge inference)
267
+
268
+ - **Connecting a complex-typed output into a loop/switch infer slot or a group
269
+ boundary no longer dies silently.** ADD_EDGE's apply step deep-copied the
270
+ inference node data with `structuredClone`, which throws `DataCloneError` on
271
+ the first function it meets — and a zod `complexSchema`'s internals are
272
+ functions. The dispatch died mid-`produce` (no toast: an exception is not a
273
+ validation rejection), so the edge simply never landed. The clone is now
274
+ `cloneDeepPreservingNonPlainObjects`: plain data is deep-copied (Immer gets
275
+ its mutable subtree), while functions/class instances — schemas included —
276
+ pass through **by reference**.
277
+ - **Inference no longer mints schema copies.** The same pipeline's update values
278
+ were cloned with lodash `cloneDeep`, which rebuilds class instances — an
279
+ equivalent-but-_different_ schema object on every materialized handle,
280
+ silently breaking the reference-identity comparison edge validation relies on
281
+ ("data types are immutable singletons"). Same fix, same helper; handle schemas
282
+ now stay `===` to their data type's singleton across inference.
283
+ - **Edge validation's complex-type fallback no longer treats two ABSENT schemas
284
+ as proof of sameness.** Export strips `complexSchema` from handle
285
+ `dataTypeObject`s, so a state loaded via a raw `REPLACE_STATE` had
286
+ `undefined === undefined` on every complex handle pair — cross-type wires
287
+ between imported nodes validated. Ids remain the primary key; a schema
288
+ reference only counts when it exists.
289
+
290
+ ### Fixed — uncontrolled runner records (`<FullGraph>` without record props)
291
+
292
+ - Omitting the `executionRecord` prop made the runner **controlled with a noop
293
+ sink**: runs completed but every record evaporated (timeline forever "No
294
+ execution record", previews never fed). `FullGraph` now preserves the absent
295
+ prop as `undefined` through `RecordContext`, selecting `useNodeRunner`'s real
296
+ UNCONTROLLED mode — Run populates the timeline/previews with no parent state.
297
+ The prop is tri-state and documented: omit = uncontrolled, `null` =
298
+ controlled-empty, record = controlled-loaded.
299
+ - **Type change (barrel-exported):** `RecordContextValue.executionRecord`
300
+ widened `ExecutionRecord | null` → `ExecutionRecord | null | undefined`.
301
+ Consumers reading it off `useRecordContext()` must now handle `undefined`.
302
+ - Controlling `executionRecord` WITHOUT wiring `onExecutionRecordChange` now
303
+ logs a dev-only `console.error` (React-controlled-input style) — that
304
+ configuration is still a silent record sink, and the warning names the fix.
305
+
306
+ ### Fixed — `SliderNumberInput` external value changes
307
+
308
+ - The slider's internal chaining state initialized from the `value` prop at
309
+ MOUNT only, so after a programmatic `UPDATE_INPUT_VALUE` (seeded defaults,
310
+ undo/redo) the first `‹`/`›` click chained off the stale mount-time value —
311
+ `0.4` visibly became `0.04` instead of `0.44`. External controlled-value
312
+ changes now re-sync the internal state (internal changes are unaffected — they
313
+ already sync before `onChange` fires).
314
+
315
+ ### Fixed — `enableDebugMode` node id badge
316
+
317
+ - The debug id in the node header rendered flush against the title; it now has
318
+ its own left margin (visible only when `enableDebugMode` is on).
319
+
320
+ ### Added — SDF Shape Studio (Storybook, `Advanced Graph Examples`)
321
+
322
+ - A new top-level Storybook section demonstrating closure-valued complex data
323
+ types + the `nodePreviews` feature at full stretch: **31 SDF node types**
324
+ (plus the standard structural set — groups, loops, and switches work inside
325
+ the studio) build 2D vector art from signed distance fields — shapes (Circle,
326
+ Box, Star, Rounded Box, Hexagon, Triangle, Vesica, Moon, Pie, Heart),
327
+ boolean/smooth operators (Union, Subtract, Intersect, Xor, Smooth ×3), shape
328
+ modifiers (Round, Onion), domain transforms (Translate, Rotate, Scale, Mirror
329
+ X/Y, artifact-free grid Repeat, two-sector Radial Repeat), **threshold masks**
330
+ (Less Than / Greater Than → binary black/white images), **measurement nodes**
331
+ (Measure Mask, Measure Brightness) that turn images into plain numbers (pixel
332
+ counts / ratios over a fixed 220² grid) which can drive any downstream
333
+ parameter, and an output **Render** sink. Every formula is an IQ-exact port
334
+ pinned by known-point unit tests (`src/advancedGraphExamples/sdfLib.ts`);
335
+ definitions live in `src/advancedGraphExamples/sdfStudioDefinitions.ts` so
336
+ tests consume the real tables.
337
+ - Previews render each node's RECORDED value at the CURRENT timeline position
338
+ (strictly `atStep` — scrubbing before a node's first execution shows "Not
339
+ reached at this step", never a stale final value): the IQ orange/blue debug
340
+ field on compute nodes, strict black/white on masks, formatted numbers on
341
+ measurement nodes, and an anti-aliased cosine-palette fill (+glow) on Render —
342
+ all Canvas2D (no WebGL context pressure), values read by reference off the
343
+ execution record.
344
+ - **Rendering is manual by design in the Playground**: press Run in the runner
345
+ panel (no auto-run on edits; params seed their defaults on add, batched so one
346
+ undo removes them). The `Showcase` story pre-loads a UI-authored fixture
347
+ (`.storybook/static/graphStates/sdf-shape-studio-state.json` — a six-heart
348
+ radial flower smooth-unioned onto a circle, split two ways: a glowing palette
349
+ Render, and a Less-Than mask whose Measure Mask reports pixel coverage)
350
+ through the REAL import pipeline (schemas rehydrated), then runs it ONCE so
351
+ the story opens already rendered. Story chrome adds theme (dark/light) and
352
+ frame (full/390px) toggles.
353
+ - New Playwright project `advancedGraphExamples` (4 tests: seeding +
354
+ slider-sync + no-auto-run pins, render-on-Run, binary-mask + plausible-ratio
355
+ oracle, Showcase preload/auto-run + Reset→Run cycle).
356
+
357
+ ### Added — group execution-path / instance tracking
358
+
359
+ - **Every execution step now records an `instancePath`** — the chain of
360
+ group-instance node ids down to the scope that executed it (absent at root).
361
+ Unlike `groupNodeId` (a shared subtree TEMPLATE id below depth 1), the chain
362
+ uniquely identifies which instance path produced a step; it mirrors the
363
+ ValueStore's scoped-prefix chain and round-trips through recording
364
+ export/import unchanged. The thread-through covers the FULL executor surface —
365
+ including loops and switches nested inside groups, which previously recorded
366
+ their steps with no group attribution at all.
367
+ - **Instance-aware previews and status borders.** Standing inside a group
368
+ instance (opened via the node's open button), per-node previews and runner
369
+ visual states now derive only from THAT instance's steps — two instances of
370
+ one group type show their own values on the shared template node instead of
371
+ last-instance-wins. Template opens (top-left selector) keep the aggregate
372
+ view. Recordings exported before this feature lack paths and filter to empty
373
+ inside instances — re-run to refresh.
374
+ - **Follow into groups.** A timeline-toolbar toggle (default ON, session-only)
375
+ makes scrubbing, stepping, and autoplay open/close group scopes so the canvas
376
+ follows the scrub head into the exact instance that executed, then centers the
377
+ node. `OPEN_NODE_GROUP` / `CLOSE_NODE_GROUP` are now NON-undoable (view
378
+ concerns, like `SET_VIEWPORT`) so navigation never pollutes Ctrl+Z.
379
+ - **Step over / step out.** Timeline replay buttons jump over a group's interior
380
+ (or out of the enclosing scope) using instancePath depth — plus a live
381
+ `stepOver()` on `useNodeRunner` (and an optional Step-over transport button)
382
+ that drains step-by-step execution through a group's interior with pause/stop
383
+ honored.
384
+
385
+ ### Changed — runner stories consolidated (Storybook)
386
+
387
+ - The runner-family stories collapsed 9 → 3: `EmptyRunnerPlayground`
388
+ (unchanged), `WithRunner` (new story-chrome control panel: preview-mode ×
389
+ theme × frame — replaces WithNodePreviews / NodePreviewsStepThrough /
390
+ NodePreviewsErrorHandling / NodePreviewsWithoutRunner / NodePreviewsThemed /
391
+ WithRunnerNarrow), and `RunnerFixtureDemos` (fixture selector over real
392
+ UI-exported graphs, including a two-instance group fixture that pins the
393
+ instance-tracking behavior). Fixture conventions documented in
394
+ `.storybook/static/graphStates/README.md`.
395
+
396
+ ### Added — ordered fan-in connections
397
+
398
+ - **Multi-connection input handles now expose a user-orderable connection
399
+ sequence.** When several wires feed one input handle (fan-in), a count badge
400
+ on the input row opens a popover to drag the connections into the desired
401
+ order. The order is persisted per edge as `edge.data.order` — the connection's
402
+ contiguous `0..n-1` rank within its target handle's fan-in group — via the new
403
+ `REORDER_INPUT_CONNECTIONS` action. The compiler fixes the order in one place,
404
+ for the executor's `connections[]` AND every codegen target, so the on-screen
405
+ order equals the runtime and generated-code order. Additive and
406
+ back-compatible: edges never reordered carry no `order` and fall back to the
407
+ `state.edges` array order. Import repair gained an opt-in
408
+ `normalizeConnectionOrder` strategy that repacks out-of-contract imported
409
+ orders back to `0..n-1`. The compiler fixes the order via an explicit
410
+ `edgesArrayIndex` tiebreak, additively surfaced on the `json-ir` run target's
411
+ `inputResolutionMap` entries.
412
+
413
+ ### Added — self-contained codegen artifact (`emitImplementations: 'source'`)
414
+
415
+ - **The `codegen-js` / `codegen-ts` targets can now bake your node
416
+ implementations into the emitted module**, so the generated `runGraph()` runs
417
+ standalone with no `functionImplementations` argument. Opt in via
418
+ `makeCodegenRunTarget({ emitImplementations: 'source', knownFunctions })` —
419
+ one object whose keys matching a node-type id are that type's impl and whose
420
+ other keys are helpers referenced by name. Codegen analyses each function (via
421
+ `Function.prototype.toString()`), emits the covered ones plus the `readInput`
422
+ intrinsic as real `const` definitions, calls them by name, and drops the
423
+ `functionImplementations` parameter when EVERY node is covered. A REGISTERED
424
+ node type it cannot prove behaves identically to the in-process executor — one
425
+ reading executor-only state (`context.state`/`loopIteration`/`groupDepth`), a
426
+ non-`.value` connection field, handle metadata, reading `this`, a generator,
427
+ or referencing an unresolvable (e.g. bundler-namespaced) identifier —
428
+ gracefully keeps its threaded call and emits a `// warning:` naming the
429
+ reason. (Only node types you list in `knownFunctions` are analysed; a
430
+ used-but-unregistered type simply stays threaded with no warning and needs its
431
+ impl at run time.) The artifact is always runnable. The value-API surface
432
+ guard is inter-procedural (passing `inputs` to a registered helper checks that
433
+ helper too), so the common `firstVal(inputs, name)` value-extraction pattern
434
+ is covered. Additive and back-compatible: with the option off, codegen output
435
+ is byte-for-byte unchanged. New `CodegenRunTargetOptions`:
436
+ `emitImplementations`, `knownFunctions`, `additionalGlobals`. See
437
+ `docs/runner/runTargetsDoc.md`.
438
+
439
+ ### Changed — root Graph I/O inference parity (behavior change)
440
+
441
+ - **Connecting a wire to a root Graph Input/Output now behaves like a group
442
+ boundary by default:** the connected handle concretizes its type, **renames to
443
+ the connected source's name**, and grows a fresh blank infer spare. Previously
444
+ root boundary handles did NOT rename on connect. Because a root handle's name
445
+ is its `runGraph` parameter and its `rootInputs` key, this is a behavior
446
+ change for existing consumers — a user wiring the graph can move a
447
+ `rootInputs` key on the next connect.
448
+ - **Migration:** to keep stable root I/O names, set
449
+ `allowRootIORename={false}` on `<FullGraph>` (and usually
450
+ `allowRootIOStructureEdit={false}` to also freeze the handle count).
451
+ Alternatively, key `rootInputs` by the stable handle **id** instead of the
452
+ name — `seedRootInputs` now honors id keys as a fallback, so id-keyed inputs
453
+ are immune to renames. (`record.rootOutputs` stays name-keyed, byte-for-byte
454
+ matching codegen's `runGraph` return.)
455
+ - New optional `<FullGraph>` props: `allowRootIORename?: boolean` (default
456
+ `true`) and `allowRootIOStructureEdit?: boolean` (default `true`). Setting
457
+ them `false` opts out of root rename-on-connect and root add/grow/delete
458
+ respectively, gating BOTH the inference path and the Graph I/O editor.
459
+
460
+ ### Breaking — codegen v2
461
+
462
+ - Codegen metadata moved off the core types. `TypeOfNode.codegen` and
463
+ `DataType.codegenTypes` are removed; per-node `emit` and the per-data-type
464
+ TypeScript type are now supplied to the codegen factory
465
+ (`makeCodegenRunTarget`) / `emitJs` via the `CodegenMetadata` registry
466
+ (`nodeTypeMetadata`, `dataTypeToTsType`). This decouples the editor core from
467
+ codegen. No migration shim.
468
+ - The `initialInputValues` runtime-override parameter is removed from the
469
+ emitted `runGraph` signature (and from the `emitJs` / codegen-target API).
470
+ Unconnected input handles bake their current state value INLINE instead.
471
+
472
+ ### Added — codegen v2 (clean `runGraph`)
473
+
474
+ - The built-in **codegen run targets now route through `emitGraph` v2.**
475
+ `codegenJsRunTarget` / `codegenTsRunTarget` (and `makeCodegenRunTarget`) call
476
+ `emitGraph(plan, state, options)` (async): the proven string emit, then opt-in
477
+ `ts.transform` optimization passes over the generated TypeScript AST, then
478
+ Prettier. `typescript` is now a runtime dependency (externalized from the
479
+ bundle, lazy-`import()`ed only on codegen use), used as the AST substrate for
480
+ the passes. With no opt-in options the export is a faithful, threaded
481
+ `runGraph`; the optimization passes are opt-in (see below).
482
+ - **Auto-emit** (`analyzeImplementations: true` + `impls`): a self-contained
483
+ value-API implementation that reads inputs through the now-exported
484
+ `readInput` intrinsic and returns `new Map([[name, pureExpr]])` is emitted
485
+ INLINE (no manual `emit` hook, no threading). Recognition is AST-based and
486
+ robust to Vite/esbuild transpilation; author `emit` hooks take precedence;
487
+ anything not provably self-contained falls back to threading.
488
+ - **Dead-code elimination** (`optimize.deadCode`, needs
489
+ `assumePureImplementations`): drops bindings/blocks no returned value depends
490
+ on (including dead loop/switch/group blocks), then cleans the signature —
491
+ removes unreferenced parameters and the `async` keyword when no `await`
492
+ survives.
493
+ - **`readInput(inputs, name)` and `emitJs` are now exported** from the public
494
+ run-targets barrel (`src/utils/nodeRunner/runTargets/index.ts`). `readInput`
495
+ is the recommended way for node implementations to read an input (returns the
496
+ value array; index `[0]` for the first) and is the auto-emit marker; `emitJs`
497
+ is the low-level codegen string entry point.
498
+ - **Loops** now emit ONE named variable per loop variable (`let loopValue = …`)
499
+ declared at function scope, instead of a `currentValues[i]` array (Masterplan
500
+ §12).
501
+ - The CodegenStudio stories gain an **`optimize`** toggle (DCE + auto-emit), and
502
+ a new `CodegenStudioWithGraphIO` story demonstrates the clean
503
+ `runGraph(a, b)`.
504
+
505
+ ### Added
506
+
507
+ - Root Graph I/O editing — build a graph's `runGraph(...)` signature in the
508
+ studio. At root scope the canvas context menu gains single-instance **"Add
509
+ Graph Input"** / **"Add Graph Output"** entries, the placed boundary nodes
510
+ display as "Graph Input" / "Graph Output" and carry an edit Pencil, and a new
511
+ `GraphIOEditDrawer` (reusing `InputOutputReorderSection` with its new optional
512
+ `allowLeafRename` / `onAddItem` props) adds, renames, reorders, and deletes
513
+ their handles by name. A new instance-scoped `UPDATE_GRAPH_IO_HANDLES` action
514
+ cascades the root edges of deleted handles and mints new `groupInfer` handles
515
+ that concretize on connect. The compiler/executor seed these as `rootInputs` /
516
+ `rootOutputs`, and the JS codegen emits a clean `function runGraph(a, b)`
517
+ whose parameters are the Graph Input handle names and whose return is keyed by
518
+ the Graph Output handle names. See `docs/ui/editorsDoc.md`.
519
+ - `FullGraph` gains an optional `rootInputs?: Record<string, unknown>` prop that
520
+ seeds the root Graph Input handle values for an in-editor run. Both instant
521
+ and step-by-step execution now seed `rootInputs` and collect `rootOutputs`, so
522
+ the in-editor run and the emitted `runGraph(...)` are value-equivalent.
523
+ - Optional graph theme system: `GraphThemeProvider`, `useGraphTheme`, the typed
524
+ `GraphTheme` per-component/per-slot className map, `blenderDark` / `light`
525
+ presets, and the `mergeGraphThemes` / `resolveGraphTheme` utilities. Without a
526
+ provider the graph keeps its existing default look.
527
+ - Pluggable run targets: register named execution strategies via `FullGraph`'s
528
+ additive `runTargets` / `defaultRunTargetId` props and pick one from the
529
+ runner's split Run button. Two modes — `execute` (feeds the timeline like
530
+ today) and `artifact` (downloads a file/string). Ships three built-ins: the
531
+ in-process executor (default), `json-ir` (export the compiled plan as JSON),
532
+ and `codegen-js` (emit a standalone, dependency-free, human-readable
533
+ JavaScript `runGraph`). The run-targets module — `RunTarget`,
534
+ `makeRunTargetWithAutoInfer`, the `inProcessRunTarget` / `jsonIrRunTarget` /
535
+ `codegenJsRunTarget` values, `downloadTextArtifact`, and the runner IR/record
536
+ types — is now part of the public API. Omitting `runTargets` keeps the
537
+ existing single Run button. See `docs/runner/runTargetsDoc.md`.
538
+ - Code generation emits cleaner output and adds a TypeScript target. The
539
+ JavaScript `runGraph` is value-API-trimmed via a one-time `makeInput` /
540
+ `makeOutputs` / `makeContext` helper prelude (compact and dependency-free).
541
+ New `codegenTsRunTarget` (`id: 'codegen-ts'`) and the `makeCodegenRunTarget`
542
+ factory emit a typed `runGraph`, casting stored values from the
543
+ `CodegenMetadata` registry's `dataTypeToTsType` map (data-type id → TS type
544
+ string, e.g. `{ numberType: 'number' }`); both are public via the run-targets
545
+ barrel. Opt-in `returnValues` narrows what `runGraph` returns, and
546
+ `assumePureImplementations` additionally runs dead-code elimination, dropping
547
+ pure nodes no returned value depends on.
548
+ - Generated code now reads like hand-written source: values are readable local
549
+ variables (named from the node + handle, e.g. `bitInputOut`, deduped) declared
550
+ inline (`const sum = (await …).get("Sum")`) or hoisted, instead of a
551
+ `values["nodeId:handleId"]` map, and loops render as a natural `for` with a
552
+ single `if (!condition) break`. A node type can opt into an `emit` template
553
+ (supplied via the `CodegenMetadata` registry's per-node `emit` hook, see
554
+ `CodegenEmitContext`) to render itself as an inline expression (e.g.
555
+ `const gateOut = Boolean(a) && Boolean(b);`) instead of an implementation
556
+ call. The returned object keys stay `nodeId:handleId`.
557
+
558
+ ### Changed — type-level (no runtime change)
559
+
560
+ - `ConfigurableEdgeState['data']` is typed
561
+ `{ order?: number } & Record<string, unknown>` (was `{}`). Object `data`
562
+ payloads continue to compile, so this is a non-breaking, lint-clean
563
+ replacement for the bare `{}`. The typed `order` is the connection's fan-in
564
+ rank (see _Added — ordered fan-in connections_ above) and is the one edge
565
+ field the library reads; all edge **visuals** remain derived from the
566
+ connected handles at render time.
567
+ - `FullGraphContext`'s value type now matches its runtime shape:
568
+ `allProps.state` is `Pick<State, 'typeOfNodes' | 'enableDebugMode'>` (the only
569
+ slices the runtime value ever carried). Reading any other `state` field
570
+ through this context returned `undefined` at runtime before; it is a compile
571
+ error now. `allProps` additionally carries an `isAtRootScope` boolean (true
572
+ when no node group is open) so nodes can tell a root Graph I/O boundary from a
573
+ group-internal `groupInput` / `groupOutput`.
574
+
575
+ ### Changed — DOM class strings (computed styles identical)
576
+
577
+ - Hardcoded hex utility classes were renamed to semantic token utilities (e.g.
578
+ `bg-[#222222]` → `bg-graph-elevated-surface-bg`, including the string returned
579
+ by the exported `modalContentVariants`). Rendered pixels are unchanged;
580
+ consumers keying on literal class strings (CSS attribute selectors, DOM
581
+ snapshots) must update.
582
+ - Library-emitted CSS variables use a `--color-graph-*` namespace for the
583
+ generic surface tokens (menu, elevated surface, node panel, input placeholder,
584
+ scrollbar thumb, toggle track) to avoid colliding with consumer-defined
585
+ Tailwind theme tokens.
586
+
587
+ ### Fixed
588
+
589
+ - **`Select`**: the option-list sync no longer calls `setState` during render,
590
+ removing a React "Cannot update a component (`Select`) while rendering a
591
+ different component (`SelectContent`)" console error that fired on every graph
592
+ (the run-target picker and node-group breadcrumb use `Select`).
593
+ - **Codegen auto-emit**: hardened recognition so it cannot inline an
594
+ implementation that is not actually self-contained — a `readInput(...)` call
595
+ is only recognized when its first argument is the implementation's own input
596
+ parameter, and placeholder substitution is index-keyed so handle names
597
+ containing non-identifier characters (e.g. `"Color A"`) no longer corrupt the
598
+ emitted expression.
599
+ - **Root Graph I/O serialization round-trip**: `serializeExecutionPlan` now
600
+ preserves `rootInputNodeId` / `rootOutputNodeId`, and the execution-record
601
+ serializer preserves `rootOutputs`, so the `json-ir` export and recording
602
+ import/export no longer drop a graph's root I/O boundary.
603
+ - **Codegen ≡ executor on malformed structures**: the codegen loop/switch
604
+ lowering now applies the same handle-count / condition validation the executor
605
+ enforces, instead of silently emitting `<ref> = undefined` for a desynced
606
+ structure.
607
+ - **Auto-emit scope tracking**: the `deriveAutoEmit` visitor no longer
608
+ mis-recognizes a nested lambda parameter that shadows the implementation's
609
+ `inputs` parameter as a node-input read.
610
+ - **Graph I/O editor deletion review**: deleting a Graph Input/Output handle
611
+ that carries connections now opens the same blast-radius deletion review
612
+ (preview of the connections that will break) as the node-type editor.
613
+ - **`Select`**: `selectedIndex` reports `null` (not a transient `-1`) for the
614
+ commit before the option registry settles.
615
+ - **Topological-sort cycle**: a detected cycle now throws a structured
616
+ `GraphError` at the engine boundary instead of a bare `Error`.
617
+ - **Import validation**: importing a graph with duplicate or empty root Graph
618
+ I/O handle names, or extra root boundary nodes, is now validated rather than
619
+ silently collapsing at runtime.