Fix: Cut and Paste functionality #9

Merged
tymurbaniak merged 3 commits from claude/issue-8 into main 2026-03-26 01:00:42 +00:00
Owner

Implements cut and paste functionality for graph nodes (closes #8).

Summary

  • Add CutStore Zustand store (src/stores/CutStore.ts) to hold cut node IDs, nodes, edges, and source graph ID
  • Update graphToDot() in Graphviz.tsx to render cut nodes with dashed grey fill as visual indicator
  • Add Cut action to the node context menu; Paste action to the empty-area context menu
  • Cut logic: takes ctrl+selected nodes (or right-clicked node if none selected) plus all nodes reachable via direct outgoing edges; clears selection store
  • Paste logic: adds nodes/edges to target graph and removes them from source graph in GraphsStore
  • Pasting on the same graph as the cut source cancels the cut (no duplication)
  • Graph.tsx subscribes to cutNodeIds so the graph re-renders automatically on cut; clears cut store on file load
  • 7 Playwright E2E tests in e2e/cut-paste.spec.ts

Assumptions

  • "Nodes linked with link direction from selected nodes" = direct outgoing neighbours (one hop)
  • Pasting on the same graph as cut source cancels the cut instead of duplicating nodes
  • If no nodes are ctrl+selected when Cut is clicked, the right-clicked node is the only base node

Test plan

  • Cut option visible in node context menu
  • Paste option visible in empty-area context menu
  • Cut nodes display with dashed grey styling
  • Linked nodes (outgoing) are included in the cut set
  • Paste adds nodes to target subgraph
  • After paste, nodes are removed from the source graph
  • Same-graph paste cancels cut without duplication

🤖 Generated with Claude Code

Implements cut and paste functionality for graph nodes (closes #8). ## Summary - Add `CutStore` Zustand store (`src/stores/CutStore.ts`) to hold cut node IDs, nodes, edges, and source graph ID - Update `graphToDot()` in `Graphviz.tsx` to render cut nodes with dashed grey fill as visual indicator - Add **Cut** action to the node context menu; **Paste** action to the empty-area context menu - Cut logic: takes ctrl+selected nodes (or right-clicked node if none selected) plus all nodes reachable via direct outgoing edges; clears selection store - Paste logic: adds nodes/edges to target graph and removes them from source graph in GraphsStore - Pasting on the same graph as the cut source cancels the cut (no duplication) - `Graph.tsx` subscribes to `cutNodeIds` so the graph re-renders automatically on cut; clears cut store on file load - 7 Playwright E2E tests in `e2e/cut-paste.spec.ts` ## Assumptions - "Nodes linked with link direction from selected nodes" = direct outgoing neighbours (one hop) - Pasting on the same graph as cut source cancels the cut instead of duplicating nodes - If no nodes are ctrl+selected when Cut is clicked, the right-clicked node is the only base node ## Test plan - [ ] Cut option visible in node context menu - [ ] Paste option visible in empty-area context menu - [ ] Cut nodes display with dashed grey styling - [ ] Linked nodes (outgoing) are included in the cut set - [ ] Paste adds nodes to target subgraph - [ ] After paste, nodes are removed from the source graph - [ ] Same-graph paste cancels cut without duplication 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

Change assumption: "Nodes linked with link direction from selected nodes" = direct outgoing neighbours (one hop)

It should be:
Nodes that are outgoing neighbours and structure for which they are root nodes = outgoing structure (all hops)

Change assumption: "Nodes linked with link direction from selected nodes" = direct outgoing neighbours (one hop) It should be: Nodes that are outgoing neighbours and structure for which they are root nodes = outgoing structure (all hops)
Author
Owner

Addressed the feedback: changed the cut operation from collecting only direct outgoing neighbours (one hop) to a full BFS traversal following outgoing edges from all selected nodes (all hops).

The case 'cut' in NodeContextMenu.tsx now uses a BFS queue starting from the base nodes, expanding through each outgoing edge until no new nodes are found. This means cutting a node now captures its entire outgoing subtree, not just its immediate children.

Please re-check src/components/NodeContextMenu.tsx lines 115–124.

Addressed the feedback: changed the cut operation from collecting only direct outgoing neighbours (one hop) to a full BFS traversal following outgoing edges from all selected nodes (all hops). The `case 'cut'` in `NodeContextMenu.tsx` now uses a BFS queue starting from the base nodes, expanding through each outgoing edge until no new nodes are found. This means cutting a node now captures its entire outgoing subtree, not just its immediate children. Please re-check `src/components/NodeContextMenu.tsx` lines 115–124.
Author
Owner

Another issue to address: when a tree of nodes is subject of cut and paste action, the navigation tree is not updated - if some nodes with subgraphs are moved this way it should be also visible in the tree. Example: in main graph there are nodes - 'sky' and 'apple', 'sky' has a subgraph that contains 'wave' which also contains a subgraph. 'wave' in the tree is displayed as child of 'sky'. User cuts 'wave' then navigates to 'apple' and pastes - node 'wave' now should be displayed as child of 'apple' in the navigation tree and breadcrumbs also should be consistent with the new state.

Another issue to address: when a tree of nodes is subject of cut and paste action, the navigation tree is not updated - if some nodes with subgraphs are moved this way it should be also visible in the tree. Example: in main graph there are nodes - 'sky' and 'apple', 'sky' has a subgraph that contains 'wave' which also contains a subgraph. 'wave' in the tree is displayed as child of 'sky'. User cuts 'wave' then navigates to 'apple' and pastes - node 'wave' now should be displayed as child of 'apple' in the navigation tree and breadcrumbs also should be consistent with the new state.
Author
Owner

Addressed the feedback — the navigation tree now updates correctly when nodes with subgraphs are cut and pasted.

What changed:

  • Added a move(nodeId, newParentId) function to TreeStore that reparents a tree node: removes it from its current parent (or root) and places it under the new parent, updating parentIdByChildId, the parent's children array, and rebuilding the tree.
  • In the paste logic (NodeContextMenu.tsx), after transferring nodes/edges between graphs, the code now iterates over the cut node IDs and calls moveTreeNode for any that have a tree entry (i.e. have a subgraph). Only nodes whose tree parent matches the source graph are reparented — nested tree nodes (subgraphs within the cut structure) keep their existing parent relationships intact.

Example from the review: After cutting wave from sky's subgraph and pasting into apple's subgraph, wave now appears as a child of apple in the sidebar tree, and breadcrumbs reflect the new hierarchy. Sub-subgraphs (e.g. nodes with subgraphs nested inside wave) continue to appear correctly under wave.

Addressed the feedback — the navigation tree now updates correctly when nodes with subgraphs are cut and pasted. **What changed:** - Added a `move(nodeId, newParentId)` function to `TreeStore` that reparents a tree node: removes it from its current parent (or root) and places it under the new parent, updating `parentIdByChildId`, the parent's `children` array, and rebuilding the tree. - In the paste logic (`NodeContextMenu.tsx`), after transferring nodes/edges between graphs, the code now iterates over the cut node IDs and calls `moveTreeNode` for any that have a tree entry (i.e. have a subgraph). Only nodes whose tree parent matches the source graph are reparented — nested tree nodes (subgraphs within the cut structure) keep their existing parent relationships intact. **Example from the review:** After cutting `wave` from `sky`'s subgraph and pasting into `apple`'s subgraph, `wave` now appears as a child of `apple` in the sidebar tree, and breadcrumbs reflect the new hierarchy. Sub-subgraphs (e.g. nodes with subgraphs nested inside `wave`) continue to appear correctly under `wave`.
tymurbaniak added 3 commits 2026-03-26 01:00:05 +00:00
- Add CutStore (Zustand) to store cut node IDs, node data, edges, and source graph ID
- Update graphToDot() to render cut nodes with dashed grey style for visual indication
- Add 'Cut' action to node context menu; 'Paste' action to empty-area context menu
- Cut logic: captures selected nodes (or right-clicked node if none selected) plus
  all nodes reachable by outgoing edges from those nodes; clears selection afterwards
- Paste logic: adds cut nodes/edges to target graph, removes them from source graph
  in GraphsStore; pasting on the same graph as the cut source cancels the cut
- Graph re-renders automatically when cutNodeIds changes via useCutStore subscription
- Clear cut store on file load for consistency
- Add E2E tests covering: Cut menu visibility, Paste menu visibility, cut styling,
  linked-node inclusion, cross-subgraph paste, source-graph cleanup, same-graph cancel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When nodes with subgraphs are cut and pasted to a different graph,
reparent their tree entries so the sidebar tree and breadcrumbs
reflect the new hierarchy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tymurbaniak force-pushed claude/issue-8 from f7f2938bb1 to d5107ac6d3 2026-03-26 01:00:05 +00:00 Compare
tymurbaniak merged commit 4e342dc776 into main 2026-03-26 01:00:42 +00:00
Sign in to join this conversation.