Commit Graph

2 Commits

Author SHA1 Message Date
Arnas Donauskas
dcdfff938d
feat(flows): canvas / mind-map view (PR 2b of 3 — full editing)
Closes the editing-affordance gap from PR 2a. The canvas is now in
full parity with the list view for everything that fits in a node
graph: connections, deletes, and adding nodes all work on-canvas.

Adds:

  - Per-slot Handle components on the custom node renderer. Single-
    outgoing types get one handle on the right edge; condition gets
    `true` / `false` handles; send_buttons and send_list render slot
    rows inside the card with one handle each, so the user can SEE
    which button or list row they're wiring. Every non-start node
    gets a target handle on the left.

  - Drag-to-connect (`onConnect`). Looks up the source node, dispatches
    the right config patch via the new `applyEdgeConnection(node,
    sourceHandle, target)` helper, and routes through updateNodeConfig.
    Rejects self-loops silently (a button whose target is its own node
    = infinite reprompt).

  - Keyboard delete on nodes (Backspace + Delete, both for cross-OS
    parity). Routes through removeNode, which now ALSO unlinks every
    inbound reference via the new `unlinkNodeReferences(nodes, key)`
    helper. No more dangling arrows after a delete — the validator
    previously flagged these as errors and the user had to chase them
    down manually.

  - Edge delete. Selecting an edge + Delete clears just that slot's
    next_node_key (rather than removing anything), via the same
    applyEdgeConnection helper passing "" as the target.

  - Floating "+ Add node" button (React-Flow Panel, bottom-right).
    Same dropdown as the list view; drops the new node at the visible
    viewport center via reactFlow.screenToFlowPosition.

New pure helpers in src/lib/flows/edges.ts (no React, fully unit-
tested — 18 new tests, 319 total passing):

  - outgoingSlots(node) — list of {id, label} per outgoing slot.
  - applyEdgeConnection(node, sourceHandle, targetKey) — config patch
    for a drag-to-connect, or null for unrecognised handles.
  - unlinkNodeReferences(nodes, deletedKey) — clears every direct +
    nested reference to deletedKey. Returns nodes-by-identity when
    no reference matches to avoid needless re-renders.

The hook's removeNode is the only consumer of unlinkNodeReferences
right now, which means LIST-view deletes also stop leaving dangling
refs — strict improvement for both views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:32:59 +02:00
Arnas Donauskas
1645034e1a
feat(flows): canvas / mind-map view (PR 2a of 3 — editable foundation)
Builds on PR 1's read-only canvas. This PR brings the canvas to ~75%
of list-view parity: drag-to-reposition (persisted) and click-to-edit
via a side panel that mounts the same per-node forms the list view
uses. Drag-to-connect + add/delete from canvas land in PR 2b.

Architecture (the bulk of the diff):

  - Extracted the per-node forms (SendButtonsForm, SendListForm,
    SendMediaForm, ConditionForm, SetTagForm + inline forms for the
    smaller types) plus the shared field components (TextRow,
    NextNodeRow, NodeKeySelect) from flow-builder.tsx into
    src/components/flows/forms/. Both views now mount the SAME
    NodeConfigForm dispatcher so behaviour can't drift between
    "what the list editor shows" and "what the canvas side panel
    shows".
  - Lifted all editor state into a context/hook
    (src/components/flows/flow-editor-state.tsx) with a
    FlowEditorProvider that wraps both views in flow-editor-shell.
    Single source of truth: toggling Canvas ⇄ List never resets
    unsaved edits, and a drag on the canvas updates the same nodes
    array the list view reads.
  - flow-builder.tsx shrinks from ~2400 lines to ~900 (extracted
    forms) and stops owning state (now consumes via useFlowEditor()).
    List-only UI state — expanded cards, scroll refs, jump-to-node
    flash — stays internal.

Canvas additions:

  - onNodeDragStop writes the new position to context via
    updateNodePosition (flips dirty; serialized in the existing PUT
    /api/flows/[id] body — the route already accepts position_x /
    position_y per migration 010).
  - onNodeClick opens a controlled shadcn Sheet (right-side panel)
    with the per-node form, plus "Set as entry" and "Delete node"
    buttons. Esc / overlay / close button all close.

What's intentionally still NOT in this PR (PR 2b):

  - Per-slot Handle components on the custom node renderer.
  - Drag-to-connect handler that maps target → updated
    next_node_key for the right slot.
  - Floating "+" button to add nodes from canvas.
  - Keyboard / context-menu delete.

Tests: 9 new pure-helper tests (uniqueNodeKey + defaultConfigFor
self-consistency). Hook integration tests are deferred — would
require @testing-library/react + jsdom infra. Existing 271 tests
still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:17:15 +02:00