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,435 @@
1
+ # Typed Dispatcher Design
2
+
3
+ Design exploration for tightening end-to-end typing of the command dispatcher
4
+ and cleaning up the loose `CommandContext.getActiveBuffer` surface.
5
+
6
+ ## Context
7
+
8
+ The PixelWeaver command system uses a single dispatcher singleton. Plugins
9
+ register commands via `api.addCommand<P>(name, definition)`, and any code can
10
+ fire them via `api.dispatch({ type, plugin, version, params })`. A prior
11
+ refactor (I-03 in `todo/.done/audit-implementation-plan.md`) made
12
+ `Command<P>` and `CommandDefinition<P>` generic on the params type, but the
13
+ two ends of the pipe are still untyped.
14
+
15
+ Two pain points remain:
16
+
17
+ - **Untyped `dispatch(type, params)`**: `PluginAPI.dispatch` takes
18
+ `Omit<Command, 'id' | 'timestamp'>` where `Command.params` is
19
+ `Record<string, unknown>`. The dispatcher looks the definition up in the
20
+ registry at runtime, and since the registry stores `CommandDefinition<any>`
21
+ there is no type link between the `type` string and the shape of `params`.
22
+ As a result every command `execute()` body starts with defensive casts
23
+ like `const color = params["color"] as string;` and every `api.dispatch`
24
+ call site spells out a loosely-typed object literal. If the caller forgets
25
+ a field, the mistake surfaces at runtime, not at compile time.
26
+
27
+ - **Loose `CommandContext.getActiveBuffer`**: `CommandContext` is declared as
28
+ `{ getActiveBuffer?: () => PixelBuffer | null; [key: string]: unknown }`.
29
+ The method is optional and the index signature makes the whole object a
30
+ typed bag, which forced earlier cast patterns and still forces every
31
+ caller to write `ctx.getActiveBuffer?.()` followed by a null check.
32
+ `getActiveBuffer` is called from ~44 sites across 27 files and is
33
+ effectively non-optional at runtime (it is wired up during bootstrap by
34
+ `canvas-init-plugin.ts`), yet the type says otherwise.
35
+
36
+ The goal is to make both sides tight enough that:
37
+
38
+ - The `type` string in `dispatch()` constrains the shape of `params`.
39
+ - Command `execute()` bodies receive statically-typed `params` and
40
+ `context` without local casts.
41
+ - `ctx.getActiveBuffer()` (or an equivalent) is non-optional, non-nullable
42
+ in the common case, and does not require a cast.
43
+
44
+ ## Approaches
45
+
46
+ ### Approach A -- Global command-type-to-params map (module augmentation)
47
+
48
+ A single ambient interface `CommandParamsMap` maps command type strings to
49
+ their params interfaces. Plugins declare their entries via TypeScript module
50
+ augmentation. The registry, `dispatch()`, and `addCommand()` all derive
51
+ `P` from the `type` string via an index lookup.
52
+
53
+ ```ts
54
+ // In core/command-registry-types.ts
55
+ declare global {
56
+ interface CommandParamsMap {
57
+ // populated via module augmentation
58
+ }
59
+ }
60
+ type CommandType = keyof CommandParamsMap;
61
+ type ParamsOf<T extends CommandType> = CommandParamsMap[T];
62
+
63
+ // In a plugin file
64
+ interface FloodFillParams { x: number; y: number; color: string }
65
+ declare global {
66
+ interface CommandParamsMap {
67
+ flood_fill: FloodFillParams;
68
+ }
69
+ }
70
+
71
+ api.addCommand('flood_fill', {
72
+ execute(params, ctx) {
73
+ const buffer = ctx.getBuffer(); // typed, non-optional
74
+ const { x, y, color } = params; // typed FloodFillParams
75
+ // ...
76
+ },
77
+ // ...
78
+ });
79
+
80
+ api.dispatch({
81
+ type: 'flood_fill',
82
+ plugin: 'builtin/fill',
83
+ version: '1.0.0',
84
+ params: { x: 4, y: 2, color: '#f00' }, // required keys enforced
85
+ });
86
+ ```
87
+
88
+ `PluginAPI` would be redefined so that `addCommand` and `dispatch` have
89
+ overloads keyed on `CommandType`:
90
+
91
+ ```ts
92
+ addCommand<T extends CommandType>(
93
+ name: T,
94
+ definition: CommandDefinition<ParamsOf<T>>,
95
+ ): void;
96
+
97
+ dispatch<T extends CommandType>(
98
+ cmd: { type: T; plugin: string; version: string; params: ParamsOf<T> },
99
+ ): void;
100
+ ```
101
+
102
+ `CommandContext` becomes a simple interface without an index signature.
103
+ `getActiveBuffer` is renamed to `getBuffer` and made non-optional,
104
+ returning `PixelBuffer` (no `| null`). A new `getBufferOrNull` can be
105
+ provided for the small number of call sites that legitimately want to
106
+ tolerate a missing buffer.
107
+
108
+ - **Pros**
109
+ - Single source of truth for command signatures; any code anywhere can
110
+ look up the params shape by type string.
111
+ - Callers get autocomplete on the `params` object from the `type` string.
112
+ - Works across plugin boundaries without explicit imports (important
113
+ because `ctx.api.dispatch` is called from tool handlers that do not
114
+ import the command that owns the type).
115
+ - Zero runtime cost -- all validation happens at the TypeScript layer.
116
+
117
+ - **Cons**
118
+ - Module augmentation is global and must be imported for the augmentation
119
+ to take effect. Forgetting the side-effect import on a plugin boundary
120
+ silently falls back to `never`/`unknown` for that type.
121
+ - Collision risk: two plugins cannot register the same command type
122
+ without TypeScript errors.
123
+ - `keyof CommandParamsMap` produces large union types that can slow down
124
+ IDE tooling if the command count grows (currently ~60 commands, which
125
+ is still comfortable).
126
+ - Requires rewriting every `execute`/`undo`/`describe` signature and
127
+ every `api.dispatch` call site. Heavy touch but mechanical.
128
+
129
+ - **Effort**: Medium-to-large. ~60 command definitions, ~25 dispatch call
130
+ sites, ~44 `getActiveBuffer` call sites, plus every test that mocks a
131
+ `CommandContext`. Each plugin file gains a params interface declaration.
132
+
133
+ ### Approach B -- Locally-typed command tokens (phantom-typed handles)
134
+
135
+ Instead of a global map, `api.addCommand` returns a typed token that
136
+ encodes the params type in its phantom type parameter. Dispatch happens
137
+ through the token, not through a raw string.
138
+
139
+ ```ts
140
+ // Token type carries the params shape as a phantom type parameter.
141
+ interface CommandToken<P> {
142
+ readonly type: string;
143
+ readonly plugin: string;
144
+ readonly version: string;
145
+ // phantom -- never instantiated
146
+ readonly __params?: (p: P) => void;
147
+ }
148
+
149
+ // addCommand returns a token
150
+ const floodFill = api.addCommand('flood_fill', {
151
+ execute(params: FloodFillParams, ctx) { /* ... */ },
152
+ undo(params, ctx, snap) { /* ... */ },
153
+ describe(params) { return `Flood fill at ${params.x},${params.y}`; },
154
+ });
155
+ // floodFill: CommandToken<FloodFillParams>
156
+
157
+ // Dispatch uses the token, not a raw string
158
+ api.dispatch(floodFill, { x: 4, y: 2, color: '#f00' });
159
+ ```
160
+
161
+ `CommandContext` is cleaned up the same way as Approach A
162
+ (non-optional `getBuffer()`). Raw-string dispatch remains available as an
163
+ untyped escape hatch for dynamic cases (e.g. shortcut routing, action log
164
+ replay).
165
+
166
+ - **Pros**
167
+ - Type inference is local -- no global map, no augmentation, no
168
+ side-effect imports.
169
+ - Each plugin owns the types for its own commands and exports the tokens
170
+ it wants other plugins to dispatch.
171
+ - Natural fit for factory-style plugins that generate commands
172
+ dynamically at registration time.
173
+ - Preserves the ability to have two internally-scoped commands with the
174
+ same name in different plugins without ambient-type collisions.
175
+
176
+ - **Cons**
177
+ - Requires cross-plugin imports to dispatch from one plugin's code into
178
+ another's command (e.g. tool handlers that fire the commands exposed
179
+ by the drawing-primitives plugin). Today these are decoupled through
180
+ string dispatch; moving to tokens creates new dependencies.
181
+ - Action log replay, shortcut routing, and menu contributions all work on
182
+ raw strings, so a second untyped path must be kept alive -- you get
183
+ two parallel APIs.
184
+ - Renaming a command becomes awkward because the string identifier is
185
+ still the canonical form for serialization but the token is the
186
+ canonical form for dispatch. Two sources of truth.
187
+ - The phantom-type trick is unfamiliar to most TS devs; readability
188
+ suffers slightly.
189
+
190
+ - **Effort**: Large. Touches every call site that currently uses string
191
+ dispatch AND introduces new plugin-to-plugin imports, which means the
192
+ plugin dependency graph must be updated. Likely spawns follow-up
193
+ refactors to avoid circular plugin references.
194
+
195
+ ### Approach C -- Discriminated union of command descriptors
196
+
197
+ Instead of a map, a single exported union type enumerates every command
198
+ descriptor. `addCommand` and `dispatch` use a generic that extracts
199
+ the matching member from the union.
200
+
201
+ ```ts
202
+ // In a central types file, one entry per command
203
+ export type CommandDescriptor =
204
+ | { type: 'flood_fill'; params: { x: number; y: number; color: string } }
205
+ | { type: 'draw_rect'; params: { x: number; y: number; w: number; h: number; color: string } }
206
+ | { type: 'blur'; params: { radius: number } }
207
+ // ...
208
+ ;
209
+
210
+ type ParamsFor<T extends CommandDescriptor['type']> =
211
+ Extract<CommandDescriptor, { type: T }>['params'];
212
+
213
+ api.addCommand('flood_fill', {
214
+ execute(params, ctx) {
215
+ // params inferred as { x: number; y: number; color: string }
216
+ const buffer = ctx.getBuffer();
217
+ // ...
218
+ },
219
+ // ...
220
+ });
221
+
222
+ api.dispatch({
223
+ type: 'flood_fill',
224
+ plugin: 'builtin/fill',
225
+ version: '1.0.0',
226
+ params: { x: 4, y: 2, color: '#f00' },
227
+ });
228
+ ```
229
+
230
+ `CommandContext` is tightened identically to Approach A.
231
+
232
+ - **Pros**
233
+ - All command signatures live in one file -- easy to review, easy to
234
+ grep, easy to detect naming collisions (TS errors immediately).
235
+ - Switch-exhaustiveness checks work on `cmd.type` throughout the app,
236
+ useful for action log replay and menu builders.
237
+ - No module augmentation gymnastics; works with a single explicit import.
238
+ - Discriminated unions are a mainstream TS idiom, familiar to all devs.
239
+
240
+ - **Cons**
241
+ - Central file becomes a bottleneck: every new command requires editing
242
+ a shared file that every plugin imports. This fights the plugin
243
+ architecture's intent that plugins own their own surface area.
244
+ - Scaling: extract over a large union is slower than an index lookup;
245
+ TS compilation time will grow faster than Approach A as command count
246
+ climbs.
247
+ - Third-party plugins cannot extend the union from outside the repo
248
+ without forking the union definition, which defeats the plugin model
249
+ the codebase otherwise leans into.
250
+
251
+ - **Effort**: Medium. Similar call-site churn to Approach A, plus one
252
+ mechanical pass to move params interfaces into the central union file.
253
+
254
+ ### Comparison
255
+
256
+ | Dimension | A: global map | B: phantom tokens | C: central union |
257
+ |------------------------------------|----------------------------|-------------------------------|-----------------------------|
258
+ | Where types live | Alongside each plugin | Alongside each plugin | One central file |
259
+ | Dispatch key | String | Token object | String |
260
+ | Requires plugin cross-imports | No | Yes (for cross-plugin calls) | Just the union module |
261
+ | Extensible by third-party plugins | Yes (augmentation) | Yes (export tokens) | No (union is closed) |
262
+ | Autocomplete on `type` string | Yes | n/a (token is the key) | Yes |
263
+ | Autocomplete on `params` | Yes | Yes | Yes |
264
+ | Supports dynamic string dispatch | Yes | Parallel untyped path | Yes |
265
+ | Works with action log replay | Yes (string-keyed) | Needs string fallback | Yes (string-keyed) |
266
+ | TS perf at ~60 commands | Good | Great (local inference) | Good |
267
+ | TS perf at ~500 commands | Acceptable (index lookup) | Great | Degrades (Extract on union) |
268
+ | Familiarity | Intermediate | Advanced | Beginner |
269
+ | Scales with plugin ecosystem | Good | Fair | Poor |
270
+ | Effort | Medium-to-large | Large | Medium |
271
+
272
+ All three approaches tighten `CommandContext` identically: drop the index
273
+ signature, rename `getActiveBuffer` -> `getBuffer`, make it non-optional,
274
+ return `PixelBuffer` (throw or assert on missing buffer, which cannot
275
+ happen in practice after `canvas-init-plugin` has run). Provide a separate
276
+ `tryGetBuffer()` for the small number of call sites that want to tolerate
277
+ its absence (if any turn out to be real).
278
+
279
+ ## Recommendation
280
+
281
+ **Approach A (global command-type-to-params map via module augmentation).**
282
+
283
+ Rationale:
284
+
285
+ - It is the only approach that matches the existing architecture where
286
+ plugins are discovered via `import.meta.glob` and communicate with each
287
+ other through string identifiers routed by the registry. String-keyed
288
+ dispatch is the codebase's status quo, and Approach A tightens the
289
+ types without changing that shape.
290
+ - Plugin cross-calls work without new imports. Today, for example,
291
+ `edit-commands.ts` fires `select_rect` via a string dispatch without
292
+ importing the selection plugin. Approach B would force that import and
293
+ create a dependency cycle; Approach A keeps the decoupling.
294
+ - Action log replay, shortcut routing, and menu contributions all
295
+ naturally stay string-keyed.
296
+ - Module augmentation has a known footgun (forgotten side-effect imports),
297
+ but this is mitigated by declaring all params interfaces in the same
298
+ file as `addCommand`, since registering the command and augmenting the
299
+ map live side by side. Existing lint rules can catch missing imports.
300
+ - Effort is on par with Approach C and substantially lower than Approach B.
301
+
302
+ The central union (Approach C) is second-best and would be acceptable if
303
+ we wanted a single grep-friendly catalog, but it scales poorly with
304
+ plugin count and fights the plugin model.
305
+
306
+ ## Migration Strategy
307
+
308
+ Incremental rollout, build stays green at every step.
309
+
310
+ - **Step 1: Add `CommandParamsMap` infrastructure (non-breaking).**
311
+ - Create `src/lib/core/command-types.ts` with an empty
312
+ `interface CommandParamsMap {}` in the global namespace and the
313
+ `CommandType` / `ParamsOf<T>` helper types.
314
+ - Leave existing `addCommand<P>` and `dispatch` signatures in place.
315
+ - No behavior change; build stays green.
316
+
317
+ - **Step 2: Introduce typed overloads alongside the existing ones.**
318
+ - Add new overloads of `addCommand` and `dispatch` on `PluginAPI` that
319
+ key on `CommandType`. Keep the existing generic overloads as a fallback
320
+ for any command type not yet registered in the map.
321
+ - Update `PluginAPI.dispatch` to prefer the typed overload when the
322
+ `type` is known.
323
+ - No plugin changes required yet; build stays green.
324
+
325
+ - **Step 3: Tighten `CommandContext` in a single commit.**
326
+ - Add a new non-optional `getBuffer(): PixelBuffer` method to
327
+ `CommandContext`, throwing if the dispatcher context has not been
328
+ initialized (it always is, in practice, by `canvas-init-plugin`).
329
+ - Keep the old `getActiveBuffer?: () => PixelBuffer | null` method on
330
+ the interface temporarily so existing call sites continue to compile.
331
+ - Wire both methods in `canvas-init-plugin.ts`.
332
+
333
+ - **Step 4: Migrate plugins one at a time.**
334
+ - For each plugin file, define the params interfaces inline, augment
335
+ `CommandParamsMap`, rewrite `execute`/`undo`/`describe` to use typed
336
+ params, and swap `ctx.getActiveBuffer?.()` for `ctx.getBuffer()`.
337
+ - Drop the `as Type` casts inside the bodies as they become redundant.
338
+ - One commit per plugin. Order by risk: start with the smallest
339
+ (e.g. `fill-tool.ts`), end with `drawing-primitives-plugin.ts` and
340
+ `selection-tool.ts`.
341
+ - Each commit should keep the tree fully buildable, since untouched
342
+ plugins still use the old index-signature path.
343
+
344
+ - **Step 5: Migrate dispatch call sites.**
345
+ - Once all commands are in the map, sweep for `api.dispatch({ type: ...})`
346
+ and make sure each call site benefits from the typed overload. No
347
+ functional change; this is mostly a verification pass that reveals
348
+ missing/extra params fields as TS errors.
349
+
350
+ - **Step 6: Remove legacy surface.**
351
+ - Drop `getActiveBuffer` from `CommandContext`; remove the index
352
+ signature from `CommandContext`.
353
+ - Drop the fallback generic overload on `addCommand`/`dispatch`.
354
+ - Update tests to construct `CommandContext` mocks via a helper that
355
+ implements `getBuffer()` directly.
356
+ - Bump the audit doc to reflect the new baseline.
357
+
358
+ At every step the project compiles, tests pass, and no runtime behavior
359
+ changes. The only commit where TS errors would appear is Step 6, and only
360
+ if a call site was missed in Step 4 or 5.
361
+
362
+ ## Files That Would Change
363
+
364
+ Core types and dispatcher:
365
+
366
+ - `src/lib/core/commands.ts` -- tighten `CommandContext`; drop the index
367
+ signature; add typed helpers.
368
+ - `src/lib/core/dispatcher.ts` -- update context storage type.
369
+ - `src/lib/core/plugin-api.ts` -- new typed overloads of `addCommand` and
370
+ `dispatch`; pass the typed context.
371
+ - `src/lib/core/plugin-types.ts` -- new `PluginAPI.addCommand`/`dispatch`
372
+ overloads keyed on `CommandType`.
373
+ - `src/lib/core/registries.svelte.ts` -- `commandRegistry` type updated
374
+ (still heterogeneous, but no longer needs an explicit `any`).
375
+ - `src/lib/core/command-types.ts` (new) -- declares `CommandParamsMap`,
376
+ `CommandType`, `ParamsOf<T>`.
377
+
378
+ Plugins (each gets a params interface and a `CommandParamsMap` augmentation):
379
+
380
+ - `plugins/builtin/drawing-primitives-plugin.ts`
381
+ - `plugins/builtin/fill-tool.ts`
382
+ - `plugins/builtin/advanced-fill-tool.ts`
383
+ - `plugins/builtin/selection-tool.ts`
384
+ - `plugins/builtin/noise-tool.ts`
385
+ - `plugins/builtin/dither-tool.ts`
386
+ - `plugins/builtin/gradient-tool.ts`
387
+ - `plugins/builtin/pattern-stamp-tool.ts`
388
+ - `plugins/builtin/circle-tool.ts`
389
+ - `plugins/builtin/diamond-tool.ts`
390
+ - `plugins/builtin/line-tool.ts`
391
+ - `plugins/builtin/rect-tool.ts`
392
+ - `plugins/builtin/make-stroke-tool.ts`
393
+ - `plugins/builtin/eyedropper-tool.ts`
394
+ - `plugins/builtin/effects/blur.ts`
395
+ - `plugins/builtin/effects/glow.ts`
396
+ - `plugins/builtin/effects/shadow.ts`
397
+ - `plugins/builtin/effects/outline.ts`
398
+ - `plugins/builtin/effects/sharpen.ts`
399
+ - `plugins/builtin/effects/scale.ts`
400
+ - `plugins/builtin/effects/rotate.ts`
401
+ - `plugins/builtin/effects/flip.ts`
402
+ - `plugins/builtin/effects/color-effects.ts`
403
+ - `plugins/builtin/drawing-utils.ts` -- helpers that read `ctx.getBuffer()`.
404
+
405
+ UI-level command plugins:
406
+
407
+ - `src/lib/ui/edit-commands.ts`
408
+ - `src/lib/ui/image-commands.ts`
409
+ - `src/lib/ui/file-commands.ts` (if present in the registration pass)
410
+ - `src/lib/history/history-commands.ts`
411
+
412
+ Canvas wiring:
413
+
414
+ - `src/lib/canvas/canvas-init-plugin.ts` -- provide `getBuffer()` in the
415
+ dispatcher context (plus a guarded error path instead of `| null`).
416
+
417
+ Tests:
418
+
419
+ - `plugins/builtin/tool-plugins.test.ts`
420
+ - `plugins/builtin/effects.test.ts`
421
+ - `plugins/builtin/gradient.test.ts`
422
+ - `plugins/builtin/noise-dither.test.ts`
423
+ - `plugins/builtin/selection.test.ts`
424
+ - Any other test that mocks `CommandContext` via `setContext({ getActiveBuffer: ... })`.
425
+
426
+ Categories of change:
427
+
428
+ | Category | Count (approx) | Change kind |
429
+ |----------------------------------|----------------|------------------------------------------------------|
430
+ | Core type files | 5 | Tighten types, add helpers |
431
+ | Plugin command files | ~24 | Add params interfaces + augmentation, drop casts |
432
+ | UI command files | ~4 | Same as plugin command files |
433
+ | Dispatch call sites | ~25 | Benefit from typed overload; fix any real mismatches |
434
+ | `getActiveBuffer` -> `getBuffer` | ~44 | Mechanical rename |
435
+ | Test mock files | ~5 | Update `CommandContext` stubs |