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,495 @@
1
+ /**
2
+ * Tests for drawing utility functions.
3
+ *
4
+ * Covers line algorithms, rectangle/ellipse/diamond primitives,
5
+ * flood fill, and snapshot/apply helpers.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { PixelBuffer } from '../../src/lib/canvas/pixel-buffer.js';
10
+ import {
11
+ bresenhamLine,
12
+ bresenhamLineNoDoubles,
13
+ rectOutline,
14
+ rectFilled,
15
+ ellipseOutline,
16
+ ellipseFilled,
17
+ diamondOutline,
18
+ diamondFilled,
19
+ floodFill,
20
+ snapshotPixels,
21
+ applyPixels,
22
+ hexToRgba,
23
+ } from './drawing-utils.js';
24
+
25
+ // --- Helpers ---
26
+
27
+ /** Convert point array to a set of "x,y" strings for easy lookup. */
28
+ function toSet(points: { x: number; y: number }[]): Set<string> {
29
+ return new Set(points.map((p) => `${String(p.x)},${String(p.y)}`));
30
+ }
31
+
32
+ /** Check that a point list has no duplicate coordinates. */
33
+ function hasNoDuplicates(points: { x: number; y: number }[]): boolean {
34
+ return toSet(points).size === points.length;
35
+ }
36
+
37
+ // --- Bresenham Line ---
38
+
39
+ describe('bresenhamLine', () => {
40
+ it('should return a single point for same start and end', () => {
41
+ const points = bresenhamLine(5, 5, 5, 5);
42
+ expect(points).toEqual([{ x: 5, y: 5 }]);
43
+ });
44
+
45
+ it('should draw a horizontal line', () => {
46
+ const points = bresenhamLine(0, 0, 4, 0);
47
+ expect(points).toHaveLength(5);
48
+ for (let x = 0; x <= 4; x++) {
49
+ expect(points[x]).toEqual({ x, y: 0 });
50
+ }
51
+ });
52
+
53
+ it('should draw a vertical line', () => {
54
+ const points = bresenhamLine(2, 0, 2, 3);
55
+ expect(points).toHaveLength(4);
56
+ for (let y = 0; y <= 3; y++) {
57
+ expect(points[y]).toEqual({ x: 2, y });
58
+ }
59
+ });
60
+
61
+ it('should draw a 45-degree diagonal', () => {
62
+ const points = bresenhamLine(0, 0, 3, 3);
63
+ expect(points).toHaveLength(4);
64
+ for (let i = 0; i <= 3; i++) {
65
+ expect(points[i]).toEqual({ x: i, y: i });
66
+ }
67
+ });
68
+
69
+ it('should draw a line in reverse direction', () => {
70
+ const forward = bresenhamLine(0, 0, 5, 3);
71
+ const reverse = bresenhamLine(5, 3, 0, 0);
72
+ // Both should cover the same set of points
73
+ expect(toSet(forward)).toEqual(toSet(reverse));
74
+ });
75
+
76
+ it('should draw a steep line (dy > dx)', () => {
77
+ const points = bresenhamLine(0, 0, 2, 6);
78
+ expect(points).toHaveLength(7);
79
+ // Should start at origin and end at (2,6)
80
+ expect(points[0]).toEqual({ x: 0, y: 0 });
81
+ expect(points[points.length - 1]).toEqual({ x: 2, y: 6 });
82
+ });
83
+
84
+ it('should include both endpoints', () => {
85
+ const points = bresenhamLine(1, 2, 7, 5);
86
+ const set = toSet(points);
87
+ expect(set.has('1,2')).toBe(true);
88
+ expect(set.has('7,5')).toBe(true);
89
+ });
90
+ });
91
+
92
+ // --- No-Doubles Bresenham ---
93
+
94
+ describe('bresenhamLineNoDoubles', () => {
95
+ it('should return a single point for same start and end', () => {
96
+ const points = bresenhamLineNoDoubles(3, 3, 3, 3);
97
+ expect(points).toEqual([{ x: 3, y: 3 }]);
98
+ });
99
+
100
+ it('should produce the same result as standard for horizontal line', () => {
101
+ const standard = bresenhamLine(0, 0, 5, 0);
102
+ const noDoubles = bresenhamLineNoDoubles(0, 0, 5, 0);
103
+ expect(toSet(noDoubles)).toEqual(toSet(standard));
104
+ });
105
+
106
+ it('should produce the same result as standard for vertical line', () => {
107
+ const standard = bresenhamLine(0, 0, 0, 5);
108
+ const noDoubles = bresenhamLineNoDoubles(0, 0, 0, 5);
109
+ expect(toSet(noDoubles)).toEqual(toSet(standard));
110
+ });
111
+
112
+ it('should produce the same result as standard for diagonal line', () => {
113
+ const standard = bresenhamLine(0, 0, 4, 4);
114
+ const noDoubles = bresenhamLineNoDoubles(0, 0, 4, 4);
115
+ expect(toSet(noDoubles)).toEqual(toSet(standard));
116
+ });
117
+
118
+ it('should never have two consecutive pixels differing only on minor axis', () => {
119
+ // Test with a shallow slope where doubles are likely
120
+ const points = bresenhamLineNoDoubles(0, 0, 10, 3);
121
+ for (let i = 1; i < points.length; i++) {
122
+ const prev = points[i - 1];
123
+ const curr = points[i];
124
+ if (!prev || !curr) continue;
125
+ // Two consecutive pixels should not have same x but different y
126
+ // (that would be a "double" on the minor axis for a shallow slope)
127
+ const sameX = prev.x === curr.x;
128
+ const sameY = prev.y === curr.y;
129
+ // At least one axis must change; if x is the major axis and stays same,
130
+ // that's a double
131
+ expect(sameX && sameY).toBe(false);
132
+ }
133
+ });
134
+
135
+ it('should produce fewer or equal points than standard for non-axis-aligned lines', () => {
136
+ const standard = bresenhamLine(0, 0, 10, 4);
137
+ const noDoubles = bresenhamLineNoDoubles(0, 0, 10, 4);
138
+ expect(noDoubles.length).toBeLessThanOrEqual(standard.length);
139
+ });
140
+
141
+ it('should include both endpoints', () => {
142
+ const points = bresenhamLineNoDoubles(1, 2, 8, 5);
143
+ const set = toSet(points);
144
+ expect(set.has('1,2')).toBe(true);
145
+ expect(set.has('8,5')).toBe(true);
146
+ });
147
+ });
148
+
149
+ // --- Rectangle Outline ---
150
+
151
+ describe('rectOutline', () => {
152
+ it('should return empty for zero-sized rect', () => {
153
+ expect(rectOutline(0, 0, 0, 0)).toEqual([]);
154
+ expect(rectOutline(0, 0, 0, 5)).toEqual([]);
155
+ expect(rectOutline(0, 0, 5, 0)).toEqual([]);
156
+ });
157
+
158
+ it('should return single point for 1x1 rect', () => {
159
+ expect(rectOutline(3, 4, 1, 1)).toEqual([{ x: 3, y: 4 }]);
160
+ });
161
+
162
+ it('should return correct perimeter for a 3x3 rect', () => {
163
+ const points = rectOutline(0, 0, 3, 3);
164
+ // 3x3 rect has 8 perimeter points (3*4 - 4 corners counted once)
165
+ expect(points).toHaveLength(8);
166
+ expect(hasNoDuplicates(points)).toBe(true);
167
+ // Center should NOT be included (that's interior)
168
+ expect(toSet(points).has('1,1')).toBe(false);
169
+ });
170
+
171
+ it('should return correct perimeter for a 4x2 rect', () => {
172
+ const points = rectOutline(0, 0, 4, 2);
173
+ // Top: (0,0)(1,0)(2,0)(3,0), Bottom: (3,1)(2,1)(1,1)(0,1) = 8 points
174
+ expect(points).toHaveLength(8);
175
+ expect(hasNoDuplicates(points)).toBe(true);
176
+ });
177
+
178
+ it('should handle 1-wide rect (vertical line)', () => {
179
+ const points = rectOutline(5, 2, 1, 4);
180
+ expect(points).toHaveLength(4);
181
+ for (let y = 2; y <= 5; y++) {
182
+ expect(toSet(points).has(`5,${String(y)}`)).toBe(true);
183
+ }
184
+ });
185
+
186
+ it('should handle 1-tall rect (horizontal line)', () => {
187
+ const points = rectOutline(0, 3, 5, 1);
188
+ expect(points).toHaveLength(5);
189
+ for (let x = 0; x <= 4; x++) {
190
+ expect(toSet(points).has(`${String(x)},3`)).toBe(true);
191
+ }
192
+ });
193
+ });
194
+
195
+ // --- Filled Rectangle ---
196
+
197
+ describe('rectFilled', () => {
198
+ it('should return empty for zero-sized rect', () => {
199
+ expect(rectFilled(0, 0, 0, 0)).toEqual([]);
200
+ });
201
+
202
+ it('should return correct area for a 3x2 rect', () => {
203
+ const points = rectFilled(1, 1, 3, 2);
204
+ expect(points).toHaveLength(6);
205
+ expect(hasNoDuplicates(points)).toBe(true);
206
+ });
207
+
208
+ it('should contain all interior and perimeter points', () => {
209
+ const points = rectFilled(0, 0, 4, 3);
210
+ expect(points).toHaveLength(12);
211
+ const set = toSet(points);
212
+ for (let y = 0; y < 3; y++) {
213
+ for (let x = 0; x < 4; x++) {
214
+ expect(set.has(`${String(x)},${String(y)}`)).toBe(true);
215
+ }
216
+ }
217
+ });
218
+ });
219
+
220
+ // --- Ellipse Outline ---
221
+
222
+ describe('ellipseOutline', () => {
223
+ it('should return single point for radius 0,0', () => {
224
+ const points = ellipseOutline(5, 5, 0, 0);
225
+ expect(points).toEqual([{ x: 5, y: 5 }]);
226
+ });
227
+
228
+ it('should return a vertical line for rx=0', () => {
229
+ const points = ellipseOutline(5, 5, 0, 3);
230
+ expect(points).toHaveLength(7);
231
+ const set = toSet(points);
232
+ for (let y = 2; y <= 8; y++) {
233
+ expect(set.has(`5,${String(y)}`)).toBe(true);
234
+ }
235
+ });
236
+
237
+ it('should return a horizontal line for ry=0', () => {
238
+ const points = ellipseOutline(5, 5, 3, 0);
239
+ expect(points).toHaveLength(7);
240
+ const set = toSet(points);
241
+ for (let x = 2; x <= 8; x++) {
242
+ expect(set.has(`${String(x)},5`)).toBe(true);
243
+ }
244
+ });
245
+
246
+ it('should produce an approximately circular shape for equal radii', () => {
247
+ const r = 5;
248
+ const points = ellipseOutline(10, 10, r, r);
249
+ expect(points.length).toBeGreaterThan(0);
250
+ expect(hasNoDuplicates(points)).toBe(true);
251
+
252
+ // All points should be approximately distance r from center
253
+ for (const p of points) {
254
+ const dist = Math.sqrt((p.x - 10) ** 2 + (p.y - 10) ** 2);
255
+ // Allow some tolerance for pixel grid rasterization
256
+ expect(dist).toBeGreaterThan(r - 1.5);
257
+ expect(dist).toBeLessThan(r + 1.5);
258
+ }
259
+ });
260
+
261
+ it('should be symmetric around center', () => {
262
+ const points = ellipseOutline(10, 10, 4, 3);
263
+ const set = toSet(points);
264
+ // For every point (cx+dx, cy+dy), the mirrors should also exist
265
+ for (const p of points) {
266
+ const dx = p.x - 10;
267
+ const dy = p.y - 10;
268
+ expect(set.has(`${String(10 - dx)},${String(10 + dy)}`)).toBe(true);
269
+ expect(set.has(`${String(10 + dx)},${String(10 - dy)}`)).toBe(true);
270
+ expect(set.has(`${String(10 - dx)},${String(10 - dy)}`)).toBe(true);
271
+ }
272
+ });
273
+
274
+ it('should include cardinal extreme points', () => {
275
+ const points = ellipseOutline(10, 10, 5, 3);
276
+ const set = toSet(points);
277
+ expect(set.has('15,10')).toBe(true); // right
278
+ expect(set.has('5,10')).toBe(true); // left
279
+ expect(set.has('10,13')).toBe(true); // bottom
280
+ expect(set.has('10,7')).toBe(true); // top
281
+ });
282
+ });
283
+
284
+ // --- Filled Ellipse ---
285
+
286
+ describe('ellipseFilled', () => {
287
+ it('should return single point for radius 0,0', () => {
288
+ const points = ellipseFilled(5, 5, 0, 0);
289
+ expect(points).toEqual([{ x: 5, y: 5 }]);
290
+ });
291
+
292
+ it('should fill more points than outline', () => {
293
+ const outline = ellipseOutline(10, 10, 4, 3);
294
+ const filled = ellipseFilled(10, 10, 4, 3);
295
+ expect(filled.length).toBeGreaterThan(outline.length);
296
+ });
297
+
298
+ it('should include the center point', () => {
299
+ const points = ellipseFilled(10, 10, 3, 3);
300
+ expect(toSet(points).has('10,10')).toBe(true);
301
+ });
302
+
303
+ it('should contain all outline points', () => {
304
+ const outline = ellipseOutline(10, 10, 4, 3);
305
+ const filledSet = toSet(ellipseFilled(10, 10, 4, 3));
306
+ for (const p of outline) {
307
+ expect(filledSet.has(`${String(p.x)},${String(p.y)}`)).toBe(true);
308
+ }
309
+ });
310
+ });
311
+
312
+ // --- Diamond Outline ---
313
+
314
+ describe('diamondOutline', () => {
315
+ it('should return single point for radius 0,0', () => {
316
+ const points = diamondOutline(5, 5, 0, 0);
317
+ expect(points).toEqual([{ x: 5, y: 5 }]);
318
+ });
319
+
320
+ it('should have no duplicate points', () => {
321
+ const points = diamondOutline(10, 10, 5, 5);
322
+ expect(hasNoDuplicates(points)).toBe(true);
323
+ });
324
+
325
+ it('should include the four vertices', () => {
326
+ const cx = 10, cy = 10, rx = 4, ry = 3;
327
+ const set = toSet(diamondOutline(cx, cy, rx, ry));
328
+ expect(set.has(`${String(cx)},${String(cy - ry)}`)).toBe(true); // top
329
+ expect(set.has(`${String(cx + rx)},${String(cy)}`)).toBe(true); // right
330
+ expect(set.has(`${String(cx)},${String(cy + ry)}`)).toBe(true); // bottom
331
+ expect(set.has(`${String(cx - rx)},${String(cy)}`)).toBe(true); // left
332
+ });
333
+
334
+ it('should be symmetric', () => {
335
+ const cx = 10, cy = 10;
336
+ const points = diamondOutline(cx, cy, 5, 3);
337
+ const set = toSet(points);
338
+ for (const p of points) {
339
+ const dx = p.x - cx;
340
+ const dy = p.y - cy;
341
+ expect(set.has(`${String(cx - dx)},${String(cy + dy)}`)).toBe(true);
342
+ expect(set.has(`${String(cx + dx)},${String(cy - dy)}`)).toBe(true);
343
+ expect(set.has(`${String(cx - dx)},${String(cy - dy)}`)).toBe(true);
344
+ }
345
+ });
346
+ });
347
+
348
+ // --- Diamond Filled ---
349
+
350
+ describe('diamondFilled', () => {
351
+ it('should return single point for radius 0,0', () => {
352
+ const points = diamondFilled(5, 5, 0, 0);
353
+ expect(points).toEqual([{ x: 5, y: 5 }]);
354
+ });
355
+
356
+ it('should fill more points than outline', () => {
357
+ const outline = diamondOutline(10, 10, 5, 5);
358
+ const filled = diamondFilled(10, 10, 5, 5);
359
+ expect(filled.length).toBeGreaterThanOrEqual(outline.length);
360
+ });
361
+
362
+ it('should include the center', () => {
363
+ const set = toSet(diamondFilled(10, 10, 4, 3));
364
+ expect(set.has('10,10')).toBe(true);
365
+ });
366
+ });
367
+
368
+ // --- Flood Fill ---
369
+
370
+ describe('floodFill', () => {
371
+ it('should return empty for out-of-bounds start', () => {
372
+ const buf = new PixelBuffer(4, 4);
373
+ expect(floodFill(buf, -1, 0)).toEqual([]);
374
+ expect(floodFill(buf, 4, 0)).toEqual([]);
375
+ });
376
+
377
+ it('should fill entire buffer when all pixels are the same color', () => {
378
+ const buf = new PixelBuffer(4, 3);
379
+ // All transparent (default)
380
+ const points = floodFill(buf, 0, 0);
381
+ expect(points).toHaveLength(12);
382
+ });
383
+
384
+ it('should stop at color boundaries', () => {
385
+ const buf = new PixelBuffer(5, 5);
386
+ // Draw a vertical wall at x=2
387
+ for (let y = 0; y < 5; y++) {
388
+ buf.setPixel(2, y, 255, 0, 0, 255);
389
+ }
390
+ // Fill from (0,0) should only reach x=0 and x=1 (5 rows * 2 cols = 10)
391
+ const points = floodFill(buf, 0, 0);
392
+ expect(points).toHaveLength(10);
393
+ // All points should have x < 2
394
+ for (const p of points) {
395
+ expect(p.x).toBeLessThan(2);
396
+ }
397
+ });
398
+
399
+ it('should fill a bounded region', () => {
400
+ const buf = new PixelBuffer(6, 6);
401
+ // Create a box with walls
402
+ for (let i = 0; i < 6; i++) {
403
+ buf.setPixel(i, 0, 255, 0, 0, 255); // top wall
404
+ buf.setPixel(i, 5, 255, 0, 0, 255); // bottom wall
405
+ buf.setPixel(0, i, 255, 0, 0, 255); // left wall
406
+ buf.setPixel(5, i, 255, 0, 0, 255); // right wall
407
+ }
408
+ // Fill from center (3,3), should fill interior: 4x4 = 16 transparent pixels
409
+ const points = floodFill(buf, 3, 3);
410
+ expect(points).toHaveLength(16);
411
+ for (const p of points) {
412
+ expect(p.x).toBeGreaterThanOrEqual(1);
413
+ expect(p.x).toBeLessThanOrEqual(4);
414
+ expect(p.y).toBeGreaterThanOrEqual(1);
415
+ expect(p.y).toBeLessThanOrEqual(4);
416
+ }
417
+ });
418
+
419
+ it('should return single pixel if it is surrounded by different colors', () => {
420
+ const buf = new PixelBuffer(3, 3);
421
+ buf.fill(255, 0, 0, 255); // Fill all red
422
+ buf.setPixel(1, 1, 0, 0, 0, 0); // Center is transparent
423
+ const points = floodFill(buf, 1, 1);
424
+ expect(points).toHaveLength(1);
425
+ expect(points[0]).toEqual({ x: 1, y: 1 });
426
+ });
427
+
428
+ it('should handle L-shaped region', () => {
429
+ const buf = new PixelBuffer(4, 4);
430
+ buf.fill(255, 0, 0, 255);
431
+ // Create an L-shaped transparent region
432
+ buf.setPixel(0, 0, 0, 0, 0, 0);
433
+ buf.setPixel(0, 1, 0, 0, 0, 0);
434
+ buf.setPixel(0, 2, 0, 0, 0, 0);
435
+ buf.setPixel(1, 2, 0, 0, 0, 0);
436
+
437
+ const points = floodFill(buf, 0, 0);
438
+ expect(points).toHaveLength(4);
439
+ });
440
+ });
441
+
442
+ // --- snapshotPixels / applyPixels roundtrip ---
443
+
444
+ describe('snapshotPixels and applyPixels', () => {
445
+ it('should roundtrip pixel data correctly', () => {
446
+ const buf = new PixelBuffer(4, 4);
447
+ buf.setPixel(1, 1, 255, 0, 0, 255);
448
+ buf.setPixel(2, 2, 0, 255, 0, 128);
449
+ buf.setPixel(3, 3, 0, 0, 255, 64);
450
+
451
+ const points = [{ x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }];
452
+ const snapshot = snapshotPixels(buf, points);
453
+
454
+ // Overwrite the pixels
455
+ buf.setPixel(1, 1, 0, 0, 0, 0);
456
+ buf.setPixel(2, 2, 0, 0, 0, 0);
457
+ buf.setPixel(3, 3, 0, 0, 0, 0);
458
+
459
+ // Restore from snapshot
460
+ applyPixels(buf, snapshot);
461
+
462
+ expect(buf.getPixel(1, 1)).toEqual([255, 0, 0, 255]);
463
+ expect(buf.getPixel(2, 2)).toEqual([0, 255, 0, 128]);
464
+ expect(buf.getPixel(3, 3)).toEqual([0, 0, 255, 64]);
465
+ });
466
+
467
+ it('should snapshot out-of-bounds points as [0,0,0,0]', () => {
468
+ const buf = new PixelBuffer(2, 2);
469
+ buf.fill(255, 255, 255, 255);
470
+
471
+ const snapshot = snapshotPixels(buf, [{ x: -1, y: 0 }, { x: 10, y: 10 }]);
472
+ expect(snapshot[0]).toEqual({ x: -1, y: 0, r: 0, g: 0, b: 0, a: 0 });
473
+ expect(snapshot[1]).toEqual({ x: 10, y: 10, r: 0, g: 0, b: 0, a: 0 });
474
+ });
475
+ });
476
+
477
+ // --- hexToRgba ---
478
+
479
+ describe('hexToRgba', () => {
480
+ it('should parse black', () => {
481
+ expect(hexToRgba('#000000')).toEqual({ r: 0, g: 0, b: 0, a: 255 });
482
+ });
483
+
484
+ it('should parse white', () => {
485
+ expect(hexToRgba('#FFFFFF')).toEqual({ r: 255, g: 255, b: 255, a: 255 });
486
+ });
487
+
488
+ it('should parse red', () => {
489
+ expect(hexToRgba('#FF0000')).toEqual({ r: 255, g: 0, b: 0, a: 255 });
490
+ });
491
+
492
+ it('should parse arbitrary color', () => {
493
+ expect(hexToRgba('#1A2B3C')).toEqual({ r: 26, g: 43, b: 60, a: 255 });
494
+ });
495
+ });