pixelweaver 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/.env.development +1 -0
  2. package/.github/workflows/ci.yml +22 -0
  3. package/.github/workflows/publish.yml +18 -0
  4. package/.prettierignore +5 -0
  5. package/.prettierrc +16 -0
  6. package/.python-version +1 -0
  7. package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
  8. package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
  9. package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
  10. package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
  11. package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
  12. package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
  13. package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
  14. package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
  15. package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
  16. package/.rlsbl/bases/CHANGELOG.md +5 -0
  17. package/.rlsbl/bases/LICENSE +21 -0
  18. package/.rlsbl/changes/.validated +1 -0
  19. package/.rlsbl/changes/0.1.0.jsonl +85 -0
  20. package/.rlsbl/changes/0.1.0.md +13 -0
  21. package/.rlsbl/changes/unreleased.jsonl +0 -0
  22. package/.rlsbl/config.json +6 -0
  23. package/.rlsbl/hashes.json +14 -0
  24. package/.rlsbl/hooks/post-release.sh +8 -0
  25. package/.rlsbl/hooks/pre-checks.sh +5 -0
  26. package/.rlsbl/hooks/pre-release.sh +8 -0
  27. package/.rlsbl/lint/go.toml +17 -0
  28. package/.rlsbl/lint/npm.toml +19 -0
  29. package/.rlsbl/lint/python.toml +25 -0
  30. package/.rlsbl/releases/unreleased.toml +0 -0
  31. package/.rlsbl/releases/v0.1.0.toml +3 -0
  32. package/.rlsbl/version +1 -0
  33. package/.selfdoc/hashes/hashes.json +146 -0
  34. package/.strictcli/schema.json +227 -0
  35. package/CHANGELOG.md +17 -0
  36. package/CLAUDE.md +100 -0
  37. package/LICENSE +21 -0
  38. package/README.md +116 -0
  39. package/assets/icon.png +0 -0
  40. package/docs/_README.md +117 -0
  41. package/docs/cli-config.md +35 -0
  42. package/docs/cli-dev.md +21 -0
  43. package/docs/cli-diagnose.md +12 -0
  44. package/docs/cli-index.md +30 -0
  45. package/docs/cli-list.md +18 -0
  46. package/docs/cli-mcp.md +18 -0
  47. package/docs/cli-new.md +26 -0
  48. package/docs/cli-open.md +21 -0
  49. package/docs/cli-serve.md +21 -0
  50. package/docs/cli-stop.md +12 -0
  51. package/docs/gen-index.md +36 -0
  52. package/docs/index.md +13 -0
  53. package/docs/server-src-pixelweaver-__main__.md +12 -0
  54. package/docs/server-src-pixelweaver-autosave.md +12 -0
  55. package/docs/server-src-pixelweaver-bridge.md +12 -0
  56. package/docs/server-src-pixelweaver-cli.md +12 -0
  57. package/docs/server-src-pixelweaver-config.md +12 -0
  58. package/docs/server-src-pixelweaver-connections.md +12 -0
  59. package/docs/server-src-pixelweaver-main.md +12 -0
  60. package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
  61. package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
  62. package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
  63. package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
  64. package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
  65. package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
  66. package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
  67. package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
  68. package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
  69. package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
  70. package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
  71. package/docs/server-src-pixelweaver-mcp_server.md +12 -0
  72. package/docs/server-src-pixelweaver-protocol.md +12 -0
  73. package/docs/server-src-pixelweaver-state.md +12 -0
  74. package/docs/server-src-pixelweaver-storage.md +12 -0
  75. package/docs/server-src-pixelweaver-websocket.md +12 -0
  76. package/docs/server-src-pixelweaver.md +12 -0
  77. package/e2e/app-launch.test.ts +35 -0
  78. package/e2e/menus.test.ts +26 -0
  79. package/e2e/tools.test.ts +27 -0
  80. package/e2e/undo-redo.test.ts +11 -0
  81. package/eslint.config.js +62 -0
  82. package/index.html +13 -0
  83. package/package.json +48 -0
  84. package/playwright.config.ts +19 -0
  85. package/plugins/builtin/.gitkeep +0 -0
  86. package/plugins/builtin/advanced-fill-tool.ts +146 -0
  87. package/plugins/builtin/circle-tool.ts +186 -0
  88. package/plugins/builtin/diamond-tool.ts +182 -0
  89. package/plugins/builtin/dither-tool.ts +186 -0
  90. package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
  91. package/plugins/builtin/drawing-utils.test.ts +495 -0
  92. package/plugins/builtin/drawing-utils.ts +431 -0
  93. package/plugins/builtin/effects/blur.ts +97 -0
  94. package/plugins/builtin/effects/color-effects.ts +278 -0
  95. package/plugins/builtin/effects/flip.ts +83 -0
  96. package/plugins/builtin/effects/glow.ts +118 -0
  97. package/plugins/builtin/effects/outline.ts +110 -0
  98. package/plugins/builtin/effects/rotate.ts +357 -0
  99. package/plugins/builtin/effects/scale.ts +258 -0
  100. package/plugins/builtin/effects/shadow.ts +111 -0
  101. package/plugins/builtin/effects/sharpen.ts +102 -0
  102. package/plugins/builtin/effects.test.ts +715 -0
  103. package/plugins/builtin/eraser-tool.ts +23 -0
  104. package/plugins/builtin/eyedropper-tool.ts +83 -0
  105. package/plugins/builtin/fill-tool.ts +93 -0
  106. package/plugins/builtin/gradient-tool.ts +204 -0
  107. package/plugins/builtin/gradient.test.ts +142 -0
  108. package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
  109. package/plugins/builtin/importers/aseprite-parser.ts +497 -0
  110. package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
  111. package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
  112. package/plugins/builtin/line-tool.ts +267 -0
  113. package/plugins/builtin/make-stroke-tool.ts +271 -0
  114. package/plugins/builtin/noise-dither.test.ts +151 -0
  115. package/plugins/builtin/noise-tool.ts +131 -0
  116. package/plugins/builtin/pattern-stamp-tool.ts +162 -0
  117. package/plugins/builtin/pencil-tool.ts +25 -0
  118. package/plugins/builtin/rect-tool.ts +179 -0
  119. package/plugins/builtin/selection-tool.ts +388 -0
  120. package/plugins/builtin/selection.test.ts +195 -0
  121. package/plugins/builtin/tool-plugins.test.ts +529 -0
  122. package/public/favicon.svg +7 -0
  123. package/public/sw.js +91 -0
  124. package/pyproject.toml +49 -0
  125. package/scripts/eslint-wave-a-list.sh +24 -0
  126. package/scripts/eslint-wave-a-status.sh +25 -0
  127. package/scripts/fix-index-signature-access.py +127 -0
  128. package/scripts/fix-unchecked-index.py +128 -0
  129. package/scripts/fix-unchecked-vars.py +127 -0
  130. package/scripts/fix-wave-a-bangs.py +36 -0
  131. package/scripts/fix-wave-a-templates.py +108 -0
  132. package/scripts/fix-wave-b-void-expr.py +167 -0
  133. package/scripts/generate-command-params.py +540 -0
  134. package/scripts/migrate-single-frame-to-multi.py +171 -0
  135. package/scripts/smoke-test.sh +77 -0
  136. package/selfdoc.json +10 -0
  137. package/server/README.md +0 -0
  138. package/server/src/pixelweaver/__init__.py +1 -0
  139. package/server/src/pixelweaver/__main__.py +4 -0
  140. package/server/src/pixelweaver/autosave.py +114 -0
  141. package/server/src/pixelweaver/bridge.py +127 -0
  142. package/server/src/pixelweaver/cli.py +199 -0
  143. package/server/src/pixelweaver/config.py +24 -0
  144. package/server/src/pixelweaver/connections.py +54 -0
  145. package/server/src/pixelweaver/main.py +271 -0
  146. package/server/src/pixelweaver/mcp_bridge.py +189 -0
  147. package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
  148. package/server/src/pixelweaver/mcp_export_tools.py +291 -0
  149. package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
  150. package/server/src/pixelweaver/mcp_history_tools.py +106 -0
  151. package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
  152. package/server/src/pixelweaver/mcp_lock.py +37 -0
  153. package/server/src/pixelweaver/mcp_project_tools.py +302 -0
  154. package/server/src/pixelweaver/mcp_read_tools.py +163 -0
  155. package/server/src/pixelweaver/mcp_registry.py +247 -0
  156. package/server/src/pixelweaver/mcp_resources.py +312 -0
  157. package/server/src/pixelweaver/mcp_server.py +234 -0
  158. package/server/src/pixelweaver/protocol.py +219 -0
  159. package/server/src/pixelweaver/state.py +267 -0
  160. package/server/src/pixelweaver/storage.py +293 -0
  161. package/server/src/pixelweaver/websocket.py +282 -0
  162. package/server/tests/__init__.py +0 -0
  163. package/server/tests/conftest.py +17 -0
  164. package/server/tests/test_api.py +96 -0
  165. package/server/tests/test_bridge.py +161 -0
  166. package/server/tests/test_health.py +9 -0
  167. package/server/tests/test_integration.py +86 -0
  168. package/server/tests/test_mcp_bridge.py +293 -0
  169. package/server/tests/test_mcp_lock.py +34 -0
  170. package/server/tests/test_mcp_registry.py +679 -0
  171. package/server/tests/test_mcp_resources.py +648 -0
  172. package/server/tests/test_protocol.py +125 -0
  173. package/server/tests/test_state.py +87 -0
  174. package/server/tests/test_storage.py +306 -0
  175. package/server/tests/test_websocket.py +275 -0
  176. package/src/App.svelte +107 -0
  177. package/src/app.css +215 -0
  178. package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
  179. package/src/lib/animation/animation-commands.test.ts +228 -0
  180. package/src/lib/animation/animation-commands.ts +540 -0
  181. package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
  182. package/src/lib/animation/animation-preview.svelte.ts +151 -0
  183. package/src/lib/animation/clipboard.ts +134 -0
  184. package/src/lib/animation/frame-model.svelte.ts +437 -0
  185. package/src/lib/animation/frame-model.test.ts +314 -0
  186. package/src/lib/animation/frame-selection.svelte.ts +77 -0
  187. package/src/lib/animation/frame-tags.svelte.ts +238 -0
  188. package/src/lib/animation/frame-tags.test.ts +136 -0
  189. package/src/lib/animation/import.test.ts +141 -0
  190. package/src/lib/animation/import.ts +112 -0
  191. package/src/lib/animation/spritesheet-export.test.ts +239 -0
  192. package/src/lib/animation/spritesheet-export.ts +314 -0
  193. package/src/lib/canvas/CanvasViewport.svelte +216 -0
  194. package/src/lib/canvas/canvas-init-plugin.ts +178 -0
  195. package/src/lib/canvas/canvas-renderer.ts +408 -0
  196. package/src/lib/canvas/canvas-state.svelte.ts +232 -0
  197. package/src/lib/canvas/canvas-state.test.ts +139 -0
  198. package/src/lib/canvas/input-handler.ts +221 -0
  199. package/src/lib/canvas/input-plugin.ts +150 -0
  200. package/src/lib/canvas/onion-skin.ts +94 -0
  201. package/src/lib/canvas/pixel-buffer.test.ts +249 -0
  202. package/src/lib/canvas/pixel-buffer.ts +151 -0
  203. package/src/lib/canvas/render-state.svelte.ts +18 -0
  204. package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
  205. package/src/lib/canvas/tile-mode.test.ts +53 -0
  206. package/src/lib/canvas/tile-mode.ts +92 -0
  207. package/src/lib/canvas/viewport-utils.ts +24 -0
  208. package/src/lib/canvas/zoom-utils.ts +31 -0
  209. package/src/lib/color/.gitkeep +0 -0
  210. package/src/lib/color/color-commands.ts +87 -0
  211. package/src/lib/color/color-state.svelte.ts +98 -0
  212. package/src/lib/color/color-state.test.ts +91 -0
  213. package/src/lib/color/color-utils.test.ts +220 -0
  214. package/src/lib/color/color-utils.ts +243 -0
  215. package/src/lib/color/palette-state.svelte.ts +127 -0
  216. package/src/lib/color/palette-state.test.ts +154 -0
  217. package/src/lib/color/palette.ts +79 -0
  218. package/src/lib/core/bootstrap.ts +66 -0
  219. package/src/lib/core/command-params.generated.ts +1549 -0
  220. package/src/lib/core/command-params.ts +20 -0
  221. package/src/lib/core/command-runner.ts +79 -0
  222. package/src/lib/core/commands.ts +134 -0
  223. package/src/lib/core/dispatcher.test.ts +548 -0
  224. package/src/lib/core/dispatcher.ts +361 -0
  225. package/src/lib/core/index.test.ts +7 -0
  226. package/src/lib/core/notification-state.svelte.ts +119 -0
  227. package/src/lib/core/plugin-api.ts +210 -0
  228. package/src/lib/core/plugin-discovery.test.ts +53 -0
  229. package/src/lib/core/plugin-discovery.ts +65 -0
  230. package/src/lib/core/plugin-loader.test.ts +159 -0
  231. package/src/lib/core/plugin-loader.ts +240 -0
  232. package/src/lib/core/plugin-types.ts +286 -0
  233. package/src/lib/core/registries.svelte.ts +74 -0
  234. package/src/lib/core/tool-options-state.svelte.ts +61 -0
  235. package/src/lib/dock/DockLayout.svelte +375 -0
  236. package/src/lib/dock/TabAddMenu.svelte +90 -0
  237. package/src/lib/dock/dock-persistence.ts +46 -0
  238. package/src/lib/dock/dock-plugin.ts +49 -0
  239. package/src/lib/dock/dock-presets.ts +156 -0
  240. package/src/lib/dock/dock-state.svelte.ts +77 -0
  241. package/src/lib/dock/dock-types.ts +2 -0
  242. package/src/lib/dock/dockview-theme.css +226 -0
  243. package/src/lib/dock/dockview-theme.ts +17 -0
  244. package/src/lib/dock/header-action-renderer.ts +77 -0
  245. package/src/lib/edit/clipboard-state.ts +34 -0
  246. package/src/lib/export/download.ts +201 -0
  247. package/src/lib/export/png-metadata.ts +181 -0
  248. package/src/lib/history/ActionLogPanel.svelte +418 -0
  249. package/src/lib/history/action-log-panel-plugin.ts +24 -0
  250. package/src/lib/history/action-log.svelte.ts +172 -0
  251. package/src/lib/history/action-log.test.ts +168 -0
  252. package/src/lib/history/history-commands.ts +403 -0
  253. package/src/lib/history/macros.svelte.ts +320 -0
  254. package/src/lib/history/macros.test.ts +224 -0
  255. package/src/lib/history/replay-engine.test.ts +241 -0
  256. package/src/lib/history/replay-engine.ts +149 -0
  257. package/src/lib/history/spec-format.test.ts +250 -0
  258. package/src/lib/history/spec-format.ts +210 -0
  259. package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
  260. package/src/lib/iso/iso-math.test.ts +77 -0
  261. package/src/lib/iso/iso-math.ts +77 -0
  262. package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
  263. package/src/lib/iso/seam-checker.test.ts +126 -0
  264. package/src/lib/iso/seam-checker.ts +93 -0
  265. package/src/lib/iso/tessellation.ts +67 -0
  266. package/src/lib/layers/compositor.test.ts +193 -0
  267. package/src/lib/layers/compositor.ts +175 -0
  268. package/src/lib/layers/layer-commands.test.ts +263 -0
  269. package/src/lib/layers/layer-commands.ts +429 -0
  270. package/src/lib/layers/layer-tree.svelte.ts +516 -0
  271. package/src/lib/layers/layer-tree.test.ts +383 -0
  272. package/src/lib/layers/layer-types.ts +56 -0
  273. package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
  274. package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
  275. package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
  276. package/src/lib/leveleditor/depth-sort.test.ts +70 -0
  277. package/src/lib/leveleditor/depth-sort.ts +39 -0
  278. package/src/lib/leveleditor/level-editor-commands.ts +353 -0
  279. package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
  280. package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
  281. package/src/lib/leveleditor/map-state.svelte.ts +372 -0
  282. package/src/lib/leveleditor/map-state.test.ts +243 -0
  283. package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
  284. package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
  285. package/src/lib/leveleditor/tiled-export.test.ts +144 -0
  286. package/src/lib/leveleditor/tiled-export.ts +374 -0
  287. package/src/lib/pywebview.d.ts +15 -0
  288. package/src/lib/recovery/.gitkeep +0 -0
  289. package/src/lib/recovery/idb-store.ts +118 -0
  290. package/src/lib/recovery/recovery-manager.test.ts +321 -0
  291. package/src/lib/recovery/recovery-manager.ts +115 -0
  292. package/src/lib/recovery/recovery-plugin.ts +55 -0
  293. package/src/lib/recovery/recovery-state.svelte.ts +21 -0
  294. package/src/lib/recovery/sw-plugin.ts +18 -0
  295. package/src/lib/recovery/sw-register.ts +17 -0
  296. package/src/lib/save/directory-format.ts +42 -0
  297. package/src/lib/save/project-snapshot.ts +139 -0
  298. package/src/lib/save/recent-projects.ts +56 -0
  299. package/src/lib/save/save-state.svelte.ts +35 -0
  300. package/src/lib/save/storage.ts +167 -0
  301. package/src/lib/save/zip-format.ts +45 -0
  302. package/src/lib/shortcuts/.gitkeep +0 -0
  303. package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
  304. package/src/lib/shortcuts/default-bindings.ts +61 -0
  305. package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
  306. package/src/lib/shortcuts/shortcut-init.ts +380 -0
  307. package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
  308. package/src/lib/shortcuts/shortcut-manager.ts +281 -0
  309. package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
  310. package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
  311. package/src/lib/sync/patch-applicator.ts +300 -0
  312. package/src/lib/sync/patch-types.ts +65 -0
  313. package/src/lib/sync/project-manager.ts +108 -0
  314. package/src/lib/sync/sync-init.ts +152 -0
  315. package/src/lib/sync/sync-plugin.ts +19 -0
  316. package/src/lib/sync/sync-state.svelte.ts +56 -0
  317. package/src/lib/sync/ws-client.test.ts +604 -0
  318. package/src/lib/sync/ws-client.ts +574 -0
  319. package/src/lib/tools/.gitkeep +0 -0
  320. package/src/lib/ui/.gitkeep +0 -0
  321. package/src/lib/ui/AboutDialog.svelte +113 -0
  322. package/src/lib/ui/ColorPicker.svelte +761 -0
  323. package/src/lib/ui/ContextMenu.svelte +216 -0
  324. package/src/lib/ui/ExportDialog.svelte +747 -0
  325. package/src/lib/ui/FrameStrip.svelte +854 -0
  326. package/src/lib/ui/LayerPanel.svelte +810 -0
  327. package/src/lib/ui/MenuBar.svelte +590 -0
  328. package/src/lib/ui/NewProjectDialog.svelte +803 -0
  329. package/src/lib/ui/PluginManagerPanel.svelte +475 -0
  330. package/src/lib/ui/PromptDialog.svelte +252 -0
  331. package/src/lib/ui/RecoveryDialog.svelte +295 -0
  332. package/src/lib/ui/ResizeDialog.svelte +416 -0
  333. package/src/lib/ui/StatusBar.svelte +145 -0
  334. package/src/lib/ui/ToolbarPanel.svelte +488 -0
  335. package/src/lib/ui/animation-menu-commands.ts +194 -0
  336. package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
  337. package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
  338. package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
  339. package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
  340. package/src/lib/ui/dialog-state.svelte.ts +70 -0
  341. package/src/lib/ui/edit-commands.ts +271 -0
  342. package/src/lib/ui/file-commands.ts +275 -0
  343. package/src/lib/ui/file-open.ts +99 -0
  344. package/src/lib/ui/help-commands.ts +93 -0
  345. package/src/lib/ui/image-commands.ts +181 -0
  346. package/src/lib/ui/layer-menu-commands.ts +420 -0
  347. package/src/lib/ui/menu-builder.ts +224 -0
  348. package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
  349. package/src/lib/ui/notifications/notification-plugin.ts +29 -0
  350. package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
  351. package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
  352. package/src/lib/ui/plugin-state.svelte.ts +62 -0
  353. package/src/lib/ui/select-commands.ts +75 -0
  354. package/src/lib/ui/theme-plugin.ts +18 -0
  355. package/src/lib/ui/theme.svelte.ts +45 -0
  356. package/src/lib/ui/theme.test.ts +51 -0
  357. package/src/lib/ui/toolbar-config.ts +90 -0
  358. package/src/lib/ui/toolbar-plugin.ts +39 -0
  359. package/src/lib/ui/view-commands.ts +629 -0
  360. package/src/lib/variants/BisectionExportDialog.svelte +500 -0
  361. package/src/lib/variants/VariantPanel.svelte +822 -0
  362. package/src/lib/variants/bisection-export.test.ts +113 -0
  363. package/src/lib/variants/bisection-export.ts +148 -0
  364. package/src/lib/variants/palette-extraction.test.ts +111 -0
  365. package/src/lib/variants/palette-extraction.ts +84 -0
  366. package/src/lib/variants/palette-interpolation.test.ts +113 -0
  367. package/src/lib/variants/palette-interpolation.ts +87 -0
  368. package/src/lib/variants/palette-swap.test.ts +101 -0
  369. package/src/lib/variants/palette-swap.ts +114 -0
  370. package/src/lib/variants/variant-commands.ts +594 -0
  371. package/src/lib/variants/variant-panel-plugin.ts +27 -0
  372. package/src/lib/variants/variant-randomizer.ts +101 -0
  373. package/src/lib/variants/variant-state.svelte.ts +166 -0
  374. package/src/lib/variants/variant-state.test.ts +138 -0
  375. package/src/main.ts +14 -0
  376. package/src/vite-env.d.ts +3 -0
  377. package/svelte.config.js +2 -0
  378. package/todo/.done/audit-design-decisions.md +812 -0
  379. package/todo/.done/audit-implementation-plan.md +1235 -0
  380. package/todo/.done/happy-path-polish.md +177 -0
  381. package/todo/.done/pixelweaver-full-build.md +937 -0
  382. package/todo/.done/server-multi-frame-design.md +405 -0
  383. package/todo/.done/typed-dispatcher-design.md +435 -0
  384. package/todo/.done/unified-toolbar-and-action-system.md +323 -0
  385. package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
  386. package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
  387. package/todo/comprehensive-audit.md +1085 -0
  388. package/tsconfig.app.json +26 -0
  389. package/tsconfig.json +7 -0
  390. package/tsconfig.node.json +20 -0
  391. package/uv.lock +1167 -0
  392. package/vite.config.ts +32 -0
