diff --git a/REFACTOR.md b/REFACTOR.md new file mode 100644 index 000000000..229238044 --- /dev/null +++ b/REFACTOR.md @@ -0,0 +1,160 @@ +# Refactoring Roadmap + +Comprehensive audit of vibe-coding leftovers and structural debt. + +## 🔴 Critical (Architectural Debt) + +### 1. `src/stores/editor.ts` — 2,505 lines, the God Store + +One closure captures ~30 mutable `let` variables and exposes ~80 methods covering: file I/O, undo, pen tool, layout, selection, export, import, clipboard, images, paging, zooming, autosave, file watching, component ops, text editing. + +**Refactoring:** Extract `createEditor()` to `packages/core/src/editor/` folder with domain modules sharing an `EditorContext` interface. App store becomes a thin Vue wrapper. See "Editor Folder Split" section below. + +**Status:** 🔧 In progress + +### 2. `src/composables/use-canvas-input.ts` — 1,501 lines, the Input Monolith + +Handles 8 drag types, touch/gesture, wheel zoom, double-click, hit testing, snap guides, auto-layout indicators, reparenting, and hover cursors — all in one function. + +**Refactoring:** Extract by concern: +- `use-drag-move.ts` — move with snap, reparent, auto-layout break +- `use-drag-resize.ts` — resize with aspect ratio +- `use-drag-rotate.ts` — rotation with snap +- `use-touch-input.ts` — touch/gesture/pinch +- `use-hit-test.ts` — hit testing (already pure functions) + +### 3. State is fully mutable to all consumers + +`editor.state` is a raw `shallowReactive` — every composable mutates it freely (`store.state.panX = ...`, `store.state.zoom = ...`). Zero uses of `readonly`/`shallowReadonly` anywhere. + +**Refactoring:** Expose `shallowReadonly(state)` as the public API, add explicit setter methods for mutations that need to happen from outside the store. + +## 🟡 Medium (Code Quality) + +### 4. Fire-and-forget async calls — 10+ unhandled + +Multiple `void asyncFn()` calls where rejections are silently swallowed: +- `void switchPage()` in `addPage`/`deletePage` +- `void reloadFromDisk()` in file watcher +- `void loadFontsForNodes()` in paste handler +- `saveFigFile`, `writeFile`, `reloadFromDisk` — no try/catch, silent data loss risk + +**Refactoring:** Wrap fire-and-forget calls in a `safeFire(promise, context)` helper that catches and shows a toast. Add try/catch to all async store methods. + +### 5. Module-level state in composables + +`use-chat.ts` declares all state (`providerID`, `apiKey`, `modelID`, etc.) at module scope outside the composable function. `use-menu.ts` calls `useFileDialog()` and `useEditorStore()` at module scope — technically incorrect for VueUse composables that expect `setup()` context. + +**Refactoring:** Move state inside the composable function or convert `use-chat.ts` to a proper store. Move `useFileDialog()` inside the exported function. + +### 6. Bounding-box computation duplicated ~10 times + +The `let minX = Infinity; ... Math.min/max` pattern appears in `editor.ts` (7×), `tools/read.ts`, `tools/vector.ts`, `clipboard.ts`, `renderer/overlays.ts`, `figma-api.ts`. + +**Refactoring:** Add a generic `computeBounds(items, getRect): Rect` to `geometry.ts`, replace all copies. + +### 7. "Node not found" guard repeated 44 times in tools + +Every tool function starts with `const node = figma.getNodeById(id); if (!node) return { error: ... }`. + +**Refactoring:** Add `requireNode(figma, id)` to `tools/schema.ts` that either returns the node or throws a typed error caught by the tool executor. + +### 8. Barrel import overuse — 58 consumers use `@open-pencil/core` vs 2 using subpaths + +Subpath exports (`/scene-graph`, `/tools`, `/renderer`, etc.) exist but are almost unused. The barrel `export * from './constants'` dumps 91 exports. + +**Refactoring:** Migrate app imports to use subpath exports. Improves tree-shaking and makes dependency intent explicit. + +### 9. `kiwi-serialize.ts` misplaced + +495-line file at the core root that imports exclusively from `./kiwi/*`. Belongs inside `kiwi/`. + +**Refactoring:** Move to `packages/core/src/kiwi/kiwi-serialize.ts`, update imports. + +### 10. `tools/stock-photo.ts` throws instead of returning `{ error }` + +All other tools return `{ error: string }` for AI consumers, but `stock-photo.ts` throws. + +**Refactoring:** Change to return `{ error }` like all other tools. + +## 🟢 Low Priority (Polish) + +### 11. Large Vue components (8 over 300 lines) + +| Lines | Component | Split into | +|-------|-----------|-----------| +| 553 | `VariablesDialog.vue` | `VariablesTable` + `VariableCell` + dialog wrapper | +| 537 | `LayoutSection.vue` | auto-layout vs absolute sections | +| 504 | `FillPicker.vue` | extract `GradientEditor` | +| 503 | `LayerTree.vue` | extract drag-to-reparent composable | +| 388 | `Toolbar.vue` | extract action menu sub-components | +| 382 | `StrokeSection.vue` | extract shared paint picker with FillSection | +| 337 | `NodeContextMenuContent.vue` | group into sub-menus | +| 314 | `ProviderSettings.vue` | extract per-provider form components | + +### 12. Icon import bloat + +`Toolbar.vue` (14 icon imports), `LayerTree.vue` (13), `MobileHud.vue` (5). Extract icon maps to shared utils. + +### 13. Unscoped `