@theclearsky/react-blender-nodes 0.0.11 → 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/README.md CHANGED
@@ -7,12 +7,67 @@
7
7
  a flexible and customizable node-based graph editor for web applications.
8
8
  </p>
9
9
 
10
+ > **Note**: This project is not affiliated with Blender Foundation. If you find
11
+ > Blender useful, consider
12
+ > [donating to support their work](https://fund.blender.org/).
13
+
10
14
  <p align="center">
11
15
  <a href="https://bundlejs.com/?q=%40theclearsky%2Freact-blender-nodes"><img src="https://deno.bundlejs.com/?q=%40theclearsky%2Freact-blender-nodes&badge=detailed&badge-style=for-the-badge" alt="spring-easing's badge" /></a>
12
16
  </p>
13
17
 
14
18
  ![React Blender Nodes Banner](./docs/screenshots/banner.png)
15
19
 
20
+ ## How these projects fit together
21
+
22
+ This library is the engine at the centre of a small family: two plugins extend
23
+ it, and one application proves it in anger.
24
+
25
+ ```text
26
+ react-blender-nodes · MIT · published
27
+ T H E E N G I N E
28
+ ┌────────────────────────────────────────────────────────────┐
29
+ │ ◀── this package │
30
+ │ A Blender-style node-graph editor for React. │
31
+ │ Typed handles · validate → plan → apply state · node │
32
+ │ groups · loops & switches · graph compiler + runner · │
33
+ │ import / export │
34
+ └──────┬──────────────────────┬────────────────────────┬─────┘
35
+ │ │ │
36
+ │ peerDependency │ peerDependency │ file:
37
+ │ >=0.0.13 <1 │ >=0.0.13 <1 │ dependency
38
+ │ │ (via /contract — │
39
+ ▼ ▼ React-free) │
40
+ ┌──────────────────────┐ ┌──────────────────────┐ │
41
+ │ …-timeline │ │ …-codegen │ │
42
+ │ AGPL-3.0 · published │ │ AGPL-3.0 · published │ │
43
+ ├──────────────────────┤ ├──────────────────────┤ │
44
+ │ Keyframed CURVES and │ │ Compiles a graph into│ │
45
+ │ a transport. A curve │ │ a standalone, │ │
46
+ │ becomes a live signal│ │ dependency-free │ │
47
+ │ the running graph │ │ runGraph module. │ │
48
+ │ can read. │ │ No React at runtime. │ │
49
+ └──────────┬───────────┘ └──────────────────────┘ │
50
+ │ │
51
+ │ file: dependency │
52
+ └───────────────────┬──────────────────────────┘
53
+
54
+ ┌────────────────────────────────────────────────────────────┐
55
+ │ react-blender-nodes-sound · AGPL-3.0 · private app │
56
+ │ T H E A P P L I C A T I O N │
57
+ ├────────────────────────────────────────────────────────────┤
58
+ │ Here the nodes ARE the audio graph (Tone.js / Web Audio): │
59
+ │ draw a waveform and hear it · gate-driven envelopes · │
60
+ │ 16-key polyphony · timeline curves automating any │
61
+ │ parameter while it plays · a spectrally-modelled │
62
+ │ instrument library │
63
+ └────────────────────────────────────────────────────────────┘
64
+ ```
65
+
66
+ An arrow points from a package **to the package that depends on it**. This
67
+ library stays MIT so anyone can build on it; the plugins and the app are
68
+ AGPL-3.0-only. The two plugins never import each other — the app is the only
69
+ place they meet.
70
+
16
71
  ## Quick Links
17
72
 
18
73
  - [![Storybook](https://img.shields.io/badge/Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=white)](https://theclearsky.github.io/react-blender-nodes/?path=/story/organisms-fullgraph--with-runner) -
@@ -34,6 +89,15 @@ system with automatic inference, complex data validation, and comprehensive
34
89
  connection validation to ensure your node graphs are always type-safe and
35
90
  error-free.
36
91
 
92
+ Beyond editing, the library can **execute** your graphs with a built-in runner
93
+ and timeline debugger — or hand them to pluggable **run targets**, including a
94
+ separate AGPL-3.0-only codegen plugin that compiles them to standalone
95
+ JavaScript/TypeScript — compose reusable **node groups**, build control flow
96
+ with first-class **loops** and **switches** (rendered as visual zones), edit
97
+ those structures and node types through in-canvas **drawers**, and step backward
98
+ and forward through every change with full **undo/redo** history. Graph state
99
+ and execution recordings can be exported to and imported from JSON.
100
+
37
101
  ## Quick Start
38
102
 
39
103
  ### Installation
@@ -51,8 +115,8 @@ import {
51
115
  makeStateWithAutoInfer,
52
116
  makeTypeOfNodeWithAutoInfer,
53
117
  makeDataTypeWithAutoInfer,
54
- } from 'react-blender-nodes';
55
- import 'react-blender-nodes/style.css';
118
+ } from '@theclearsky/react-blender-nodes';
119
+ import '@theclearsky/react-blender-nodes/style.css';
56
120
 
57
121
  function MyNodeEditor() {
58
122
  // Define data types with auto-infer for type safety
@@ -229,82 +293,220 @@ your graph into an execution plan and runs it — with full debugging support.
229
293
 
230
294
  ![Loop Execution Timeline](./docs/screenshots/execution-timeline-loop-iterations.png)
231
295
 
232
- ### Usage
296
+ ### ⚡ Codegen & Pluggable Run Targets — Export Your Graph as Code
297
+
298
+ ![Codegen — generate a standalone runGraph() live from the graph](./docs/screenshots/codegen-export.png)
299
+
300
+ The in-process runner is just the default **run target**. The Run button is a
301
+ split control: register your own named targets, use the built-in
302
+ `jsonIrRunTarget` to export the execution plan as a JSON intermediate
303
+ representation, or install the **separate codegen plugin** to compile a graph
304
+ into a **standalone, dependency-free function** — `runGraph(...)` — in
305
+ JavaScript or typed TypeScript.
306
+
307
+ - **Export as code (plugin)**: `codegenJsRunTarget` / `codegenTsRunTarget` from
308
+ [`@theclearsky/react-blender-nodes-codegen`](https://github.com/TheClearSky/react-blender-nodes-codegen)
309
+ (AGPL-3.0-only, published separately) emit a self-contained `runGraph()` (TS
310
+ adds typed parameters, a return type, and value casts). Built into this
311
+ package: `jsonIrRunTarget` exports the execution plan as JSON, and the default
312
+ `inProcessRunTarget` runs the graph live and feeds the timeline.
313
+ - **Clean signature from Graph I/O**: declare root **Graph Input** / **Graph
314
+ Output** nodes and their handle names become the function's parameters and
315
+ returned-object keys — an all-inlined graph compiles to a tidy
316
+ `function runGraph(a, b) { … return { out, flag }; }` with no plumbing
317
+ arguments.
318
+ - **Opt-in optimization passes**: by default the codegen targets emit a
319
+ faithful, threaded `runGraph`. Enable the codegen-v2 passes per target to get
320
+ the clean output — `assumePureImplementations` runs dead-code elimination
321
+ (drops branches no output depends on); unconnected inputs always inline their
322
+ current value; and `analyzeImplementations` makes value-API nodes whose
323
+ implementation reads inputs through the exported `readInput` intrinsic
324
+ **auto-emit inline as expressions** instead of threading through an
325
+ implementations argument.
326
+ - **Pluggable contract**: a `RunTarget` is either an `execute` target (produces
327
+ an `ExecutionRecord`, drives the timeline, and may support stepping) or an
328
+ `artifact` target (produces a string/download). Register them via the
329
+ `runTargets` prop and pick the default with `defaultRunTargetId`.
233
330
 
234
331
  ```tsx
235
- import { FullGraph, useFullGraph } from 'react-blender-nodes';
236
- import { makeFunctionImplementationsWithAutoInfer } from 'react-blender-nodes';
237
-
238
- // Define what each node type does when executed
239
- const functionImplementations = makeFunctionImplementationsWithAutoInfer({
240
- myNodeType: async ({ inputs }) => {
241
- // Process inputs and return outputs
242
- return { outputHandle: inputs.inputHandle * 2 };
243
- },
244
- });
332
+ import { FullGraph, jsonIrRunTarget } from '@theclearsky/react-blender-nodes';
333
+ // Code generation is a separate, AGPL-3.0-only plugin:
334
+ import {
335
+ codegenJsRunTarget,
336
+ codegenTsRunTarget,
337
+ } from '@theclearsky/react-blender-nodes-codegen';
245
338
 
246
- // Pass implementations to FullGraph to enable the runner
339
+ // The Run button becomes a split control listing every registered target.
247
340
  <FullGraph
248
341
  state={state}
249
342
  dispatch={dispatch}
250
343
  functionImplementations={functionImplementations}
344
+ runTargets={[codegenJsRunTarget, codegenTsRunTarget, jsonIrRunTarget]}
251
345
  />;
252
346
  ```
253
347
 
254
- ### useNodeRunner Hook
348
+ ### 🔁 Loops, 🔀 Switches & Zones
349
+
350
+ Build control flow directly on the canvas. Loops and switches are first-class
351
+ structures backed by dedicated standard nodes, and each renders as a labelled
352
+ **zone** — a frame polygon drawn around the nodes it contains.
353
+
354
+ - **Loops**: Drop a loop-start / loop-stop / loop-end node triplet to define an
355
+ iterative body. The runner compiles the body into a `LoopExecutionBlock` and
356
+ records every iteration, with a configurable max-iteration safety limit.
357
+ - **Switches**: A switch-start / switch-end pair routes execution down a `true`
358
+ or `false` branch based on a condition handle. The compiler resolves the taken
359
+ branch and skips the other, surfacing skipped nodes on the canvas.
360
+ - **Zones**: System zones are created and re-discovered automatically as you add
361
+ structures or change connections. Each zone tracks the body nodes inside it
362
+ and can enforce connection boundaries (blocking edges that cross in or out).
363
+ Zones are scope-local, so structures inside a node group get their own zones.
364
+ - **Nesting**: Loops, switches, and groups can be nested inside one another;
365
+ zone discovery and the compiler resolve nested structures recursively.
366
+
367
+ ### 🪟 In-Canvas Editors
368
+
369
+ Structures and node types are edited through slide-out drawers, dispatched via
370
+ the graph state and tracked on `state.activeDrawer`:
371
+
372
+ - **Node Type editor** (`editNodeType`): rename a node type, change its header
373
+ color, and add, remove, or reorder its inputs and outputs.
374
+ - **Loop editor** (`editLoop`): configure the handles carried through the loop
375
+ triplet, organized into levels.
376
+ - **Switch editor** (`editSwitch`): configure the handles carried through the
377
+ switch pair across its true/false branches.
378
+ - **Graph I/O editor** (`editGraphInput` / `editGraphOutput`): rename, reorder,
379
+ add, or delete the handles of a root **Graph Input / Output** node — the
380
+ graph's I/O boundary, whose handle names define the `runGraph(...)` signature
381
+ used by codegen.
382
+
383
+ ![Graph I/O editor — rename, reorder, and add graph input/output handles](./docs/screenshots/graph-io-editor.png)
384
+
385
+ The Node Type / Node Group editor (`editNodeType`) edits a type's name, header
386
+ color, and its full input/output list — including grouping handles into
387
+ collapsible **panels** and drag-reordering them:
388
+
389
+ ![Node type editor — name, header color, panelled inputs (Transform, Color Settings), outputs, drag-reorder](./docs/screenshots/editor-nodetype.png)
390
+
391
+ Deleting a handle or channel that carries connections opens a **deletion
392
+ review** — it previews exactly which connections would break (with an expandable
393
+ mini-map highlighting them) and lets you include or exclude each deletion before
394
+ committing:
395
+
396
+ ![Deletion review — preview of the connections each handle deletion will break](./docs/screenshots/editor-deletion-review.png)
397
+
398
+ ### ↩️ Undo / Redo History
399
+
400
+ Every structural edit is recorded in an Immer-patch-based undo/redo history, so
401
+ users can freely step backward and forward.
402
+
403
+ - **Patch-based**: history stores forward and inverse Immer patches per entry —
404
+ compact and exact, with a configurable `maxSize`.
405
+ - **Smart undoability**: viewport changes, navigation, drawer open/close, and
406
+ selection-only ReactFlow updates are intentionally _not_ recorded.
407
+ - **Batching**: `BEGIN_BATCH` / `END_BATCH` collapse a sequence of related edits
408
+ (e.g. a multi-node drag) into a single undo step.
409
+ - **Keyboard shortcuts**: `<FullGraph>` listens for `Ctrl+Z` / `Ctrl+Shift+Z` /
410
+ `Ctrl+Y` by default (toggle with `enableUndoRedoShortcuts`).
411
+ - **Serializable**: history can be exported and re-imported alongside graph
412
+ state (non-serializable patch values such as Zod schemas are stripped).
413
+
414
+ ### 📡 Graph Event Stream
415
+
416
+ For tests, dev tooling, and telemetry, subscribe to a single unified
417
+ observability stream via `onGraphEvent`. Reducer-layer events (`action:applied`
418
+ / `action:rejected` / `state:committed`) carry typed payloads (e.g. an
419
+ `action:rejected` event carries the original `ValidationError` so you can switch
420
+ on `.code`), and UI-layer events (`ui:drag:ended` / `ui:delete:attempted` /
421
+ `ui:state:imported` / `ui:recording:imported`) cover moments that bypass the
422
+ reducer. Pass the _same_ handler to both
423
+ `useFullGraph(initialState, { onGraphEvent })` and
424
+ `<FullGraph onGraphEvent={...} />` to receive every event.
255
425
 
256
- For advanced control over graph execution, use the `useNodeRunner` hook directly
257
- instead of relying on the built-in runner UI:
426
+ ### Usage
258
427
 
259
428
  ```tsx
260
- import { FullGraph, useFullGraph, useNodeRunner } from 'react-blender-nodes';
429
+ import {
430
+ FullGraph,
431
+ useFullGraph,
432
+ makeFunctionImplementationsWithAutoInfer,
433
+ } from '@theclearsky/react-blender-nodes';
434
+
435
+ // Define what each node type does when executed.
436
+ // An implementation receives positional args: (inputs, outputs, context).
437
+ // `inputs` is a ReadonlyMap keyed by handle *name*; read a connected value
438
+ // via inputs.get('Name')?.connections[0]?.value. Return a Map of output
439
+ // handle *names* to computed values (sync Map or Promise<Map>).
440
+ const functionImplementations = makeFunctionImplementationsWithAutoInfer({
441
+ myNodeType: async (inputs) => {
442
+ const value = Number(inputs.get('Input')?.connections[0]?.value ?? 0);
443
+ return new Map([['Output', value * 2]]);
444
+ },
445
+ });
261
446
 
262
447
  function MyExecutableGraph() {
263
448
  const { state, dispatch } = useFullGraph(initialState);
264
449
 
265
- const {
266
- // State
267
- runnerState, // 'idle' | 'compiling' | 'running' | 'paused' | 'completed' | 'errored'
268
- nodeVisualStates, // Map<nodeId, 'idle' | 'running' | 'completed' | 'errored' | 'skipped'>
269
- executionRecord, // Full execution recording with per-step timing and I/O snapshots
270
- currentStepIndex, // Index of the currently active/viewed step
271
-
272
- // Actions
273
- run, // Start execution (mode-aware: instant or step-by-step)
274
- pause, // Pause during step-by-step execution
275
- resume, // Resume paused step-by-step execution
276
- step, // Advance one step (starts a new run if idle)
277
- stop, // Abort the current execution
278
- reset, // Clear all execution state back to idle
279
- replayTo, // Seek to a specific step index in a completed recording
280
- loadRecord, // Load an imported ExecutionRecord (validates against current graph)
281
-
282
- // Settings
283
- mode, // Current execution mode: 'instant' | 'stepByStep'
284
- setMode, // Switch execution mode
285
- maxLoopIterations, // Max iterations before a loop is force-stopped
286
- setMaxLoopIterations,
287
- } = useNodeRunner({
288
- state,
289
- functionImplementations,
290
- options: { maxLoopIterations: 100 },
291
- });
450
+ // Pass implementations to FullGraph to enable the runner
451
+ return (
452
+ <FullGraph
453
+ state={state}
454
+ dispatch={dispatch}
455
+ functionImplementations={functionImplementations}
456
+ />
457
+ );
458
+ }
459
+ ```
460
+
461
+ ### Controlling execution
462
+
463
+ Execution is driven through `FullGraph`'s runner props the runner hook itself
464
+ (`useNodeRunner`) is internal to `FullGraph` and is not exported. Register run
465
+ targets, pick the default, seed the root Graph Input, and observe or control the
466
+ execution record:
467
+
468
+ ```tsx
469
+ import { useState } from 'react';
470
+ import {
471
+ FullGraph,
472
+ useFullGraph,
473
+ jsonIrRunTarget,
474
+ type ExecutionRecord,
475
+ } from '@theclearsky/react-blender-nodes';
476
+
477
+ function MyExecutableGraph() {
478
+ const { state, dispatch } = useFullGraph(initialState);
479
+ const [record, setRecord] = useState<ExecutionRecord | undefined>();
292
480
 
293
481
  return (
294
- <div>
295
- <button onClick={run}>Run</button>
296
- <button onClick={step}>Step</button>
297
- <button onClick={pause}>Pause</button>
298
- <button onClick={resume}>Resume</button>
299
- <button onClick={stop}>Stop</button>
300
- <button onClick={reset}>Reset</button>
301
- <p>Status: {runnerState}</p>
302
- <FullGraph state={state} dispatch={dispatch} />
303
- </div>
482
+ <FullGraph
483
+ state={state}
484
+ dispatch={dispatch}
485
+ functionImplementations={functionImplementations}
486
+ runTargets={[jsonIrRunTarget]} // the Run button becomes a split control
487
+ rootInputs={{ a: 2, b: 3 }} // seeds the root Graph Input by handle name (or id)
488
+ executionRecord={record} // controlled record — you own its lifecycle
489
+ onExecutionRecordChange={setRecord}
490
+ />
304
491
  );
305
492
  }
306
493
  ```
307
494
 
495
+ For headless use — CI checks, tooling, or inspecting what the runner will do —
496
+ compile a graph directly and serialize the plan:
497
+
498
+ ```ts
499
+ import {
500
+ compile,
501
+ serializeExecutionPlan,
502
+ } from '@theclearsky/react-blender-nodes';
503
+
504
+ const plan = compile(state, functionImplementations, {
505
+ maxLoopIterations: 100,
506
+ });
507
+ console.log(JSON.stringify(serializeExecutionPlan(plan), null, 2));
508
+ ```
509
+
308
510
  ### Import/Export & Automatic Repair
309
511
 
310
512
  Graph state and execution recordings can be exported to JSON and re-imported
@@ -316,7 +518,7 @@ repair common issues via opt-in repair strategies.
316
518
  Pass a `repair` object to `importGraphState` to enable automatic fixes:
317
519
 
318
520
  ```tsx
319
- import { importGraphState } from 'react-blender-nodes';
521
+ import { importGraphState } from '@theclearsky/react-blender-nodes';
320
522
 
321
523
  const result = importGraphState(json, {
322
524
  dataTypes: myDataTypes,
@@ -326,7 +528,8 @@ const result = importGraphState(json, {
326
528
  removeDuplicateNodeIds: true, // Deduplicate nodes with the same ID (keep first)
327
529
  removeDuplicateEdgeIds: true, // Deduplicate edges with the same ID (keep first)
328
530
  fillMissingDefaults: true, // Fill missing optional fields (viewport, etc.) with defaults
329
- rehydrateDataTypeObjects: true, // Rebuild handle dataType objects from provided dataTypes
531
+ rehydrateDataTypeObjects: true, // Effectively always-on — the importer always rebuilds handle dataType objects from provided dataTypes (this flag is not read)
532
+ normalizeConnectionOrder: true, // Repack imported fan-in connection orders to contiguous 0..n-1
330
533
  },
331
534
  });
332
535
 
@@ -343,12 +546,12 @@ if (result.success) {
343
546
  Pass a `repair` object to `importExecutionRecord` for recording-specific fixes:
344
547
 
345
548
  ```tsx
346
- import { importExecutionRecord } from 'react-blender-nodes';
549
+ import { importExecutionRecord } from '@theclearsky/react-blender-nodes';
347
550
 
348
551
  const result = importExecutionRecord(json, {
349
552
  repair: {
350
- sanitizeNonSerializableValues: true, // Replace non-serializable values with "[non-serializable]"
351
- removeOrphanSteps: true, // Remove steps referencing nodes not present in the record
553
+ sanitizeNonSerializableValues: true, // No-op values parsed from JSON are already serializable; kept for API symmetry
554
+ removeOrphanSteps: true, // Remove malformed steps missing nodeId, nodeTypeId, or stepIndex
352
555
  },
353
556
  });
354
557
  ```
@@ -498,24 +701,52 @@ const handleShapes = [
498
701
  // Clicking a node type adds it at the cursor position
499
702
  ```
500
703
 
501
- ## 🎨 Styling
704
+ ## 🎨 Styling & Theming
705
+
706
+ The whole graph — canvas, nodes, handles, menus, runner panel, timeline, and the
707
+ in-canvas editor drawers — is retheme-able from a single typed theme object.
708
+ Below: the same graph under the built-in path plus two custom presets (a
709
+ cyberpunk "Neon Heist" and a comic "Halftone Pop") — nodes, edges, run button,
710
+ background grid, and accents all follow the active theme.
711
+
712
+ ![Neon Heist theme — cyberpunk magenta/cyan, glowing nodes and run button](./docs/screenshots/theme-neonheist.png)
502
713
 
503
- The library uses Tailwind CSS for styling and provides a dark theme that matches
504
- Blender's aesthetic:
714
+ ![Halftone Pop theme comic pop-art, yellow halftone background and red accents](./docs/screenshots/theme-halftonepop.png)
715
+
716
+ Import the stylesheet once; the graph ships with the default Blender-style dark
717
+ look:
505
718
 
506
719
  ```css
507
- /* Import the default styles */
508
- @import 'react-blender-nodes/style.css';
509
-
510
- /* Customize colors using CSS variables */
511
- :root {
512
- --primary-black: #181818;
513
- --primary-dark-gray: #272727;
514
- --primary-gray: #3f3f3f;
515
- --primary-white: #ffffff;
516
- }
720
+ @import '@theclearsky/react-blender-nodes/style.css';
721
+ ```
722
+
723
+ To retheme the graph, wrap it in the optional `GraphThemeProvider` — a theme is
724
+ a typed map of per-component/per-slot Tailwind className overrides plus a
725
+ `reactFlow` section, deep-merged over a named preset (`'blenderDark'` |
726
+ `'light'`):
727
+
728
+ ```tsx
729
+ import {
730
+ FullGraph,
731
+ GraphThemeProvider,
732
+ } from '@theclearsky/react-blender-nodes';
733
+
734
+ <GraphThemeProvider preset='light' theme={{ node: { header: 'rounded-none' } }}>
735
+ <FullGraph state={state} dispatch={dispatch} />
736
+ </GraphThemeProvider>;
517
737
  ```
518
738
 
739
+ Without a provider the graph keeps its default look — theming is purely
740
+ additive. The built-in presets work out of the box (their classes ship in
741
+ `style.css`); classes you write yourself need your own Tailwind v4 build
742
+ scanning the files that contain them. Var-driven surfaces (scrollbars, glows,
743
+ timeline accents) are recolored through CSS variables on the `root` slot, e.g.
744
+ `theme={{ root: '[--color-graph-menu-bg:#f5f5f5]' }}` — re-declaring the
745
+ `@theme inline` tokens from your own CSS does NOT restyle the pre-built
746
+ stylesheet (those utilities inline their values at build time). See
747
+ [docs/ui/themingDoc.md](docs/ui/themingDoc.md) for the full slot map, the three
748
+ theming mechanisms, and the portal caveats.
749
+
519
750
  ## 📚 Documentation
520
751
 
521
752
  ### Interactive Documentation
@@ -549,8 +780,8 @@ includes an ASCII architecture diagram, cross-feature dependency maps, and a
549
780
  | Modifying graph editor UI | `fullGraphDoc`, `configurableNodeDoc`, `contextMenuDoc` |
550
781
  | Working with state/reducer | `stateManagementDoc`, `immerDoc`, `edgesDoc` |
551
782
 
552
- See the [full index](./docs/index.md) for all 32 feature docs with relative
553
- links organized by tier.
783
+ See the [full index](./docs/index.md) for all 39 documentation files with
784
+ relative links organized by tier.
554
785
 
555
786
  ### Component API
556
787
 
@@ -576,6 +807,12 @@ interface FullGraphProps {
576
807
  executionRecord?: ExecutionRecord | null;
577
808
  /** Called whenever the execution record changes (run completes, reset, load, etc.). */
578
809
  onExecutionRecordChange?: (record: ExecutionRecord | null) => void;
810
+ /** Unified observability stream for UI lifecycle events (drag end, delete-attempt verdict, import outcomes). Pair with the same handler on useFullGraph for reducer-layer events. */
811
+ onGraphEvent?: (event: GraphEvent) => void;
812
+ /** Registry of custom input components keyed by DataTypeUniqueId, for data types whose underlyingType resolves to 'unsupportedDirectly'. */
813
+ inputComponents?: InputComponentRegistry;
814
+ /** Whether to listen for Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y undo/redo keyboard shortcuts. Defaults to true. */
815
+ enableUndoRedoShortcuts?: boolean;
579
816
  }
580
817
  ```
581
818
 
@@ -601,8 +838,6 @@ interface ConfigurableNodeProps {
601
838
  nodeResizerProps?: NodeResizerWithMoreControlsProps;
602
839
  /** Node type unique id */
603
840
  nodeTypeUniqueId?: string;
604
- /** Whether to show the node open button (used by node groups) */
605
- showNodeOpenButton?: boolean;
606
841
  /** Runner visual state for this node (undefined = no runner overlay) */
607
842
  runnerVisualState?: NodeVisualState;
608
843
  /** Errors from the runner for this node */