[Feat]: Canvas node editor (Prompt / Generate / Preview)
Ports the tldraw Image pipeline starter's node editor into our canvas
as a thin node-graphics layer. The user drops typed nodes, wires them
with ports, and "Run pipeline" sends the graph to LangGraph as a user
message — the agent orchestrates execution, no parallel engine.
Why this layer. The canvas's plain shapes can't carry typed
connections. Nodes + ports let the user compose structured intent
("this prompt feeds this generator") without writing prose.
Why no execution engine. LangGraph already runs the agent, the
tool calls, the credits, the checkpointer. We don't write a parallel
one. The canvas serializes → user message → agent → tools. Same
plumbing the rest of the chat uses.
Shipped: ~24 files in components/canvas/, three node types
(prompt / generate / preview), the connection / port / binding
system, port hit-testing + the pointing_port state, the
CenterHandle insert overlay, CanvasProvider.createNode bridge.
Stripped from starter: 14 other node types, the worker / wrangler
config, the execution engine (ExecutionGraph + executionState), and
per-node Run / Stop — replaced by a canvas-level Run trigger
(designed in CANVAS_NODES.md §"Run pipeline", future work).
Wiring. CanvasEditor mounts the shapes / bindings / overlay
arrays and installs PointingPort into the select state. globals.css
imports canvas-nodes.css (the starter's CSS, with the body reset /
Inter / tldraw.css imports removed — already loaded).
Bridge. lib/canvas/context.tsx gains createNode({ type, props, x?, y? }) so tool UI cards can drop nodes. Returns the shape id so
the caller can bind results back later.
Docs. docs/CANVAS_NODES.md covers the design, the data flow,
the MVP scope, what's deliberately excluded, and future work (Run
pipeline button, more node types, per-node agent calls, inline
upcoming-edit preview).
🤖 Generated with Claude Code
Summary by cubic
Adds a node editor layer to the canvas so users can place Prompt/Generate/Preview nodes and wire typed ports. Old behavior: plain shapes only. New behavior: draggable nodes with input/output ports and connections; no on-canvas execution. Side effects: globals.css now imports canvas node styles, CanvasEditor registers new shape/binding/overlay utils and a PointingPort tool, and we add classnames.
- Rendering/interaction: review
NodeShapeUtil,ConnectionShapeUtil,ConnectionBindingUtil,ConnectionCenterHandleOverlayUtil, andPointingPortfor port hit-testing, data-type compatibility, and z-ordering (connections stay under nodes). The on-canvas node picker is stubbed (OnCanvasNodePickerStub); the center “+” handle sets state but shows no UI. - Graph plumbing:
lib/canvas/context.createNode({ type, props, x?, y? })drops nodes and returns the shape id; runtime validators guard invalid props. No execution engine ships; serialization to LangGraph remains the only run path. - Styling:
app/globals.cssimportsapp/canvas-nodes.css(scoped to tldraw classes). Verify it does not regress app-wide styles.
Rollout/Migration
- Run
pnpm installto addclassnames. - Tool UI that places nodes must call
useCanvas().createNode({ type: 'prompt'|'generate'|'preview', props, x?, y? }). - No data migrations and no change to execution behavior; “Run pipeline” remains a chat message to the LangGraph agent.
Greptile Summary
The PR adds typed Prompt, Generate, and Preview canvas nodes with ports, connection bindings, insertion controls, persistence integration, and a cross-component creation bridge.
- Registers custom node, connection, binding, overlay, and port state-machine utilities in CanvasEditor.
- Adds node rendering, graph traversal, compatibility, ordering, and result-display behavior.
- Adds global node-editor styling and documents the planned LangGraph serialization flow.
Confidence Score: 3/5
The PR is not safe to merge until the ignored stylesheet import and nonfunctional picker-dependent insertion interactions are fixed.
Canvas node styling currently fails to load, while multiple mounted user interactions route insertion and cleanup through callbacks that no component ever consumes; the creation bridge and missing test coverage add further non-blocking concerns.
Files Needing Attention: app/globals.css, components/canvas/connection/ConnectionCenterHandleOverlayUtil.tsx, components/canvas/ports/PointingPort.tsx, lib/canvas/context.tsx
Important Files Changed
| Filename | Overview |
|---|---|
| app/globals.css | Adds the node stylesheet import after existing CSS rules, causing the import to be ignored. |
| components/canvas/connection/ConnectionShapeUtil.tsx | Implements connection rendering and drag lifecycle, including a picker-dependent empty-drop branch that has no consumer. |
| components/canvas/ports/PointingPort.tsx | Adds output-port click and drag wiring; click-to-insert leaves completion and cleanup inside unread picker state. |
| lib/canvas/context.tsx | Adds the createNode bridge, but optional partial props are not merged with required node defaults. |
| components/chat/CanvasEditor.tsx | Correctly registers the custom shape, binding, overlay, and pointing-port utilities, making the affected interactions reachable. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
P[Prompt node] -->|text binding| G[Generate node]
G -->|image binding| V[Preview node]
U[Port and center-handle interactions] --> S[Picker state atom]
S -. no mounted consumer .-> X[Insertion callbacks never run]
C[Tool UI / agent] --> B[CanvasProvider.createNode]
B --> T[tldraw node validation]
T --> P
T --> G
T --> V
Prompt To Fix All With AI
### Issue 1
app/globals.css:441
**Late stylesheet import is ignored**
The `@import` follows ordinary CSS rules, so browsers ignore `canvas-nodes.css`, causing node cards, ports, connection paths, and interaction affordances to render without their required layout and styling.
### Issue 2
components/canvas/connection/ConnectionCenterHandleOverlayUtil.tsx:72-75
**Insertion callbacks have no consumer**
When users click a connection's plus handle, click an output port, or drop a new connection on empty canvas, the insertion and cleanup callbacks are stored in `onCanvasNodePickerState`, but no component reads that state. The plus handle therefore inserts nothing, and output-port click paths leave an incomplete connection because `onClose` never runs.
### Issue 3
lib/canvas/context.tsx:112-115
**Optional props omit required defaults**
The public bridge permits omitted or partial `props`, but this payload does not merge the selected node definition's defaults. Calls such as `createNode({ type: "preview" })` therefore omit validator-required fields and are rejected instead of creating the requested node.
### Issue 4
components/canvas/connection/ConnectionShapeUtil.tsx:134-211
**Graph behavior lacks test coverage**
This PR adds connection replacement, cycle prevention, port compatibility, binding lifecycle, and multi-input ordering without automated tests. That violates the repository's TDD requirement and leaves regressions in the core node-wiring behavior undetected by the test suite.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Reviews (1): Last reviewed commit: "feat(canvas): add node editor (Prompt/Ge..." | Re-trigger Greptile
Greptile also left 4 inline comments on this PR.
Context used:
- Context used - CLAUDE.md (source)