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,23 @@
1
+ /**
2
+ * Eraser Tool Plugin -- sets pixels to transparent (0,0,0,0).
3
+ *
4
+ * Registers:
5
+ * - Command: `erase_pixels` (tier: 'frame')
6
+ * - Tool: `eraser`
7
+ *
8
+ * Thin wrapper around makeStrokeTool; always writes fully transparent pixels.
9
+ */
10
+
11
+ import { makeStrokeTool } from './make-stroke-tool.js';
12
+ import EraserIcon from '~icons/lucide/eraser';
13
+
14
+ export const eraserToolPlugin = makeStrokeTool({
15
+ pluginName: 'builtin/eraser',
16
+ commandName: 'erase_pixels',
17
+ toolId: 'eraser',
18
+ icon: EraserIcon,
19
+ toolbarOrder: 20,
20
+ toolbarGroup: 'draw',
21
+ describeVerb: 'Erased',
22
+ resolveColor: () => ({ r: 0, g: 0, b: 0, a: 0 }),
23
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Eyedropper Tool Plugin -- picks color from canvas pixels.
3
+ *
4
+ * Registers:
5
+ * - Tool: `eyedropper`
6
+ *
7
+ * Does NOT register its own command. Instead, it dispatches `set_foreground_color`
8
+ * (assumed to be registered by the color commands plugin). If that command is not
9
+ * registered, it falls back to a no-op with a console warning.
10
+ *
11
+ * Reads the pixel color under the pointer from the composited render buffer
12
+ * and sets it as the foreground color. Supports drag-to-sample via onPointerMove.
13
+ */
14
+
15
+ import type { PluginModule } from '../../src/lib/core/plugin-loader.js';
16
+ import type { ToolContext } from '../../src/lib/core/plugin-types.js';
17
+ import { getRenderBuffer } from '../../src/lib/canvas/render-state.svelte.js';
18
+ import { rgbToHex } from '../../src/lib/color/color-utils.js';
19
+ import EyedropperIcon from '~icons/lucide/pipette';
20
+
21
+ /**
22
+ * Sample the composited pixel at (canvasX, canvasY) and dispatch
23
+ * set_foreground_color. Skips fully transparent pixels (nothing to pick).
24
+ */
25
+ function sampleAndDispatch(ctx: ToolContext): void {
26
+ const buffer = getRenderBuffer();
27
+ if (!buffer) return;
28
+
29
+ const x = Math.floor(ctx.canvasX);
30
+ const y = Math.floor(ctx.canvasY);
31
+
32
+ if (!buffer.inBounds(x, y)) return;
33
+
34
+ const [r, g, b, a] = buffer.getPixel(x, y);
35
+
36
+ // Fully transparent pixel -- nothing meaningful to sample
37
+ if (a === 0) return;
38
+
39
+ const hex = rgbToHex(r, g, b);
40
+ ctx.api.dispatch({
41
+ type: 'set_foreground_color',
42
+ plugin: 'builtin/eyedropper',
43
+ version: '1.0.0',
44
+ params: { color: hex },
45
+ });
46
+ }
47
+
48
+ export const eyedropperToolPlugin: PluginModule = {
49
+ name: 'builtin/eyedropper',
50
+ version: '1.0.0',
51
+ dependencies: [],
52
+ register(api) {
53
+ api.addToolbarItem('toolbar:drawing-tools:eyedropper', {
54
+ toolbarId: 'drawing-tools',
55
+ kind: 'tool',
56
+ targetId: 'eyedropper',
57
+ group: 'color',
58
+ order: 10,
59
+ });
60
+
61
+ let dragging = false;
62
+
63
+ api.addTool('eyedropper', {
64
+ icon: EyedropperIcon,
65
+ cursor: 'crosshair',
66
+
67
+ onPointerDown(_e, ctx) {
68
+ dragging = true;
69
+ sampleAndDispatch(ctx);
70
+ },
71
+
72
+ onPointerMove(_e, ctx) {
73
+ // Drag-to-sample: continuously pick color while pointer is held down
74
+ if (!dragging) return;
75
+ sampleAndDispatch(ctx);
76
+ },
77
+
78
+ onPointerUp() {
79
+ dragging = false;
80
+ },
81
+ });
82
+ },
83
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Fill Bucket Tool Plugin -- flood fills contiguous same-color regions.
3
+ *
4
+ * Registers:
5
+ * - Command: `flood_fill` (tier: 'frame')
6
+ * - Tool: `fill`
7
+ *
8
+ * On click, performs a BFS flood fill from the clicked pixel, collecting all
9
+ * 4-connected pixels with the same color, then sets them to the foreground color.
10
+ * The entire operation is dispatched as a single undoable command.
11
+ */
12
+
13
+ import type { PluginModule } from '../../src/lib/core/plugin-loader.js';
14
+ import type { ToolOption } from '../../src/lib/core/plugin-types.js';
15
+ import { floodFill, snapshotPixels, applyPixels, hexToRgba, makeSnapshotUndo } from './drawing-utils.js';
16
+ import { getToolOptionValue } from '../../src/lib/core/tool-options-state.svelte.js';
17
+ import FillIcon from '~icons/lucide/paint-bucket';
18
+
19
+ export const fillToolPlugin: PluginModule = {
20
+ name: 'builtin/fill',
21
+ version: '1.0.0',
22
+ dependencies: [],
23
+ register(api) {
24
+ api.addCommand('flood_fill', {
25
+ tier: 'frame',
26
+
27
+ execute(params, ctx) {
28
+ const buffer = ctx.getActiveBuffer?.();
29
+ if (!buffer) return;
30
+
31
+ const { r, g, b, a } = hexToRgba(params["color"]);
32
+ const tolerance = params["tolerance"] ?? 0;
33
+ const region = floodFill(buffer, params["x"], params["y"], tolerance);
34
+
35
+ const snapshot = snapshotPixels(buffer, region);
36
+ const fillPixels = region.map((p) => ({ ...p, r, g, b, a }));
37
+ applyPixels(buffer, fillPixels);
38
+ return snapshot;
39
+ },
40
+
41
+ undo: makeSnapshotUndo(),
42
+
43
+ describe(params) {
44
+ const tol = params["tolerance"] ?? 0;
45
+ const tolSuffix = tol > 0 ? ` (tolerance ${String(tol)})` : '';
46
+ return `Flood fill at (${String(params["x"])}, ${String(params["y"])}) with ${String(params["color"])}${tolSuffix}`;
47
+ },
48
+ });
49
+
50
+ api.addToolbarItem('toolbar:drawing-tools:fill', {
51
+ toolbarId: 'drawing-tools',
52
+ kind: 'tool',
53
+ targetId: 'fill',
54
+ group: 'draw',
55
+ order: 30,
56
+ });
57
+
58
+ const toleranceOption: ToolOption = {
59
+ id: 'tolerance',
60
+ label: 'Tolerance',
61
+ type: 'select',
62
+ choices: [
63
+ { value: 0, label: '0' },
64
+ { value: 10, label: '10' },
65
+ { value: 25, label: '25' },
66
+ { value: 50, label: '50' },
67
+ ],
68
+ defaultValue: 0,
69
+ };
70
+
71
+ api.addTool('fill', {
72
+ icon: FillIcon,
73
+ cursor: 'crosshair',
74
+ options: [toleranceOption],
75
+
76
+ onPointerDown(_e, ctx) {
77
+ const tolerance = (getToolOptionValue('fill', 'tolerance') as number) || 0;
78
+ ctx.api.dispatch({
79
+ type: 'flood_fill',
80
+ plugin: 'builtin/fill',
81
+ version: '1.0.0',
82
+ params: {
83
+ x: ctx.canvasX,
84
+ y: ctx.canvasY,
85
+ color: ctx.color,
86
+ tolerance,
87
+ layerId: '',
88
+ },
89
+ });
90
+ },
91
+ });
92
+ },
93
+ };
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Gradient Tool Plugin -- draws linear gradients between two points.
3
+ *
4
+ * Registers:
5
+ * - Command: `draw_gradient` (tier: 'frame')
6
+ * - Tool: `gradient`
7
+ *
8
+ * Two modes:
9
+ * - 'smooth': linear RGB interpolation between color0 and color1
10
+ * - 'dithered': Bayer 2x2 ordered dithering between the two colors
11
+ *
12
+ * The gradient is drawn across the full canvas, projecting each pixel
13
+ * onto the line from (x0,y0) to (x1,y1).
14
+ */
15
+
16
+ import type { PluginModule } from '../../src/lib/core/plugin-loader.js';
17
+ import type { PixelData, Point } from './drawing-utils.js';
18
+ import { snapshotPixels, applyPixels, hexToRgba, makeSnapshotUndo } from './drawing-utils.js';
19
+ import { setShapePreview, clearShapePreview } from '../../src/lib/canvas/shape-preview-state.svelte.js';
20
+ import GradientIcon from '~icons/lucide/droplets';
21
+
22
+ /**
23
+ * Bayer 2x2 dither matrix.
24
+ * Threshold values are 0-3, normalized by dividing by 4.
25
+ */
26
+ const BAYER_2X2 = [
27
+ [0, 2],
28
+ [3, 1],
29
+ ];
30
+
31
+ /** Linearly interpolate between two values. */
32
+ function lerp(a: number, b: number, t: number): number {
33
+ return Math.round(a + (b - a) * t);
34
+ }
35
+
36
+ /**
37
+ * Compute gradient pixels for the region covered by the line from (x0,y0) to (x1,y1).
38
+ * Each pixel on the canvas is projected onto the gradient line to determine its t value.
39
+ */
40
+ export function computeGradient(
41
+ width: number,
42
+ height: number,
43
+ x0: number,
44
+ y0: number,
45
+ x1: number,
46
+ y1: number,
47
+ color0: string,
48
+ color1: string,
49
+ mode: 'smooth' | 'dithered',
50
+ ): PixelData[] {
51
+ const c0 = hexToRgba(color0);
52
+ const c1 = hexToRgba(color1);
53
+ const pixels: PixelData[] = [];
54
+
55
+ // Direction vector and its squared length
56
+ const dx = x1 - x0;
57
+ const dy = y1 - y0;
58
+ const lenSq = dx * dx + dy * dy;
59
+
60
+ // Degenerate case: start and end are the same point; fill with color0
61
+ if (lenSq === 0) {
62
+ for (let py = 0; py < height; py++) {
63
+ for (let px = 0; px < width; px++) {
64
+ pixels.push({ x: px, y: py, r: c0.r, g: c0.g, b: c0.b, a: c0.a });
65
+ }
66
+ }
67
+ return pixels;
68
+ }
69
+
70
+ for (let py = 0; py < height; py++) {
71
+ for (let px = 0; px < width; px++) {
72
+ // Project (px, py) onto the gradient line to find parameter t
73
+ const t = Math.max(0, Math.min(1,
74
+ ((px - x0) * dx + (py - y0) * dy) / lenSq,
75
+ ));
76
+
77
+ if (mode === 'smooth') {
78
+ pixels.push({
79
+ x: px,
80
+ y: py,
81
+ r: lerp(c0.r, c1.r, t),
82
+ g: lerp(c0.g, c1.g, t),
83
+ b: lerp(c0.b, c1.b, t),
84
+ a: lerp(c0.a, c1.a, t),
85
+ });
86
+ } else {
87
+ // Dithered mode: use Bayer 2x2 threshold to choose between c0 and c1
88
+ const row = BAYER_2X2[py % 2];
89
+ const threshold = (row?.[px % 2] ?? 0) / 4;
90
+ const useColor1 = t > threshold;
91
+ const c = useColor1 ? c1 : c0;
92
+ pixels.push({ x: px, y: py, r: c.r, g: c.g, b: c.b, a: c.a });
93
+ }
94
+ }
95
+ }
96
+
97
+ return pixels;
98
+ }
99
+
100
+ export const gradientToolPlugin: PluginModule = {
101
+ name: 'builtin/gradient',
102
+ version: '1.0.0',
103
+ dependencies: [],
104
+ register(api) {
105
+ api.addCommand('draw_gradient', {
106
+ tier: 'frame',
107
+
108
+ execute(params, ctx) {
109
+ const buffer = ctx.getActiveBuffer?.();
110
+ if (!buffer) return;
111
+
112
+ const allPoints: Point[] = [];
113
+ for (let y = 0; y < buffer.height; y++) {
114
+ for (let x = 0; x < buffer.width; x++) {
115
+ allPoints.push({ x, y });
116
+ }
117
+ }
118
+ const snapshot = snapshotPixels(buffer, allPoints);
119
+
120
+ const pixels = computeGradient(
121
+ buffer.width,
122
+ buffer.height,
123
+ params["x0"],
124
+ params["y0"],
125
+ params["x1"],
126
+ params["y1"],
127
+ params["color0"],
128
+ params["color1"],
129
+ params["mode"],
130
+ );
131
+ applyPixels(buffer, pixels);
132
+ return snapshot;
133
+ },
134
+
135
+ undo: makeSnapshotUndo(),
136
+
137
+ describe(params) {
138
+ return `Drew ${String(params["mode"])} gradient from (${String(params["x0"])},${String(params["y0"])}) to (${String(params["x1"])},${String(params["y1"])})`;
139
+ },
140
+ });
141
+
142
+ api.addToolbarItem('toolbar:drawing-tools:gradient', {
143
+ toolbarId: 'drawing-tools',
144
+ kind: 'tool',
145
+ targetId: 'gradient',
146
+ group: 'color',
147
+ order: 20,
148
+ });
149
+
150
+ // -- Stroke state for drag interaction --
151
+ let drawing = false;
152
+ let startX = 0;
153
+ let startY = 0;
154
+ let drawColor = '';
155
+
156
+ api.addTool('gradient', {
157
+ icon: GradientIcon,
158
+ cursor: 'crosshair',
159
+
160
+ onPointerDown(_e, ctx) {
161
+ drawing = true;
162
+ startX = ctx.canvasX;
163
+ startY = ctx.canvasY;
164
+ drawColor = ctx.color;
165
+ },
166
+
167
+ onPointerMove(_e, ctx) {
168
+ if (!drawing) return;
169
+ // Show the gradient axis as a line preview
170
+ setShapePreview({
171
+ type: 'line',
172
+ startX,
173
+ startY,
174
+ endX: ctx.canvasX,
175
+ endY: ctx.canvasY,
176
+ color: drawColor,
177
+ filled: false,
178
+ });
179
+ },
180
+
181
+ onPointerUp(_e, ctx) {
182
+ if (!drawing) return;
183
+ drawing = false;
184
+ clearShapePreview();
185
+
186
+ ctx.api.dispatch({
187
+ type: 'draw_gradient',
188
+ plugin: 'builtin/gradient',
189
+ version: '1.0.0',
190
+ params: {
191
+ x0: startX,
192
+ y0: startY,
193
+ x1: ctx.canvasX,
194
+ y1: ctx.canvasY,
195
+ color0: drawColor,
196
+ color1: '#000000',
197
+ mode: 'smooth',
198
+ layerId: '',
199
+ },
200
+ });
201
+ },
202
+ });
203
+ },
204
+ };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Tests for the gradient tool plugin.
3
+ *
4
+ * Covers smooth and dithered gradient modes, horizontal and diagonal gradients.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from 'vitest';
8
+ import { PixelBuffer } from '../../src/lib/canvas/pixel-buffer.js';
9
+ import { commandRegistry } from '../../src/lib/core/registries.svelte.js';
10
+ import { dispatch, undoLast, _resetForTesting as resetDispatcher, setContext } from '../../src/lib/core/dispatcher.js';
11
+ import type { Command } from '../../src/lib/core/commands.js';
12
+ import type { CommandType, ParamsOf } from '../../src/lib/core/command-params.js';
13
+ import { createPluginAPI } from '../../src/lib/core/plugin-api.js';
14
+
15
+ import { gradientToolPlugin, computeGradient } from './gradient-tool.js';
16
+
17
+ // --- Helpers ---
18
+
19
+ // Typed overload: validates params when type is a known command literal
20
+ function makeCommand<T extends CommandType>(type: T, params: ParamsOf<T>): Command;
21
+ // String fallback: for dynamic/unknown command types in tests
22
+ function makeCommand(type: string, params?: Record<string, unknown>): Command;
23
+ function makeCommand(type: string, params: Record<string, unknown> = {}): Command {
24
+ return { type, plugin: 'test', version: '1.0.0', params, timestamp: Date.now(), id: crypto.randomUUID() };
25
+ }
26
+
27
+ function setupBuffer(width = 8, height = 8): PixelBuffer {
28
+ const buffer = new PixelBuffer(width, height);
29
+ setContext({ getActiveBuffer: () => buffer });
30
+ return buffer;
31
+ }
32
+
33
+ // --- Tests ---
34
+
35
+ describe('Gradient Tool Plugin', () => {
36
+ beforeEach(() => {
37
+ resetDispatcher();
38
+ const api = createPluginAPI('builtin/gradient');
39
+ gradientToolPlugin.register(api);
40
+ });
41
+
42
+ it('should register the draw_gradient command', () => {
43
+ expect(commandRegistry.get('draw_gradient')).toBeDefined();
44
+ });
45
+
46
+ it('smooth gradient: endpoints have correct colors', () => {
47
+ // Horizontal gradient across an 8-wide canvas
48
+ const pixels = computeGradient(8, 1, 0, 0, 7, 0, '#FF0000', '#0000FF', 'smooth');
49
+
50
+ // Leftmost pixel should be red
51
+ const first = pixels[0];
52
+ if (!first) throw new Error("missing pixel");
53
+ expect(first.r).toBe(255);
54
+ expect(first.g).toBe(0);
55
+ expect(first.b).toBe(0);
56
+
57
+ // Rightmost pixel should be blue
58
+ const last = pixels[7];
59
+ if (!last) throw new Error("missing pixel");
60
+ expect(last.r).toBe(0);
61
+ expect(last.g).toBe(0);
62
+ expect(last.b).toBe(255);
63
+ });
64
+
65
+ it('smooth gradient: midpoint is blended', () => {
66
+ const pixels = computeGradient(8, 1, 0, 0, 7, 0, '#FF0000', '#0000FF', 'smooth');
67
+ // Midpoint (x=3 or x=4) should have some red and some blue
68
+ const mid = pixels[4];
69
+ if (!mid) throw new Error("missing pixel");
70
+ expect(mid.r).toBeGreaterThan(0);
71
+ expect(mid.b).toBeGreaterThan(0);
72
+ });
73
+
74
+ it('dithered gradient: produces a pattern (not solid colors)', () => {
75
+ // 4x4 canvas with horizontal dithered gradient
76
+ const pixels = computeGradient(4, 4, 0, 0, 3, 0, '#FF0000', '#0000FF', 'dithered');
77
+
78
+ // Collect unique colors
79
+ const colorSet = new Set<string>();
80
+ for (const p of pixels) {
81
+ colorSet.add(`${String(p.r)},${String(p.g)},${String(p.b)}`);
82
+ }
83
+
84
+ // Should have at least 2 distinct colors (the dither pattern)
85
+ expect(colorSet.size).toBeGreaterThanOrEqual(2);
86
+ });
87
+
88
+ it('horizontal gradient fills all pixels', () => {
89
+ const buffer = setupBuffer(8, 4);
90
+
91
+ dispatch(makeCommand('draw_gradient', {
92
+ x0: 0, y0: 0, x1: 7, y1: 0,
93
+ color0: '#FF0000', color1: '#0000FF',
94
+ mode: 'smooth',
95
+ layerId: '',
96
+ }));
97
+
98
+ // Every pixel should be non-transparent
99
+ for (let y = 0; y < 4; y++) {
100
+ for (let x = 0; x < 8; x++) {
101
+ const [, , , a] = buffer.getPixel(x, y);
102
+ expect(a).toBe(255);
103
+ }
104
+ }
105
+ });
106
+
107
+ it('diagonal gradient works correctly', () => {
108
+ const pixels = computeGradient(4, 4, 0, 0, 3, 3, '#000000', '#FFFFFF', 'smooth');
109
+
110
+ // Top-left (0,0) should be black
111
+ const topLeft = pixels[0];
112
+ if (!topLeft) throw new Error("missing pixel");
113
+ expect(topLeft.r).toBe(0);
114
+ expect(topLeft.g).toBe(0);
115
+ expect(topLeft.b).toBe(0);
116
+
117
+ // Bottom-right (3,3) should be white
118
+ const bottomRight = pixels[3 * 4 + 3];
119
+ if (!bottomRight) throw new Error("missing pixel");
120
+ expect(bottomRight.r).toBe(255);
121
+ expect(bottomRight.g).toBe(255);
122
+ expect(bottomRight.b).toBe(255);
123
+ });
124
+
125
+ it('gradient undoes correctly', () => {
126
+ const buffer = setupBuffer(4, 4);
127
+
128
+ dispatch(makeCommand('draw_gradient', {
129
+ x0: 0, y0: 0, x1: 3, y1: 0,
130
+ color0: '#FF0000', color1: '#0000FF',
131
+ mode: 'smooth',
132
+ layerId: '',
133
+ }));
134
+
135
+ expect(buffer.getPixel(0, 0)[0]).toBe(255); // Red
136
+
137
+ undoLast();
138
+
139
+ // Should be back to transparent
140
+ expect(buffer.getPixel(0, 0)).toEqual([0, 0, 0, 0]);
141
+ });
142
+ });