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,293 @@
1
+ """Tests for the MCP bridge (state serialization round-trip and REST endpoints)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pixelweaver.main import state
6
+ from pixelweaver.mcp_bridge import deserialize_into_state, serialize_state
7
+ from pixelweaver.state import CanvasState, FrameState, ProjectState, ServerState
8
+
9
+
10
+ class TestSerializationRoundTrip:
11
+ """serialize_state -> deserialize_into_state preserves all data."""
12
+
13
+ def test_empty_state_round_trips(self):
14
+ original = ServerState()
15
+ data = serialize_state(original)
16
+ restored = ServerState()
17
+ deserialize_into_state(restored, data)
18
+
19
+ assert restored.active_project is None
20
+ assert restored.projects == {}
21
+
22
+ def test_project_with_canvas_round_trips(self):
23
+ original = ServerState()
24
+ original.create_project("sprite-1", 16, 16)
25
+
26
+ data = serialize_state(original)
27
+ restored = ServerState()
28
+ deserialize_into_state(restored, data)
29
+
30
+ assert restored.active_project == "sprite-1"
31
+ assert "sprite-1" in restored.projects
32
+
33
+ project = restored.projects["sprite-1"]
34
+ assert project.width == 16
35
+ assert project.height == 16
36
+ assert "canvas-0" in project.canvases
37
+
38
+ canvas = project.canvases["canvas-0"]
39
+ assert canvas.width == 16
40
+ assert canvas.height == 16
41
+ assert len(canvas.layers) == 1
42
+ layer_id = canvas.layers[0]["id"]
43
+ assert layer_id in canvas.current_frame().pixel_data
44
+ # Default is transparent RGBA
45
+ assert len(canvas.current_frame().pixel_data[layer_id]) == 16 * 16 * 4
46
+
47
+ def test_pixel_data_preserved(self):
48
+ """Pixel data survives base64 encode/decode round-trip."""
49
+ original = ServerState()
50
+ original.create_project("art", 4, 4)
51
+ canvas = original.projects["art"].canvases["canvas-0"]
52
+ layer_id = canvas.layers[0]["id"]
53
+
54
+ # Paint some non-zero pixels
55
+ pixel_data = bytearray(canvas.current_frame().pixel_data[layer_id])
56
+ pixel_data[0:4] = [255, 0, 0, 255] # red pixel at (0, 0)
57
+ pixel_data[4:8] = [0, 255, 0, 128] # semi-transparent green at (1, 0)
58
+ canvas.current_frame().pixel_data[layer_id] = bytes(pixel_data)
59
+
60
+ data = serialize_state(original)
61
+ restored = ServerState()
62
+ deserialize_into_state(restored, data)
63
+
64
+ restored_canvas = restored.projects["art"].canvases["canvas-0"]
65
+ restored_data = restored_canvas.current_frame().pixel_data[layer_id]
66
+ assert restored_data[0:4] == bytes([255, 0, 0, 255])
67
+ assert restored_data[4:8] == bytes([0, 255, 0, 128])
68
+
69
+ def test_command_history_preserved(self):
70
+ original = ServerState()
71
+ original.create_project("hist", 8, 8)
72
+ project = original.projects["hist"]
73
+ project.command_history.append({"type": "draw_pixels", "id": "cmd-1"})
74
+ project.redo_stack.append({"type": "draw_rect", "id": "cmd-2"})
75
+
76
+ data = serialize_state(original)
77
+ restored = ServerState()
78
+ deserialize_into_state(restored, data)
79
+
80
+ rp = restored.projects["hist"]
81
+ assert len(rp.command_history) == 1
82
+ assert rp.command_history[0]["type"] == "draw_pixels"
83
+ assert len(rp.redo_stack) == 1
84
+ assert rp.redo_stack[0]["type"] == "draw_rect"
85
+
86
+ def test_multiple_projects(self):
87
+ original = ServerState()
88
+ original.create_project("a", 8, 8)
89
+ original.create_project("b", 16, 16)
90
+ original.active_project = "b"
91
+
92
+ data = serialize_state(original)
93
+ restored = ServerState()
94
+ deserialize_into_state(restored, data)
95
+
96
+ assert restored.active_project == "b"
97
+ assert "a" in restored.projects
98
+ assert "b" in restored.projects
99
+
100
+
101
+ def test_multiple_frames_round_trip(self):
102
+ """All frames with different pixel data survive serialization."""
103
+ original = ServerState()
104
+ original.create_project("anim", 2, 2)
105
+ canvas = original.projects["anim"].canvases["canvas-0"]
106
+ layer_id = canvas.layers[0]["id"]
107
+
108
+ # Paint frame 0 red
109
+ canvas.frames[0].pixel_data[layer_id] = bytes([255, 0, 0, 255] * 4)
110
+
111
+ # Add a second frame with green pixels
112
+ frame2 = FrameState(
113
+ id="frame-2-id",
114
+ duration_ms=200,
115
+ pixel_data={layer_id: bytes([0, 255, 0, 255] * 4)},
116
+ )
117
+ canvas.frames.append(frame2)
118
+ canvas.current_frame_index = 1
119
+ canvas.global_fps = 24.0
120
+
121
+ data = serialize_state(original)
122
+ restored = ServerState()
123
+ deserialize_into_state(restored, data)
124
+
125
+ rc = restored.projects["anim"].canvases["canvas-0"]
126
+ assert len(rc.frames) == 2
127
+ # Frame 0: red pixels
128
+ assert rc.frames[0].pixel_data[layer_id] == bytes([255, 0, 0, 255] * 4)
129
+ # Frame 1: green pixels, correct id and duration
130
+ assert rc.frames[1].id == "frame-2-id"
131
+ assert rc.frames[1].duration_ms == 200
132
+ assert rc.frames[1].pixel_data[layer_id] == bytes([0, 255, 0, 255] * 4)
133
+ # Canvas-level animation state preserved
134
+ assert rc.current_frame_index == 1
135
+ assert rc.global_fps == 24.0
136
+
137
+ def test_frame_ids_and_durations_preserved(self):
138
+ """Frame IDs and per-frame durations survive serialization."""
139
+ original = ServerState()
140
+ original.create_project("dur", 1, 1)
141
+ canvas = original.projects["dur"].canvases["canvas-0"]
142
+ layer_id = canvas.layers[0]["id"]
143
+
144
+ # Set a known ID and duration on the default frame
145
+ canvas.frames[0].id = "stable-id-1"
146
+ canvas.frames[0].duration_ms = 100
147
+
148
+ # Add frames with varying durations (including None = use global fps)
149
+ for i, dur in enumerate([None, 250, 500], start=2):
150
+ canvas.frames.append(FrameState(
151
+ id=f"stable-id-{i}",
152
+ duration_ms=dur,
153
+ pixel_data={layer_id: bytes(4)},
154
+ ))
155
+
156
+ data = serialize_state(original)
157
+ restored = ServerState()
158
+ deserialize_into_state(restored, data)
159
+
160
+ rc = restored.projects["dur"].canvases["canvas-0"]
161
+ assert len(rc.frames) == 4
162
+ assert [f.id for f in rc.frames] == [
163
+ "stable-id-1", "stable-id-2", "stable-id-3", "stable-id-4",
164
+ ]
165
+ assert [f.duration_ms for f in rc.frames] == [100, None, 250, 500]
166
+
167
+ def test_legacy_flat_pixel_data_deserialization(self):
168
+ """Deserializing legacy format (flat pixel_data, no frames key) works."""
169
+ # Simulate the old serialized format that lacks a 'frames' key
170
+ import base64
171
+ pixel_bytes = bytes([128, 64, 32, 255] * 4)
172
+ legacy_data = {
173
+ "projects": {
174
+ "old-proj": {
175
+ "name": "old-proj",
176
+ "width": 2,
177
+ "height": 2,
178
+ "canvases": {
179
+ "canvas-0": {
180
+ "name": "canvas-0",
181
+ "width": 2,
182
+ "height": 2,
183
+ "layers": [{"id": "layer-1", "name": "Layer 0",
184
+ "visible": True, "opacity": 1.0}],
185
+ "pixel_data": {
186
+ "layer-1": base64.b64encode(pixel_bytes).decode("ascii"),
187
+ },
188
+ },
189
+ },
190
+ "command_history": [],
191
+ "redo_stack": [],
192
+ },
193
+ },
194
+ "active_project": "old-proj",
195
+ }
196
+
197
+ restored = ServerState()
198
+ deserialize_into_state(restored, legacy_data)
199
+
200
+ canvas = restored.projects["old-proj"].canvases["canvas-0"]
201
+ assert len(canvas.frames) == 1
202
+ assert canvas.current_frame().pixel_data["layer-1"] == pixel_bytes
203
+
204
+
205
+ class TestStateSyncEndpoints:
206
+ """Test the /api/state/full and /api/state/sync REST endpoints."""
207
+
208
+ def test_get_full_state_empty(self, client):
209
+ response = client.get("/api/state/full")
210
+ assert response.status_code == 200
211
+ data = response.json()
212
+ assert data["projects"] == {}
213
+ assert data["active_project"] is None
214
+
215
+ def test_get_full_state_with_project(self, client, tmp_path):
216
+ from pixelweaver.config import set_data_dir
217
+ set_data_dir(str(tmp_path))
218
+
219
+ # Create a project via the REST API
220
+ client.post("/api/projects", json={
221
+ "name": "test-proj",
222
+ "width": 8,
223
+ "height": 8,
224
+ })
225
+
226
+ response = client.get("/api/state/full")
227
+ assert response.status_code == 200
228
+ data = response.json()
229
+ assert "test-proj" in data["projects"]
230
+ assert data["active_project"] == "test-proj"
231
+
232
+ # Check canvas and frame data are included
233
+ project = data["projects"]["test-proj"]
234
+ assert "canvas-0" in project["canvases"]
235
+ canvas = project["canvases"]["canvas-0"]
236
+ assert canvas["width"] == 8
237
+ assert len(canvas["frames"]) == 1 # one frame
238
+ assert len(canvas["frames"][0]["pixel_data"]) == 1 # one layer
239
+
240
+ def test_sync_state_overwrites(self, client):
241
+ """POST /api/state/sync replaces the server state."""
242
+ # Server starts empty
243
+ response = client.get("/api/state/full")
244
+ assert response.json()["projects"] == {}
245
+
246
+ # Build a state payload manually
247
+ s = ServerState()
248
+ s.create_project("synced", 12, 12)
249
+ payload = serialize_state(s)
250
+
251
+ # Push it
252
+ response = client.post("/api/state/sync", json=payload)
253
+ assert response.status_code == 200
254
+ assert response.json()["success"] is True
255
+
256
+ # Verify the server now has the project
257
+ response = client.get("/api/state/full")
258
+ data = response.json()
259
+ assert "synced" in data["projects"]
260
+ assert data["active_project"] == "synced"
261
+
262
+ def test_sync_state_round_trip(self, client, tmp_path):
263
+ """Full round trip: create project, pull state, modify, push back."""
264
+ from pixelweaver.config import set_data_dir
265
+ set_data_dir(str(tmp_path))
266
+
267
+ # Create project via REST
268
+ client.post("/api/projects", json={
269
+ "name": "round-trip",
270
+ "width": 4,
271
+ "height": 4,
272
+ })
273
+
274
+ # Pull state
275
+ response = client.get("/api/state/full")
276
+ data = response.json()
277
+
278
+ # Modify locally (simulate MCP adding a command to history)
279
+ local_state = ServerState()
280
+ deserialize_into_state(local_state, data)
281
+ project = local_state.projects["round-trip"]
282
+ project.command_history.append({"type": "draw_pixels", "id": "test-cmd"})
283
+
284
+ # Push back
285
+ modified = serialize_state(local_state)
286
+ response = client.post("/api/state/sync", json=modified)
287
+ assert response.status_code == 200
288
+
289
+ # Verify the server has the command in history
290
+ response = client.get("/api/state/full")
291
+ proj_data = response.json()["projects"]["round-trip"]
292
+ assert len(proj_data["command_history"]) == 1
293
+ assert proj_data["command_history"][0]["id"] == "test-cmd"
@@ -0,0 +1,34 @@
1
+ """Tests for the MCP sequential lock."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from pixelweaver.mcp_lock import MCPLock
8
+
9
+
10
+ class TestMCPLock:
11
+ @pytest.mark.asyncio
12
+ async def test_acquire_release(self):
13
+ """Lock can be acquired and released."""
14
+ lock = MCPLock()
15
+ assert not lock.is_locked
16
+ await lock.acquire()
17
+ assert lock.is_locked
18
+ lock.release()
19
+ assert not lock.is_locked
20
+
21
+ @pytest.mark.asyncio
22
+ async def test_is_locked_reflects_state(self):
23
+ """is_locked accurately reports the lock state across transitions."""
24
+ lock = MCPLock()
25
+ assert not lock.is_locked
26
+ await lock.acquire()
27
+ assert lock.is_locked
28
+ lock.release()
29
+ assert not lock.is_locked
30
+ # Re-acquire
31
+ await lock.acquire()
32
+ assert lock.is_locked
33
+ lock.release()
34
+ assert not lock.is_locked