@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 +619 -0
- package/README.md +408 -32
- package/dist/contract-DxqU738Q.js +514 -0
- package/dist/contract.d.ts +1119 -0
- package/dist/index.d.ts +3636 -523
- package/dist/react-blender-nodes-contract.es.js +9 -0
- package/dist/react-blender-nodes.css +1 -1
- package/dist/react-blender-nodes.es.js +34971 -22422
- package/package.json +34 -13
- package/dist/react-blender-nodes.umd.js +0 -305
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.
|