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,87 @@
1
+ /**
2
+ * Color commands plugin for PixelWeaver.
3
+ *
4
+ * Registers undoable commands for color operations:
5
+ * foreground/background color changes, color swap, and palette lock toggle.
6
+ */
7
+
8
+ import type { PluginModule } from '../core/plugin-loader.js';
9
+ import {
10
+ getForegroundColor,
11
+ setForegroundColor,
12
+ getBackgroundColor,
13
+ setBackgroundColor,
14
+ swapColors,
15
+ } from './color-state.svelte.js';
16
+ import {
17
+ isPaletteLocked,
18
+ setPaletteLocked,
19
+ } from './palette-state.svelte.js';
20
+
21
+ export const colorCommandsPlugin: PluginModule = {
22
+ name: 'builtin/colors',
23
+ version: '1.0.0',
24
+ description: 'Core color management commands',
25
+ dependencies: [],
26
+
27
+ register(api) {
28
+ api.addCommand('set_foreground_color', {
29
+ execute(params) {
30
+ const hex = params["color"];
31
+ // Store the previous color for undo (stashed in params by convention)
32
+ params["_prevColor"] = getForegroundColor();
33
+ setForegroundColor(hex);
34
+ },
35
+ undo(params) {
36
+ const prev = params["_prevColor"];
37
+ if (prev) setForegroundColor(prev);
38
+ },
39
+ describe(params) {
40
+ return `Set foreground color to ${String(params["color"])}`;
41
+ },
42
+ });
43
+
44
+ api.addCommand('set_background_color', {
45
+ execute(params) {
46
+ const hex = params["color"];
47
+ params["_prevColor"] = getBackgroundColor();
48
+ setBackgroundColor(hex);
49
+ },
50
+ undo(params) {
51
+ const prev = params["_prevColor"];
52
+ if (prev) setBackgroundColor(prev);
53
+ },
54
+ describe(params) {
55
+ return `Set background color to ${String(params["color"])}`;
56
+ },
57
+ });
58
+
59
+ api.addCommand('swap_colors', {
60
+ execute() {
61
+ swapColors();
62
+ },
63
+ undo() {
64
+ // Swap is its own inverse
65
+ swapColors();
66
+ },
67
+ describe() {
68
+ return 'Swapped foreground and background colors';
69
+ },
70
+ });
71
+
72
+ api.addCommand('set_palette_locked', {
73
+ execute(params) {
74
+ const locked = params["locked"];
75
+ params["_prevLocked"] = isPaletteLocked();
76
+ setPaletteLocked(locked);
77
+ },
78
+ undo(params) {
79
+ const prev = params["_prevLocked"];
80
+ if (prev !== undefined) setPaletteLocked(prev);
81
+ },
82
+ describe(params) {
83
+ return params["locked"] ? 'Locked palette' : 'Unlocked palette';
84
+ },
85
+ });
86
+ },
87
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Reactive color state for PixelWeaver.
3
+ *
4
+ * Module-level singleton using Svelte 5 $state runes.
5
+ * Manages foreground/background colors, recent color history,
6
+ * and the active color space for the picker UI.
7
+ */
8
+
9
+ const MAX_RECENT_COLORS = 32;
10
+
11
+ // --- Reactive state ---
12
+
13
+ let foregroundColor = $state('#000000');
14
+ let backgroundColor = $state('#FFFFFF');
15
+ let recentColors = $state<string[]>([]);
16
+ let activeColorSpace = $state<'hsv' | 'hsl' | 'rgb' | 'hex'>('hsv');
17
+
18
+ // --- Public API ---
19
+
20
+ export function getForegroundColor(): string {
21
+ return foregroundColor;
22
+ }
23
+
24
+ export function setForegroundColor(hex: string): void {
25
+ foregroundColor = hex;
26
+ }
27
+
28
+ export function getBackgroundColor(): string {
29
+ return backgroundColor;
30
+ }
31
+
32
+ export function setBackgroundColor(hex: string): void {
33
+ backgroundColor = hex;
34
+ }
35
+
36
+ /** Swap foreground and background colors */
37
+ export function swapColors(): void {
38
+ const tmp = foregroundColor;
39
+ foregroundColor = backgroundColor;
40
+ backgroundColor = tmp;
41
+ }
42
+
43
+ export function getRecentColors(): string[] {
44
+ return recentColors;
45
+ }
46
+
47
+ /**
48
+ * Add a color to the front of the recent colors list.
49
+ * Deduplicates (moves to front if already present) and caps at MAX_RECENT_COLORS.
50
+ */
51
+ export function addRecentColor(hex: string): void {
52
+ const filtered = recentColors.filter((c) => c !== hex);
53
+ recentColors = [hex, ...filtered].slice(0, MAX_RECENT_COLORS);
54
+ }
55
+
56
+ export function getActiveColorSpace(): 'hsv' | 'hsl' | 'rgb' | 'hex' {
57
+ return activeColorSpace;
58
+ }
59
+
60
+ export function setActiveColorSpace(space: 'hsv' | 'hsl' | 'rgb' | 'hex'): void {
61
+ activeColorSpace = space;
62
+ }
63
+
64
+ // --- Serialization ---
65
+
66
+ export interface ColorStateData {
67
+ foregroundColor: string;
68
+ backgroundColor: string;
69
+ recentColors: string[];
70
+ }
71
+
72
+ /** Export color state as a plain JSON-safe object. */
73
+ export function serializeColorState(): ColorStateData {
74
+ return {
75
+ foregroundColor: getForegroundColor(),
76
+ backgroundColor: getBackgroundColor(),
77
+ recentColors: [...getRecentColors()],
78
+ };
79
+ }
80
+
81
+ /** Restore color state from a serialized snapshot. */
82
+ export function deserializeColorState(data: ColorStateData): void {
83
+ setForegroundColor(data.foregroundColor);
84
+ setBackgroundColor(data.backgroundColor);
85
+ // Direct assignment -- no addRecentColor loop because we need to fully
86
+ // replace the list (not merge with existing) and preserve exact order.
87
+ recentColors = [...data.recentColors];
88
+ }
89
+
90
+ /**
91
+ * Reset all color state to defaults. Intended for tests only.
92
+ */
93
+ export function _resetForTesting(): void {
94
+ foregroundColor = '#000000';
95
+ backgroundColor = '#FFFFFF';
96
+ recentColors = [];
97
+ activeColorSpace = 'hsv';
98
+ }
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import {
3
+ getForegroundColor,
4
+ setForegroundColor,
5
+ getBackgroundColor,
6
+ setBackgroundColor,
7
+ swapColors,
8
+ getRecentColors,
9
+ addRecentColor,
10
+ getActiveColorSpace,
11
+ setActiveColorSpace,
12
+ _resetForTesting,
13
+ } from './color-state.svelte.js';
14
+
15
+ describe('Color State', () => {
16
+ beforeEach(() => {
17
+ _resetForTesting();
18
+ });
19
+
20
+ it('should default foreground to black', () => {
21
+ expect(getForegroundColor()).toBe('#000000');
22
+ });
23
+
24
+ it('should default background to white', () => {
25
+ expect(getBackgroundColor()).toBe('#FFFFFF');
26
+ });
27
+
28
+ it('should set foreground color', () => {
29
+ setForegroundColor('#FF0000');
30
+ expect(getForegroundColor()).toBe('#FF0000');
31
+ });
32
+
33
+ it('should set background color', () => {
34
+ setBackgroundColor('#00FF00');
35
+ expect(getBackgroundColor()).toBe('#00FF00');
36
+ });
37
+
38
+ it('should swap foreground and background colors', () => {
39
+ setForegroundColor('#FF0000');
40
+ setBackgroundColor('#0000FF');
41
+ swapColors();
42
+ expect(getForegroundColor()).toBe('#0000FF');
43
+ expect(getBackgroundColor()).toBe('#FF0000');
44
+ });
45
+
46
+ it('should swap colors back to original after two swaps', () => {
47
+ setForegroundColor('#AABBCC');
48
+ setBackgroundColor('#112233');
49
+ swapColors();
50
+ swapColors();
51
+ expect(getForegroundColor()).toBe('#AABBCC');
52
+ expect(getBackgroundColor()).toBe('#112233');
53
+ });
54
+
55
+ it('should start with empty recent colors', () => {
56
+ expect(getRecentColors()).toEqual([]);
57
+ });
58
+
59
+ it('should add a recent color to the front', () => {
60
+ addRecentColor('#FF0000');
61
+ addRecentColor('#00FF00');
62
+ expect(getRecentColors()[0]).toBe('#00FF00');
63
+ expect(getRecentColors()[1]).toBe('#FF0000');
64
+ });
65
+
66
+ it('should deduplicate recent colors (move to front)', () => {
67
+ addRecentColor('#FF0000');
68
+ addRecentColor('#00FF00');
69
+ addRecentColor('#FF0000'); // already present, should move to front
70
+ expect(getRecentColors()).toEqual(['#FF0000', '#00FF00']);
71
+ });
72
+
73
+ it('should cap recent colors at 32', () => {
74
+ for (let i = 0; i < 40; i++) {
75
+ const hex = `#${i.toString(16).padStart(6, '0').toUpperCase()}`;
76
+ addRecentColor(hex);
77
+ }
78
+ expect(getRecentColors()).toHaveLength(32);
79
+ // Most recent should be the last added
80
+ expect(getRecentColors()[0]).toBe('#000027');
81
+ });
82
+
83
+ it('should default active color space to hsv', () => {
84
+ expect(getActiveColorSpace()).toBe('hsv');
85
+ });
86
+
87
+ it('should set active color space', () => {
88
+ setActiveColorSpace('rgb');
89
+ expect(getActiveColorSpace()).toBe('rgb');
90
+ });
91
+ });
@@ -0,0 +1,220 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ parseColor,
4
+ hexToRgb,
5
+ rgbToHex,
6
+ rgbToHsv,
7
+ hsvToRgb,
8
+ rgbToHsl,
9
+ hslToRgb,
10
+ lerpColor,
11
+ nearestPaletteColor,
12
+ colorDistance,
13
+ } from './color-utils.js';
14
+
15
+ // --- parseColor ---
16
+
17
+ describe('parseColor', () => {
18
+ it('should parse a 6-digit hex string', () => {
19
+ const c = parseColor('#FF0000');
20
+ expect(c).toEqual({ r: 255, g: 0, b: 0, a: 1 });
21
+ });
22
+
23
+ it('should parse a 3-digit hex string', () => {
24
+ const c = parseColor('#F00');
25
+ expect(c).toEqual({ r: 255, g: 0, b: 0, a: 1 });
26
+ });
27
+
28
+ it('should parse rgb() notation', () => {
29
+ const c = parseColor('rgb(0, 128, 255)');
30
+ expect(c).toEqual({ r: 0, g: 128, b: 255, a: 1 });
31
+ });
32
+
33
+ it('should parse named colors', () => {
34
+ const c = parseColor('red');
35
+ expect(c).toEqual({ r: 255, g: 0, b: 0, a: 1 });
36
+ });
37
+
38
+ it('should return null for invalid input', () => {
39
+ expect(parseColor('notacolor')).toBeNull();
40
+ expect(parseColor('')).toBeNull();
41
+ });
42
+ });
43
+
44
+ // --- hexToRgb / rgbToHex roundtrip ---
45
+
46
+ describe('hexToRgb / rgbToHex', () => {
47
+ const cases = [
48
+ { hex: '#FF0000', r: 255, g: 0, b: 0 },
49
+ { hex: '#00FF00', r: 0, g: 255, b: 0 },
50
+ { hex: '#0000FF', r: 0, g: 0, b: 255 },
51
+ { hex: '#000000', r: 0, g: 0, b: 0 },
52
+ { hex: '#FFFFFF', r: 255, g: 255, b: 255 },
53
+ { hex: '#808080', r: 128, g: 128, b: 128 },
54
+ ];
55
+
56
+ for (const { hex, r, g, b } of cases) {
57
+ it(`hexToRgb(${hex}) should return (${String(r)}, ${String(g)}, ${String(b)})`, () => {
58
+ expect(hexToRgb(hex)).toEqual({ r, g, b });
59
+ });
60
+
61
+ it(`rgbToHex(${String(r)}, ${String(g)}, ${String(b)}) should return ${hex}`, () => {
62
+ expect(rgbToHex(r, g, b)).toBe(hex);
63
+ });
64
+ }
65
+
66
+ it('should roundtrip correctly', () => {
67
+ const original = '#AB12CD';
68
+ const { r, g, b } = hexToRgb(original);
69
+ expect(rgbToHex(r, g, b)).toBe(original);
70
+ });
71
+ });
72
+
73
+ // --- rgbToHsv / hsvToRgb roundtrip ---
74
+
75
+ describe('rgbToHsv / hsvToRgb', () => {
76
+ it('should convert red correctly', () => {
77
+ const hsv = rgbToHsv(255, 0, 0);
78
+ expect(hsv).toEqual({ h: 0, s: 100, v: 100 });
79
+ expect(hsvToRgb(0, 100, 100)).toEqual({ r: 255, g: 0, b: 0 });
80
+ });
81
+
82
+ it('should convert green correctly', () => {
83
+ const hsv = rgbToHsv(0, 255, 0);
84
+ expect(hsv).toEqual({ h: 120, s: 100, v: 100 });
85
+ expect(hsvToRgb(120, 100, 100)).toEqual({ r: 0, g: 255, b: 0 });
86
+ });
87
+
88
+ it('should convert blue correctly', () => {
89
+ const hsv = rgbToHsv(0, 0, 255);
90
+ expect(hsv).toEqual({ h: 240, s: 100, v: 100 });
91
+ expect(hsvToRgb(240, 100, 100)).toEqual({ r: 0, g: 0, b: 255 });
92
+ });
93
+
94
+ it('should convert white correctly', () => {
95
+ const hsv = rgbToHsv(255, 255, 255);
96
+ expect(hsv).toEqual({ h: 0, s: 0, v: 100 });
97
+ expect(hsvToRgb(0, 0, 100)).toEqual({ r: 255, g: 255, b: 255 });
98
+ });
99
+
100
+ it('should convert black correctly', () => {
101
+ const hsv = rgbToHsv(0, 0, 0);
102
+ expect(hsv).toEqual({ h: 0, s: 0, v: 0 });
103
+ expect(hsvToRgb(0, 0, 0)).toEqual({ r: 0, g: 0, b: 0 });
104
+ });
105
+
106
+ it('should roundtrip an arbitrary color within rounding tolerance', () => {
107
+ const hsv = rgbToHsv(100, 150, 200);
108
+ const rgb = hsvToRgb(hsv.h, hsv.s, hsv.v);
109
+ // Double rounding (RGB->HSV integers->RGB integers) can drift by +/-1
110
+ expect(Math.abs(rgb.r - 100)).toBeLessThanOrEqual(1);
111
+ expect(Math.abs(rgb.g - 150)).toBeLessThanOrEqual(1);
112
+ expect(Math.abs(rgb.b - 200)).toBeLessThanOrEqual(1);
113
+ });
114
+ });
115
+
116
+ // --- rgbToHsl / hslToRgb roundtrip ---
117
+
118
+ describe('rgbToHsl / hslToRgb', () => {
119
+ it('should convert red correctly', () => {
120
+ const hsl = rgbToHsl(255, 0, 0);
121
+ expect(hsl).toEqual({ h: 0, s: 100, l: 50 });
122
+ expect(hslToRgb(0, 100, 50)).toEqual({ r: 255, g: 0, b: 0 });
123
+ });
124
+
125
+ it('should convert white correctly', () => {
126
+ const hsl = rgbToHsl(255, 255, 255);
127
+ expect(hsl).toEqual({ h: 0, s: 0, l: 100 });
128
+ });
129
+
130
+ it('should convert black correctly', () => {
131
+ const hsl = rgbToHsl(0, 0, 0);
132
+ expect(hsl).toEqual({ h: 0, s: 0, l: 0 });
133
+ });
134
+
135
+ it('should roundtrip an arbitrary color within rounding tolerance', () => {
136
+ const hsl = rgbToHsl(100, 150, 200);
137
+ const rgb = hslToRgb(hsl.h, hsl.s, hsl.l);
138
+ // Double rounding (RGB->HSL integers->RGB integers) can drift by +/-1
139
+ expect(Math.abs(rgb.r - 100)).toBeLessThanOrEqual(1);
140
+ expect(Math.abs(rgb.g - 150)).toBeLessThanOrEqual(1);
141
+ expect(Math.abs(rgb.b - 200)).toBeLessThanOrEqual(1);
142
+ });
143
+ });
144
+
145
+ // --- lerpColor ---
146
+
147
+ describe('lerpColor', () => {
148
+ it('should return color1 at t=0', () => {
149
+ expect(lerpColor('#FF0000', '#0000FF', 0)).toBe('#FF0000');
150
+ });
151
+
152
+ it('should return color2 at t=1', () => {
153
+ expect(lerpColor('#FF0000', '#0000FF', 1)).toBe('#0000FF');
154
+ });
155
+
156
+ it('should return a midpoint at t=0.5', () => {
157
+ const mid = lerpColor('#FF0000', '#0000FF', 0.5);
158
+ // The midpoint in OKLab space is roughly a purple/violet; just check it parses
159
+ expect(mid).toMatch(/^#[0-9A-F]{6}$/);
160
+ // It should not be either endpoint
161
+ expect(mid).not.toBe('#FF0000');
162
+ expect(mid).not.toBe('#0000FF');
163
+ });
164
+
165
+ it('should return the same color when lerping between identical colors', () => {
166
+ expect(lerpColor('#AABBCC', '#AABBCC', 0.5)).toBe('#AABBCC');
167
+ });
168
+ });
169
+
170
+ // --- nearestPaletteColor ---
171
+
172
+ describe('nearestPaletteColor', () => {
173
+ const palette = ['#FF0000', '#00FF00', '#0000FF', '#FFFFFF', '#000000'];
174
+
175
+ it('should return an exact match', () => {
176
+ expect(nearestPaletteColor('#FF0000', palette)).toBe('#FF0000');
177
+ });
178
+
179
+ it('should find the closest color', () => {
180
+ // A dark red should be closer to red than to green or blue
181
+ expect(nearestPaletteColor('#CC1111', palette)).toBe('#FF0000');
182
+ });
183
+
184
+ it('should return white for a very light color', () => {
185
+ expect(nearestPaletteColor('#FAFAFA', palette)).toBe('#FFFFFF');
186
+ });
187
+
188
+ it('should return the input when palette is empty', () => {
189
+ expect(nearestPaletteColor('#123456', [])).toBe('#123456');
190
+ });
191
+ });
192
+
193
+ // --- colorDistance ---
194
+
195
+ describe('colorDistance', () => {
196
+ it('should return 0 for identical colors', () => {
197
+ expect(colorDistance('#FF0000', '#FF0000')).toBe(0);
198
+ });
199
+
200
+ it('should return 0 for the same color expressed differently', () => {
201
+ // Both parse to the same RGB
202
+ expect(colorDistance('#000000', '#000000')).toBe(0);
203
+ });
204
+
205
+ it('should return a high distance for very different colors', () => {
206
+ const dist = colorDistance('#000000', '#FFFFFF');
207
+ expect(dist).toBeGreaterThan(0.5);
208
+ });
209
+
210
+ it('should return a small distance for similar colors', () => {
211
+ const dist = colorDistance('#FF0000', '#EE1111');
212
+ expect(dist).toBeLessThan(0.15);
213
+ });
214
+
215
+ it('should be symmetric', () => {
216
+ const ab = colorDistance('#FF0000', '#0000FF');
217
+ const ba = colorDistance('#0000FF', '#FF0000');
218
+ expect(ab).toBeCloseTo(ba, 10);
219
+ });
220
+ });