Files
lemonspace_app/docs/plans/2026-04-03-canvas-modularization.md

164 lines
7.7 KiB
Markdown

# Canvas Modularization Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Split `components/canvas/canvas.tsx` into clear, testable domain hooks while preserving all current canvas behavior and public APIs.
**Architecture:** Keep `components/canvas/canvas.tsx` as the composition root and extract the largest logic clusters into domain-specific hooks. Start with sync and reconciliation because they are the most complex and most reusable, then move connection and drop flows, and only then extract a presentational view if it still adds value.
**Tech Stack:** Next.js 16, React 19, Convex, React Flow, TypeScript
---
### Task 1: Lock in the current modularization seams
**Files:**
- Modify: `components/canvas/canvas.tsx`
- Reference: `components/canvas/CLAUDE.md`
- Reference: `docs/plans/2026-04-03-canvas-modularization-design.md`
**Step 1:** Annotate the major responsibility boundaries inside `components/canvas/canvas.tsx` by grouping the existing code into the future modules: sync engine, reconciliation, interactions, connections, drop, and render.
**Step 2:** List the exact state, refs, callbacks, and mutations owned by each future hook in a scratch note or temporary checklist before moving code.
**Step 3:** Identify cross-cutting refs that must stay shared at the orchestrator level, especially optimistic ID handoff state and React Flow instance access.
**Step 4:** Remove the scratch note once the extraction map is clear.
### Task 2: Extract pure helper logic from effect bodies first
**Files:**
- Modify: `components/canvas/canvas.tsx`
- Modify: `components/canvas/canvas-helpers.ts`
- Create: `components/canvas/canvas-flow-reconciliation-helpers.ts`
- Test: `components/canvas/__tests__/canvas-flow-reconciliation-helpers.test.ts`
**Step 1:** Move deterministic edge reconciliation helpers out of `useLayoutEffect` bodies into `components/canvas/canvas-flow-reconciliation-helpers.ts`.
**Step 2:** Move deterministic node reconciliation helpers out of `useLayoutEffect` bodies into `components/canvas/canvas-flow-reconciliation-helpers.ts`.
**Step 3:** Add tests for optimistic edge carry, endpoint remapping, and position pin cleanup.
**Step 4:** Update `components/canvas/canvas.tsx` to call the extracted helpers without changing behavior.
### Task 3: Extract the sync engine hook
**Files:**
- Create: `components/canvas/use-canvas-sync-engine.ts`
- Modify: `components/canvas/canvas.tsx`
- Reference: `lib/canvas-op-queue.ts`
- Reference: `lib/canvas-local-persistence.ts`
- Test: `components/canvas/__tests__/use-canvas-sync-engine.test.ts`
**Step 1:** Create `useCanvasSyncEngine` with the queue state, online state, and mutation wrappers that are currently centered around `components/canvas/canvas.tsx:258` and `components/canvas/canvas.tsx:984`.
**Step 2:** Move deferred optimistic-node handling into the new hook, including pending resize, pending data, and pending move-after-create behavior.
**Step 3:** Return a clear API from the hook for create, move, resize, update-data, remove-edge, batch-remove, and split-edge flows.
**Step 4:** Replace the local inline sync logic in `components/canvas/canvas.tsx` with the hook.
**Step 5:** Add focused tests for optimistic create handoff and deferred resize/update behavior.
### Task 4: Extract the flow reconciliation hook
**Files:**
- Create: `components/canvas/use-canvas-flow-reconciliation.ts`
- Modify: `components/canvas/canvas.tsx`
- Modify: `components/canvas/canvas-flow-reconciliation-helpers.ts`
- Test: `components/canvas/__tests__/use-canvas-flow-reconciliation.test.ts`
**Step 1:** Create `useCanvasFlowReconciliation` to own the current Convex-to-local `useLayoutEffect` synchronization for nodes and edges.
**Step 2:** Pass in only the data it needs: Convex nodes, Convex edges, storage URLs, theme mode, local setters, and shared optimistic refs.
**Step 3:** Keep drag-lock and resize-lock behavior unchanged while moving the effects.
**Step 4:** Add tests or targeted assertions for the drag-lock and optimistic carry behavior that are hardest to reason about from the UI.
### Task 5: Extract node interaction logic
**Files:**
- Create: `components/canvas/use-canvas-node-interactions.ts`
- Modify: `components/canvas/canvas.tsx`
- Reference: `components/canvas/canvas-node-change-helpers.ts`
**Step 1:** Move `onNodesChange`, edge intersection highlighting, drag start, drag, and drag stop into `useCanvasNodeInteractions`.
**Step 2:** Keep the returned API explicit: handlers plus any local highlight state helpers.
**Step 3:** Preserve all resize side effects and split-edge-on-drop behavior exactly.
**Step 4:** Manually verify drag, resize, and edge split interactions on the canvas page.
### Task 6: Extract connection flows
**Files:**
- Create: `components/canvas/use-canvas-connections.ts`
- Modify: `components/canvas/canvas.tsx`
- Reference: `components/canvas/canvas-reconnect.ts`
- Reference: `components/canvas/canvas-connection-drop-menu.tsx`
**Step 1:** Move `onConnect`, `onConnectEnd`, connection drop menu state, and `handleConnectionDropPick` into `useCanvasConnections`.
**Step 2:** Keep validation centralized around `validateCanvasConnection` and `validateCanvasConnectionByType`.
**Step 3:** Reuse the existing reconnect hook by adapting it through the new connection hook rather than rewriting reconnect behavior.
**Step 4:** Manually verify valid connection creation, invalid connection rejection, reconnect, and connection-drop node creation.
### Task 7: Extract drag-and-drop creation flows
**Files:**
- Create: `components/canvas/use-canvas-drop.ts`
- Modify: `components/canvas/canvas.tsx`
- Reference: `components/canvas/canvas-media-utils.ts`
**Step 1:** Move `onDragOver` and `onDrop` into `useCanvasDrop`.
**Step 2:** Keep support for both sidebar/browser JSON payloads and raw node type strings.
**Step 3:** Preserve file upload drop behavior, including image dimension lookup and upload error toasts.
**Step 4:** Manually verify node drop and image file drop flows.
### Task 8: Decide whether a presentational view extraction is still useful
**Files:**
- Create: `components/canvas/canvas-view.tsx`
- Modify: `components/canvas/canvas.tsx`
**Step 1:** Measure the remaining size and readability of `components/canvas/canvas.tsx` after hook extraction.
**Step 2:** If the file still mixes visual structure with too much prop assembly, move the JSX tree into `components/canvas/canvas-view.tsx`.
**Step 3:** Keep `canvas-view.tsx` presentational only; it should not own data-fetching, queue logic, or reconciliation effects.
**Step 4:** Skip this task entirely if the orchestrator is already clearly readable.
### Task 9: Verify behavior after each extraction phase
**Files:**
- Verify only
**Step 1:** Run `pnpm lint components/canvas/canvas.tsx components/canvas/*.ts components/canvas/*.tsx` or the project-equivalent lint command for touched files.
**Step 2:** Run `pnpm tsc --noEmit` or the project-equivalent type-check command.
**Step 3:** Run the targeted canvas tests added during the refactor.
**Step 4:** Manually verify these flows on the real canvas page: create node, drag node, resize node, connect nodes, reconnect edge, delete node, drop node from sidebar, and drop image file.
### Task 10: Finish with small, reviewable commits
**Files:**
- Commit only
**Step 1:** Commit after Task 3 with a message like `refactor(canvas): extract sync engine hook`.
**Step 2:** Commit after Task 4 with a message like `refactor(canvas): extract flow reconciliation hook`.
**Step 3:** Commit after Tasks 5 to 7 with messages scoped to interactions, connections, and drop handling.
**Step 4:** If Task 8 is done, commit it separately as `refactor(canvas): extract canvas view`.