# 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. **Status:** ✅ Done — 13 modules, largest 394 lines. App store: 747 lines. ### 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 `