@@ -0,0 +1,812 @@
1
+ # PixelWeaver Codebase Audit -- Architectural Design Decisions
2
+
3
+ This document captures 10 design decisions arising from the PixelWeaver codebase audit.
4
+ Each decision includes full context, the problem, the chosen solution, exact files to change,
5
+ dependencies on other decisions, and effort estimates. The recommended implementation order
6
+ at the bottom accounts for all inter-decision dependencies.
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ - [Phase 1: DRY Factories (Isolated, No Dependencies)](#phase-1-dry-factories)
13
+ - [Decision #24: Create makeStrokeTool Factory](#decision-24-create-makestroketool-factory)
14
+ - [Decision #25: Create makeSnapshotUndo Factory](#decision-25-create-makesnapshotundo-factory)
15
+ - [Phase 2: Type System Foundation](#phase-2-type-system-foundation)
16
+ - [Decision #20: Generic Command\<P\> + Separate Snapshot from Params](#decision-20-generic-commandp--separate-snapshot-from-params)
17
+ - [Phase 3: Undoable Flag](#phase-3-undoable-flag)
18
+ - [Decision #1: Add undoable Flag to CommandDefinition](#decision-1-add-undoable-flag-to-commanddefinition)
19
+ - [Phase 4: Dialog / UI Work](#phase-4-dialog--ui-work)
20
+ - [Decision #22: Native \<dialog\> Element for Modals](#decision-22-native-dialog-element-for-modals)
21
+ - [Decision #26: Build Reusable PromptDialog Component](#decision-26-build-reusable-promptdialog-component)
22
+ - [Phase 5: God File Split](#phase-5-god-file-split)
23
+ - [Decision #14: Full 8-File Domain Split of menu-commands-plugin.ts](#decision-14-full-8-file-domain-split-of-menu-commands-plugints)
24
+ - [Phase 6: Plugin API Expansion](#phase-6-plugin-api-expansion)
25
+ - [Decision #12: Add getSelection() to PluginAPI](#decision-12-add-getselection-to-pluginapi)
26
+ - [Decision #13: Add Mutator Methods to PluginAPI Incrementally](#decision-13-add-mutator-methods-to-pluginapi-incrementally)
27
+ - [Phase 7: Strict TypeScript Flags](#phase-7-strict-typescript-flags)
28
+ - [Decision #19: Enable All 5 Strict TypeScript Flags](#decision-19-enable-all-5-strict-typescript-flags)
29
+ - [Implementation Order Summary](#implementation-order-summary)
30
+ - [Effort Summary](#effort-summary)
31
+
32
+ ---
33
+
34
+ ## Phase 1: DRY Factories
35
+
36
+ These two decisions are fully isolated from every other decision and from each other.
37
+ They can be implemented in parallel on day one.
38
+
39
+ ---
40
+
41
+ ### Decision #24: Create makeStrokeTool Factory
42
+
43
+ **Effort:** Small
44
+
45
+ **Depends on:** Nothing
46
+
47
+ #### Problem
48
+
49
+ `pencil-tool.ts` and `eraser-tool.ts` are structurally identical (~100 lines each).
50
+ They differ only in:
51
+
52
+ - Command name / description verb
53
+ - Pixel color resolution (foreground color vs. transparent)
54
+ - Icon
55
+ - Toolbar order
56
+
57
+ This is a textbook DRY violation. Any behavioral change to stroke tools must be
58
+ applied twice, and they can silently drift.
59
+
60
+ #### Solution
61
+
62
+ Create a `makeStrokeTool(config)` factory function that produces an entire `PluginModule`.
63
+
64
+ **Config interface:**
65
+
66
+ ```typescript
67
+ interface StrokeToolConfig {
68
+ pluginName: string;
69
+ commandName: string;
70
+ toolId: string;
71
+ icon: any;
72
+ toolbarOrder: number;
73
+ describeVerb: string;
74
+ resolveColor: (ctx: ToolContext) => { r: number; g: number; b: number; a: number };
75
+ }
76
+ ```
77
+
78
+ **Color resolution per tool:**
79
+
80
+ | Tool | resolveColor |
81
+ |------|-------------|
82
+ | Pencil | `(ctx) => hexToRgba(ctx.color)` |
83
+ | Eraser | `() => ({ r: 0, g: 0, b: 0, a: 0 })` |
84
+
85
+ Note: `pattern-stamp` stays separate -- it has a fundamentally different data flow
86
+ and should not be forced into this factory.
87
+
88
+ #### Files to Change
89
+
90
+ | File | Action |
91
+ |------|--------|
92
+ | `plugins/builtin/make-stroke-tool.ts` | **Create** -- the factory function |
93
+ | `plugins/builtin/pencil-tool.ts` | **Reduce** to ~15 lines calling the factory |
94
+ | `plugins/builtin/eraser-tool.ts` | **Reduce** to ~15 lines calling the factory |
95
+
96
+ #### Verification
97
+
98
+ - Draw with pencil: pixels appear in foreground color
99
+ - Draw with eraser: pixels become transparent
100
+ - Undo/redo works for both tools
101
+ - Both appear in toolbar at correct positions with correct icons
102
+
103
+ ---
104
+
105
+ ### Decision #25: Create makeSnapshotUndo Factory
106
+
107
+ **Effort:** Small (mechanical find-and-replace)
108
+
109
+ **Depends on:** Ideally done after #20 (if snapshot is separated from params, the undo
110
+ handler signature changes). Can be done before #20 with the current signature and
111
+ updated during #20.
112
+
113
+ #### Problem
114
+
115
+ 32 out of 33 undo handlers across 23 files are byte-for-byte identical:
116
+
117
+ 1. Get the active buffer from context
118
+ 2. Bail if buffer is missing
119
+ 3. Apply the snapshot pixels from params
120
+
121
+ This is massive duplication. A bug fix to the undo pattern would need 32 edits.
122
+
123
+ #### Solution
124
+
125
+ Add a `makeSnapshotUndo()` function to `drawing-utils.ts` that returns a reusable
126
+ undo handler closure.
127
+
128
+ ```typescript
129
+ export function makeSnapshotUndo(): UndoHandler {
130
+ return (params, ctx) => {
131
+ const buffer = ctx.getActiveBuffer?.();
132
+ if (!buffer) return;
133
+ applyPixels(buffer, params._snapshot);
134
+ };
135
+ }
136
+ ```
137
+
138
+ The single exception (`move_selection`) stays manual because it has custom undo logic
139
+ beyond pixel restoration.
140
+
141
+ #### Files to Change
142
+
143
+ | File | Action | Occurrences |
144
+ |------|--------|-------------|
145
+ | `plugins/builtin/drawing-utils.ts` | **Add** `makeSnapshotUndo()` | 1 (definition) |
146
+ | `plugins/builtin/drawing-primitives-plugin.ts` | **Replace** inline undo handlers | 6 |
147
+ | `plugins/builtin/color-effects.ts` | **Replace** inline undo handlers | 5 |
148
+ | ~9 other tool/effect files in `plugins/builtin/` | **Replace** inline undo handlers | ~21 |
149
+
150
+ Total: 32 replacements across 15 files, plus 1 definition.
151
+
152
+ #### Verification
153
+
154
+ - Undo/redo works for all drawing tools and effects
155
+ - `move_selection` undo still works (should be untouched)
156
+ - No regressions in any tool's undo behavior
157
+
158
+ ---
159
+
160
+ ## Phase 2: Type System Foundation
161
+
162
+ This is the most impactful single decision. It eliminates ~263 unsafe casts and makes
163
+ the entire command/dispatch system type-safe. Must come before #1 and #19.
164
+
165
+ ---
166
+
167
+ ### Decision #20: Generic Command\<P\> + Separate Snapshot from Params
168
+
169
+ **Effort:** Large
170
+
171
+ **Depends on:** Nothing (foundational change, should be done early)
172
+
173
+ #### Problem (Three Sub-Problems)
174
+
175
+ **Sub-problem A -- Untyped params:** `Command.params` is `Record<string, unknown>`,
176
+ forcing every plugin to write `params.x as number`, `params.color as string`, etc.
177
+ This produces ~170 field casts across 23 plugin files. Any typo or type mismatch is
178
+ invisible to the compiler.
179
+
180
+ **Sub-problem B -- Snapshot smuggled into params:** The `_snapshot` field is shoved
181
+ into `params` as a mutable side-channel by `execute()`, then read back in `undo()`.
182
+ This makes params non-immutable and conflates invocation data with undo state.
183
+ 21 files use this convention.
184
+
185
+ **Sub-problem C -- Untyped CommandContext:** `CommandContext` lacks a
186
+ `getActiveBuffer` definition, causing 69 casts of the form
187
+ `(ctx as { getActiveBuffer?: ... })` across plugin files.
188
+
189
+ #### Solution
190
+
191
+ **Part A -- Generic Command\<P\>:**
192
+
193
+ - Make `Command<P>` and `CommandDefinition<P>` generic over a params type `P`
194
+ - Each plugin declares a typed interface for its params:
195
+
196
+ ```typescript
197
+ interface DrawRectParams {
198
+ x: number;
199
+ y: number;
200
+ width: number;
201
+ height: number;
202
+ color: string;
203
+ }
204
+ ```
205
+
206
+ - The dispatcher and registries store `Command<any>` at the storage boundary
207
+ (type erasure at the collection level is expected and acceptable)
208
+ - ~25 param interfaces to write; eliminates ~170 field casts
209
+
210
+ **Part B -- Separate snapshot from params:**
211
+
212
+ - Add a dedicated `snapshot: unknown` slot on the undo stack entry
213
+ - `execute()` return value (or a callback/setter) provides the snapshot data:
214
+
215
+ ```typescript
216
+ execute(params: P, ctx: CommandContext): SnapshotData | void;
217
+ undo(params: P, ctx: CommandContext, snapshot: SnapshotData): void;
218
+ ```
219
+
220
+ - Params become truly immutable invocation data
221
+ - The `_snapshot` convention disappears from all 21 files
222
+
223
+ **Part C -- Typed CommandContext:**
224
+
225
+ - Add `getActiveBuffer?: () => PixelBuffer | null` to the `CommandContext` interface
226
+ - Eliminates 69 casts of `(ctx as { getActiveBuffer?: ... })`
227
+
228
+ #### Files to Change
229
+
230
+ | File | Action |
231
+ |------|--------|
232
+ | `src/lib/core/commands.ts` | Make `Command<P>` and `CommandDefinition<P>` generic; add typed `CommandContext` with `getActiveBuffer` |
233
+ | `src/lib/core/dispatcher.ts` | Accept snapshot return from `execute()`, store on `UndoEntry`, pass to `undo()` |
234
+ | `src/lib/core/registries.svelte.ts` | Store `CommandDefinition<any>` in the registry |
235
+ | All 23 files in `plugins/builtin/` | Define param interfaces, remove `as` casts, remove `_snapshot` from params |
236
+ | `src/lib/ui/menu-commands-plugin.ts` (or split successors) | Remove `ctx` casts |
237
+ | `plugins/builtin/drawing-utils.ts` | Update `snapshotPixels`/`applyPixels` if the snapshot interface changes |
238
+
239
+ #### Cast Elimination Summary
240
+
241
+ | Cast Category | Count | Fix |
242
+ |--------------|-------|-----|
243
+ | `params.field as Type` | ~170 | Typed param interfaces |
244
+ | `(ctx as { getActiveBuffer? })` | 69 | Typed CommandContext |
245
+ | `_snapshot` mutations on params | ~24 | Dedicated snapshot slot |
246
+ | **Total** | **~263** | |
247
+
248
+ #### Verification
249
+
250
+ - `tsc --noEmit` passes with zero errors
251
+ - All tools, effects, and commands work as before
252
+ - Undo/redo functions correctly for all commands
253
+ - No `as unknown` or `as any` casts remain in plugin files (beyond the storage boundary)
254
+
255
+ ---
256
+
257
+ ## Phase 3: Undoable Flag
258
+
259
+ Requires typed commands from Phase 2 for proper `dispatch()` call construction.
260
+
261
+ ---
262
+
263
+ ### Decision #1: Add undoable Flag to CommandDefinition
264
+
265
+ **Effort:** Medium
266
+
267
+ **Depends on:** #20 (Generic Command\<P\>) should ideally come first since the
268
+ dispatch call sites need proper `Command` construction. Can be done independently
269
+ with empty params/context as a stopgap.
270
+
271
+ #### Problem
272
+
273
+ All 4 UI entry points (menu bar, toolbar, context menu, command palette) call
274
+ `cmd.execute({}, {})` directly, bypassing the dispatcher. For UI-only commands
275
+ (zoom, theme, dialogs) this is fine and desirable. But destructive commands
276
+ (flatten_image, resize_canvas, merge_down, cut, paste) triggered from menus
277
+ are non-undoable because they never pass through the undo stack.
278
+
279
+ #### Solution
280
+
281
+ Add an `undoable?: boolean` field to `CommandDefinition`. UI entry points check the
282
+ flag at invocation time:
283
+
284
+ - `undoable: true` -- route through `dispatch()` (goes onto the undo stack)
285
+ - `undoable: false` or `undefined` -- call `execute()` directly (current behavior)
286
+
287
+ **Commands that need `undoable: true`** (~15 total):
288
+
289
+ - `flatten_image`
290
+ - `resize_canvas`
291
+ - `crop_to_selection`
292
+ - `merge_down`
293
+ - `cut`
294
+ - `paste`
295
+ - `select_all`
296
+ - `invert_selection`
297
+ - `select_by_color`
298
+ - Any other destructive or data-mutating command triggered from UI
299
+
300
+ **Commands that must NOT be marked undoable:**
301
+
302
+ - `undo` and `redo` (they call `undoLast`/`redoLast` directly -- marking them
303
+ undoable would create an infinite recursion)
304
+ - All view/zoom commands (non-destructive, no undo needed)
305
+ - Dialog-opening commands (no data mutation)
306
+
307
+ #### Files to Change
308
+
309
+ | File | Action |
310
+ |------|--------|
311
+ | `src/lib/core/commands.ts` | Add `undoable?: boolean` to `CommandDefinition` |
312
+ | `src/lib/ui/menu-builder.ts` | Check `undoable` flag; route through `dispatch()` if true |
313
+ | `src/lib/ui/ToolbarPanel.svelte` | Same routing logic |
314
+ | `src/lib/dock/TabAddMenu.svelte` | Same routing logic |
315
+ | `src/lib/ui/ContextMenu.svelte` | Same routing logic |
316
+ | `src/lib/ui/command-palette/command-palette-state.svelte.ts` | Same routing logic |
317
+ | `src/lib/ui/menu-commands-plugin.ts` | Mark ~15 destructive commands as `undoable: true` |
318
+
319
+ #### Verification
320
+
321
+ - Trigger `flatten_image` from the menu: it should appear on the undo stack
322
+ - Press Ctrl+Z: the flatten is undone
323
+ - Trigger `zoom_in` from the menu: it should NOT appear on the undo stack
324
+ - `undo`/`redo` commands still work as direct calls
325
+
326
+ ---
327
+
328
+ ## Phase 4: Dialog / UI Work
329
+
330
+ Independent of the command system. Can be done in parallel with Phase 2/3 if
331
+ different developers are available.
332
+
333
+ ---
334
+
335
+ ### Decision #22: Native \<dialog\> Element for Modals
336
+
337
+ **Effort:** Medium (restyling `::backdrop`, testing in Tauri WebView)
338
+
339
+ **Depends on:** Nothing
340
+
341
+ #### Problem
342
+
343
+ All 3 modal dialogs (NewProject, Export, About) plus CommandPalette lack:
344
+
345
+ - Focus traps (Tab key escapes to elements behind the overlay)
346
+ - `role="dialog"` attribute
347
+ - `aria-modal="true"` attribute
348
+ - Proper Escape key handling (AboutDialog has none at all)
349
+
350
+ These are implemented as `<div class="modal-overlay">` with manual event handlers.
351
+
352
+ #### Solution
353
+
354
+ Replace `<div class="modal-overlay">` with the native `<dialog>` element using
355
+ `.showModal()`. The browser provides all of the following for free:
356
+
357
+ - Focus trap (Tab stays within the dialog)
358
+ - Escape key closes the dialog
359
+ - `::backdrop` pseudo-element for the overlay
360
+ - `role="dialog"` and `aria-modal="true"` automatically
361
+ - Top-layer rendering (no z-index wars)
362
+
363
+ This is ideal for Tauri's Chromium WebView which has full `<dialog>` support.
364
+
365
+ #### Files to Change
366
+
367
+ | File | Action |
368
+ |------|--------|
369
+ | `src/lib/ui/NewProjectDialog.svelte` | Replace `<div class="modal-overlay">` with `<dialog>`, restyle `::backdrop`, remove manual Escape handler, remove svelte-ignore comments |
370
+ | `src/lib/ui/ExportDialog.svelte` | Same transformation |
371
+ | `src/lib/ui/AboutDialog.svelte` | Same transformation; add Escape handling (currently missing entirely) |
372
+ | `src/lib/ui/command-palette/CommandPalette.svelte` | Same pattern |
373
+ | `src/lib/ui/dialog-state.svelte.ts` | May need to store dialog element refs for `.showModal()` / `.close()` |
374
+
375
+ #### Key Implementation Notes
376
+
377
+ - Use `dialog::backdrop` for overlay styling (replaces `.modal-overlay` CSS)
378
+ - The `close` event on `<dialog>` fires on Escape; use it for cleanup
379
+ - Call `.showModal()` in a Svelte `$effect` when the dialog state becomes open
380
+ - Call `.close()` when the state becomes closed
381
+ - Test in Tauri WebView specifically -- Chromium's `<dialog>` works but verify
382
+ backdrop click behavior
383
+
384
+ #### Verification
385
+
386
+ - Tab key stays within each dialog (focus trap works)
387
+ - Escape closes each dialog
388
+ - Backdrop click closes each dialog (if desired behavior)
389
+ - Screen readers announce the dialogs correctly
390
+ - Dialogs render correctly in Tauri desktop build
391
+
392
+ ---
393
+
394
+ ### Decision #26: Build Reusable PromptDialog Component
395
+
396
+ **Effort:** Small-Medium
397
+
398
+ **Depends on:** #22 (native \<dialog\>) -- the PromptDialog should use `<dialog>`
399
+ from the start
400
+
401
+ #### Problem
402
+
403
+ Two `prompt()` calls exist in `menu-commands-plugin.ts`:
404
+
405
+ 1. Resize canvas (asks for new dimensions)
406
+ 2. Set frame duration (asks for milliseconds)
407
+
408
+ `window.prompt()` has critical platform issues:
409
+
410
+ - Fails silently on macOS in Tauri (returns null without showing)
411
+ - Unreliable on Linux in Tauri
412
+ - Ugly, non-themeable, blocks the main thread
413
+
414
+ #### Solution
415
+
416
+ Create a generic `PromptDialog.svelte` component with:
417
+
418
+ - Title string
419
+ - Message / label string
420
+ - Default value
421
+ - Validation callback (returns error string or null)
422
+ - onConfirm / onCancel callbacks
423
+ - Uses native `<dialog>` per Decision #22
424
+
425
+ Wire through `dialogState` so any command can open a prompt:
426
+
427
+ ```typescript
428
+ dialogState.openPrompt({
429
+ title: 'Resize Canvas',
430
+ message: 'Enter new width:',
431
+ defaultValue: '64',
432
+ validate: (v) => isNaN(Number(v)) ? 'Must be a number' : null,
433
+ onConfirm: (value) => { /* use value */ },
434
+ });
435
+ ```
436
+
437
+ #### Files to Change
438
+
439
+ | File | Action |
440
+ |------|--------|
441
+ | `src/lib/ui/PromptDialog.svelte` | **Create** -- generic single-input modal using native `<dialog>` |
442
+ | `src/lib/ui/dialog-state.svelte.ts` | Add prompt dialog state: open/close, config object, callback storage |
443
+ | `src/App.svelte` | Render `<PromptDialog>` when prompt state is open |
444
+ | `src/lib/ui/menu-commands-plugin.ts` (or split successors) | Replace `prompt()` calls with `dialogState.openPrompt()` |
445
+
446
+ #### Verification
447
+
448
+ - Resize canvas command opens a themed dialog, not a browser prompt
449
+ - Entering a valid number resizes the canvas
450
+ - Entering invalid input shows a validation error
451
+ - Pressing Escape or Cancel closes without action
452
+ - Works on macOS Tauri, Linux Tauri, and web browser
453
+
454
+ ---
455
+
456
+ ## Phase 5: God File Split
457
+
458
+ Easier after Phase 3 (#1, undoable flag) and Phase 4 (#26, PromptDialog replaces
459
+ `prompt()` calls). This is the largest single decision by line count but is mostly
460
+ mechanical.
461
+
462
+ ---
463
+
464
+ ### Decision #14: Full 8-File Domain Split of menu-commands-plugin.ts
465
+
466
+ **Effort:** Large (but mostly mechanical cut-and-paste)
467
+
468
+ **Depends on:**
469
+ - #1 (undoable flag) -- the split files need to mark commands correctly
470
+ - #26 (PromptDialog) -- should come first to eliminate `prompt()` calls during the split
471
+
472
+ #### Problem
473
+
474
+ `menu-commands-plugin.ts` is 1333 lines containing:
475
+
476
+ - 42 commands
477
+ - 55 menu items
478
+ - 9 toolbar items
479
+ - Duplicated zoom logic (also in `input-handler.ts` and `CanvasViewport.svelte`)
480
+ - Clipboard state management
481
+ - DOM queries for viewport center
482
+ - `prompt()` calls
483
+
484
+ This is a maintenance bottleneck. Any change to one domain (e.g., animation) requires
485
+ navigating a 1300-line file. Merge conflicts are frequent because every feature
486
+ touches this file.
487
+
488
+ #### Solution
489
+
490
+ Split into 8 domain files + 3 shared utilities. Delete the original file entirely.
491
+
492
+ **New domain files** (each is a `PluginModule` auto-discovered by `bootstrap.ts`
493
+ via the existing `import.meta.glob` pattern):
494
+
495
+ | File | Contents | ~Lines |
496
+ |------|----------|--------|
497
+ | `src/lib/ui/file-commands.ts` | `new_project_dialog`, `open_file_dialog`, `save_project`, `export_dialog` | 80 |
498
+ | `src/lib/ui/edit-commands.ts` | `undo`, `redo`, `cut`, `copy`, `paste`, `select_all` + deselect menu item | 150 |
499
+ | `src/lib/ui/image-commands.ts` | `resize_canvas`, `crop_to_selection`, `flatten_image`, `canvas_size` | 120 |
500
+ | `src/lib/ui/layer-menu-commands.ts` | `merge_down`, `move_layer_up`, `move_layer_down` + 3 menu-only items | 100 |
501
+ | `src/lib/ui/animation-menu-commands.ts` | `play_animation`, `set_frame_duration_dialog` + 3 menu-only items | 80 |
502
+ | `src/lib/ui/select-commands.ts` | `invert_selection`, `select_by_color` + shared menu items | 60 |
503
+ | `src/lib/ui/view-commands.ts` | All 17 view commands + 9 toolbar items | 350 |
504
+ | `src/lib/ui/help-commands.ts` | `about`, `keyboard_shortcuts`, `report_bug` | 70 |
505
+
506
+ Note: Files use `-menu-commands` suffix where they would collide with existing
507
+ domain command files (e.g., `layer-menu-commands.ts` avoids colliding with any
508
+ existing `layer-commands.ts`).
509
+
510
+ **Shared utilities to extract** (eliminates duplication beyond the god file):
511
+
512
+ | File | Contents | Deduplication Benefit |
513
+ |------|----------|---------------------|
514
+ | `src/lib/canvas/zoom-utils.ts` | `ZOOM_STEPS`, `zoomStepUp()`, `zoomStepDown()`, `DEFAULT_ZOOM` | Eliminates triplication across `input-handler.ts`, `CanvasViewport.svelte`, and the god file |
515
+ | `src/lib/canvas/viewport-utils.ts` | `viewportCenter()` function | Replaces `document.querySelector('.canvas-viewport')` DOM queries |
516
+ | `src/lib/edit/clipboard-state.ts` | `clipboardBuffer` type and reactive state | Isolates clipboard concern |
517
+
518
+ **The original `src/lib/ui/menu-commands-plugin.ts` is deleted entirely.**
519
+
520
+ #### Migration Strategy
521
+
522
+ 1. Create the 3 shared utilities first
523
+ 2. Create the 8 domain files one at a time, moving commands and menu items
524
+ 3. After each file is created, verify `tsc --noEmit` passes and the commands work
525
+ 4. Delete `menu-commands-plugin.ts` only after all commands are migrated
526
+ 5. Verify `bootstrap.ts` discovers all new plugin modules
527
+
528
+ #### Files to Change
529
+
530
+ | File | Action |
531
+ |------|--------|
532
+ | `src/lib/canvas/zoom-utils.ts` | **Create** -- shared zoom constants and functions |
533
+ | `src/lib/canvas/viewport-utils.ts` | **Create** -- viewport center calculation |
534
+ | `src/lib/edit/clipboard-state.ts` | **Create** -- clipboard buffer state |
535
+ | `src/lib/ui/file-commands.ts` | **Create** |
536
+ | `src/lib/ui/edit-commands.ts` | **Create** |
537
+ | `src/lib/ui/image-commands.ts` | **Create** |
538
+ | `src/lib/ui/layer-menu-commands.ts` | **Create** |
539
+ | `src/lib/ui/animation-menu-commands.ts` | **Create** |
540
+ | `src/lib/ui/select-commands.ts` | **Create** |
541
+ | `src/lib/ui/view-commands.ts` | **Create** |
542
+ | `src/lib/ui/help-commands.ts` | **Create** |
543
+ | `src/lib/ui/menu-commands-plugin.ts` | **Delete** |
544
+ | `src/lib/canvas/input-handler.ts` | **Update** -- import from `zoom-utils.ts` instead of local constants |
545
+ | `src/lib/ui/CanvasViewport.svelte` | **Update** -- import from `zoom-utils.ts` instead of local constants |
546
+
547
+ #### Verification
548
+
549
+ - All 42 commands still appear in the command palette
550
+ - All 55 menu items still appear in correct menus
551
+ - All 9 toolbar items still appear in correct toolbars
552
+ - Zoom from keyboard, mouse wheel, and menu all use the same step logic
553
+ - `tsc --noEmit` passes
554
+ - `bootstrap.ts` logs discovery of all 8 new plugin modules
555
+
556
+ ---
557
+
558
+ ## Phase 6: Plugin API Expansion
559
+
560
+ Best done after the god file split (#14) since `menu-commands-plugin.ts` will have
561
+ been reorganized into smaller files.
562
+
563
+ ---
564
+
565
+ ### Decision #12: Add getSelection() to PluginAPI
566
+
567
+ **Effort:** Small
568
+
569
+ **Depends on:** #14 (god file split) should happen first since `menu-commands-plugin.ts`
570
+ will be reorganized
571
+
572
+ #### Problem
573
+
574
+ `menu-commands-plugin.ts` directly imports selection state (`hasSelection`,
575
+ `getSelectedPixels`, `selectRect`, `deselect`) from
576
+ `plugins/builtin/selection-tool.ts`. This breaks the plugin boundary -- one plugin
577
+ reaches into another plugin's internal module.
578
+
579
+ #### Solution
580
+
581
+ Add a `getSelection()` method to `PluginAPI` returning a read-only selection interface:
582
+
583
+ ```typescript
584
+ interface SelectionView {
585
+ hasSelection(): boolean;
586
+ getSelectedPixels(): ReadonlySet<string>;
587
+ }
588
+
589
+ // On PluginAPI:
590
+ getSelection(): SelectionView;
591
+ ```
592
+
593
+ For mutations (selecting, deselecting), replace direct function calls with
594
+ `dispatch()` calls to the existing `select_rect` and `deselect` commands. This
595
+ keeps the plugin boundary intact: reads go through the API, writes go through
596
+ the command system.
597
+
598
+ #### Files to Change
599
+
600
+ | File | Action |
601
+ |------|--------|
602
+ | `src/lib/core/plugin-types.ts` | Add `getSelection(): SelectionView` to `PluginAPI` interface |
603
+ | `src/lib/core/plugin-api.ts` | Implement `getSelection()` by importing from `selection-tool.ts` internally |
604
+ | `src/lib/ui/menu-commands-plugin.ts` (or split successors) | Replace direct imports with `api.getSelection()` reads and `api.dispatch()` mutations |
605
+ | `plugins/builtin/selection-tool.ts` | No changes needed (exports remain for internal use within that plugin) |
606
+
607
+ #### Verification
608
+
609
+ - `cut` command still works (reads selection via API)
610
+ - `crop_to_selection` still works
611
+ - `select_all` and `deselect` still work (via dispatch)
612
+ - No direct imports of `selection-tool.ts` remain in UI command files
613
+
614
+ ---
615
+
616
+ ### Decision #13: Add Mutator Methods to PluginAPI Incrementally
617
+
618
+ **Effort:** Medium-Large (but each step is independently shippable)
619
+
620
+ **Depends on:** Nothing (can be done incrementally at any time)
621
+
622
+ #### Problem
623
+
624
+ All 3 importers bypass the `PluginAPI` entirely to set canvas size, add layers/frames,
625
+ and write pixel data. The API currently only has read-only getters that return `unknown`.
626
+ Approximately 15 new methods are needed to let importers work through the API.
627
+
628
+ #### Solution
629
+
630
+ Add mutator methods in 6 incremental steps. Each step is independently shippable
631
+ and testable.
632
+
633
+ **Step 1 -- Canvas:**
634
+
635
+ | Method | Signature |
636
+ |--------|-----------|
637
+ | `setCanvasSize` | `(width: number, height: number) => void` |
638
+
639
+ **Step 2 -- Layers:**
640
+
641
+ | Method | Signature |
642
+ |--------|-----------|
643
+ | `addLayer` | `(name: string, opts?: LayerOptions) => LayerId` |
644
+ | `addGroup` | `(name: string, opts?: GroupOptions) => GroupId` |
645
+ | `setLayerVisibility` | `(id: LayerId, visible: boolean) => void` |
646
+ | `setLayerOpacity` | `(id: LayerId, opacity: number) => void` |
647
+ | `resetLayers` | `() => void` |
648
+
649
+ **Step 3 -- Frames:**
650
+
651
+ | Method | Signature |
652
+ |--------|-----------|
653
+ | `addFrame` | `(opts?: FrameOptions) => FrameIndex` |
654
+ | `getFrames` | `() => ReadonlyArray<FrameInfo>` |
655
+ | `setFrameDuration` | `(index: number, ms: number) => void` |
656
+ | `setCurrentFrame` | `(index: number) => void` |
657
+ | `setGlobalFps` | `(fps: number) => void` |
658
+ | `resetFrames` | `() => void` |
659
+
660
+ **Step 4 -- Pixel Data:**
661
+
662
+ | Method | Signature |
663
+ |--------|-----------|
664
+ | `createPixelBuffer` | `(width: number, height: number) => PixelBuffer` |
665
+ | Pixel data setters | (exact API depends on PixelBuffer interface) |
666
+
667
+ **Step 5 -- Palette:**
668
+
669
+ | Method | Signature |
670
+ |--------|-----------|
671
+ | `setProjectPalette` | `(palette: Color[]) => void` |
672
+
673
+ **Step 6 -- Composite:**
674
+
675
+ | Method | Signature |
676
+ |--------|-----------|
677
+ | `resetProject` | `() => void` (clears layers + frames + canvas to defaults) |
678
+
679
+ After each step, migrate one importer to use the new API methods. All implementations
680
+ are thin wrappers around existing module functions.
681
+
682
+ #### Files to Change
683
+
684
+ | File | Action |
685
+ |------|--------|
686
+ | `src/lib/core/plugin-types.ts` | Expand `PluginAPI` interface with each step's methods |
687
+ | `src/lib/core/plugin-api.ts` | Implement methods as thin wrappers around existing module functions |
688
+ | `plugins/builtin/importers/aseprite-importer-plugin.ts` | Migrate to API calls |
689
+ | `plugins/builtin/importers/piskel-importer-plugin.ts` | Migrate to API calls |
690
+ | `plugins/builtin/importers/sky-spec-plugin.ts` | Migrate to API calls |
691
+
692
+ #### Verification (per step)
693
+
694
+ - After each step: `tsc --noEmit` passes
695
+ - After migrating each importer: import a test file, verify layers/frames/pixels are correct
696
+ - After all migrations: no direct imports of internal state modules remain in importer files
697
+
698
+ ---
699
+
700
+ ## Phase 7: Strict TypeScript Flags
701
+
702
+ Last phase. Benefits from all prior type improvements (especially #20). Touches
703
+ nearly every file in the codebase.
704
+
705
+ ---
706
+
707
+ ### Decision #19: Enable All 5 Strict TypeScript Flags
708
+
709
+ **Effort:** Large (multi-hour, but can be done incrementally flag by flag)
710
+
711
+ **Depends on:** #20 (Generic Command\<P\>) since the command params type changes
712
+ will interact with index access patterns
713
+
714
+ #### Problem
715
+
716
+ `tsconfig.app.json` does not enable several strict flags that would catch real bugs
717
+ at compile time. The current codebase has latent type safety issues that are invisible
718
+ to the compiler.
719
+
720
+ #### Solution
721
+
722
+ Enable all 5 flags in `tsconfig.app.json`, in order of ascending blast radius:
723
+
724
+ | Order | Flag | Current Errors | Fix Strategy | Estimated Time |
725
+ |-------|------|---------------|-------------|----------------|
726
+ | 1 | `noFallthroughCasesInSwitch` | 0 | Enable immediately, no fixes needed | 1 min |
727
+ | 2 | `noUnusedLocals` + `noUnusedParameters` | 7 | Delete dead imports and unused variables | 10 min |
728
+ | 3 | `exactOptionalPropertyTypes` | 11 | Narrow types before passing, or change the target type to accept `undefined` explicitly | 30-60 min |
729
+ | 4 | `noUncheckedIndexedAccess` | 470 | 257 in tests (add `!` assertions where values are known to exist), 213 in prod (case-by-case null checks). Surfaces real latent bugs. | 2-3 hours |
730
+ | 5 | `noPropertyAccessFromIndexSignature` | 698 | Force bracket notation on index signature access. Significant overlap with `noUncheckedIndexedAccess` fixes. | 2-3 hours |
731
+
732
+ **Total: ~470 errors in 54+ files for `noUncheckedIndexedAccess`, ~698 errors in 44+
733
+ files for `noPropertyAccessFromIndexSignature`.**
734
+
735
+ #### Recommended Approach
736
+
737
+ Enable one flag at a time. After each flag:
738
+
739
+ 1. Add the flag to `tsconfig.app.json`
740
+ 2. Run `tsc --noEmit` to get the error list
741
+ 3. Fix all errors
742
+ 4. Commit
743
+ 5. Move to the next flag
744
+
745
+ This prevents a massive all-at-once PR and lets each flag's fixes be reviewed
746
+ independently.
747
+
748
+ #### Files to Change
749
+
750
+ | File | Action |
751
+ |------|--------|
752
+ | `tsconfig.app.json` | Add flags incrementally |
753
+ | 54+ files across the codebase | Fix `noUncheckedIndexedAccess` errors |
754
+ | 44+ files across the codebase | Fix `noPropertyAccessFromIndexSignature` errors |
755
+ | ~7 files | Fix unused locals/params |
756
+ | ~11 files | Fix `exactOptionalPropertyTypes` errors |
757
+
758
+ #### Important Notes
759
+
760
+ - `noUncheckedIndexedAccess` will surface real latent bugs (array access without
761
+ bounds checking, map access without existence checking). These are worth fixing
762
+ properly, not just suppressing with `!`.
763
+ - In test files, `!` assertions are acceptable since the test data is controlled.
764
+ - In production code, prefer proper null checks or early returns.
765
+
766
+ #### Verification
767
+
768
+ - `tsc --noEmit` passes with all 5 flags enabled
769
+ - No runtime regressions (the fixes should be compile-time only)
770
+ - No `as any` or `@ts-ignore` comments added to suppress errors
771
+
772
+ ---
773
+
774
+ ## Implementation Order Summary
775
+
776
+ | Phase | Decisions | Key Rationale | Parallel? |
777
+ |-------|-----------|---------------|-----------|
778
+ | 1 | #24 (makeStrokeTool) + #25 (makeSnapshotUndo) | DRY factories, fully isolated, no dependencies | Yes, both in parallel |
779
+ | 2 | #20 (Generic Command\<P\> + snapshot + CommandContext) | Type system foundation -- all later phases benefit | Single item |
780
+ | 3 | #1 (undoable flag) | Needs typed commands from #20 for proper dispatch | Single item |
781
+ | 4 | #22 (native \<dialog\>) + #26 (PromptDialog) | Dialog/UI work, independent of command system | Yes, #22 first, then #26 |
782
+ | 5 | #14 (god file split) | Easier after #1 (undoable flag) and #26 (PromptDialog) | Single item |
783
+ | 6 | #12 (getSelection API) + #13 (mutator methods) | Plugin API expansion, benefits from split files | Yes, both in parallel |
784
+ | 7 | #19 (strict TS flags) | Last -- touches everything, benefits from all prior type improvements | Single item, incremental |
785
+
786
+ **Dependency graph** (read as "X must come before Y"):
787
+
788
+ - #20 must come before #1 (undoable flag needs typed Command construction)
789
+ - #20 should come before #19 (strict flags interact with command param types)
790
+ - #22 must come before #26 (PromptDialog uses native \<dialog\>)
791
+ - #1 should come before #14 (split files need undoable markings)
792
+ - #26 should come before #14 (split eliminates prompt() calls)
793
+ - #14 should come before #12 (split files are the consumers of getSelection API)
794
+ - #25 may need updating after #20 (if snapshot signature changes)
795
+
796
+ ---
797
+
798
+ ## Effort Summary
799
+
800
+ | Decision | Effort | Approximate Duration |
801
+ |----------|--------|---------------------|
802
+ | #24 makeStrokeTool | Small | 1-2 hours |
803
+ | #25 makeSnapshotUndo | Small | 1-2 hours |
804
+ | #20 Generic Command\<P\> | Large | 6-10 hours |
805
+ | #1 Undoable flag | Medium | 3-4 hours |
806
+ | #22 Native \<dialog\> | Medium | 3-4 hours |
807
+ | #26 PromptDialog | Small-Medium | 2-3 hours |
808
+ | #14 God file split | Large | 6-8 hours |
809
+ | #12 getSelection API | Small | 1-2 hours |
810
+ | #13 Mutator methods | Medium-Large | 6-8 hours |
811
+ | #19 Strict TS flags | Large | 6-8 hours |
812
+ | **Total** | | **~35-51 hours** |