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,42 @@
1
+ /**
2
+ * Directory Format Adapter -- converts ProjectSnapshot to/from a flat file map.
3
+ *
4
+ * Produces a Record<string, Uint8Array> where keys are relative paths
5
+ * (e.g. "project.json", "canvases/main/frames/0/layer-abc.raw") and
6
+ * values are file contents. The storage backend handles writing these
7
+ * to the actual filesystem (desktop) or packaging them (browser download as ZIP).
8
+ */
9
+
10
+ import type { ProjectSnapshot } from './project-snapshot.js';
11
+ import { SNAPSHOT_VERSION } from './project-snapshot.js';
12
+
13
+ /** A flat map of relative-path -> file-contents. */
14
+ export type FileMap = Record<string, Uint8Array>;
15
+
16
+ /** Convert a ProjectSnapshot into a directory file map. */
17
+ export function snapshotToFileMap(snapshot: ProjectSnapshot): FileMap {
18
+ // For now the entire snapshot is stored as a single JSON file.
19
+ // Future iterations will split pixel data into per-layer PNG files
20
+ // under canvases/<name>/frames/<index>/layer-<id>.png.
21
+ const json = JSON.stringify(snapshot, null, 2);
22
+ const encoder = new TextEncoder();
23
+ return {
24
+ 'project.json': encoder.encode(json),
25
+ };
26
+ }
27
+
28
+ /** Convert a directory file map back into a ProjectSnapshot. */
29
+ export function fileMapToSnapshot(files: FileMap): ProjectSnapshot {
30
+ const projectJson = files['project.json'];
31
+ if (!projectJson) throw new Error('Invalid project directory: missing project.json');
32
+
33
+ const decoder = new TextDecoder();
34
+ const json = decoder.decode(projectJson);
35
+ const snapshot = JSON.parse(json) as ProjectSnapshot;
36
+
37
+ if (snapshot.version !== SNAPSHOT_VERSION) {
38
+ console.warn(`Snapshot version mismatch: expected ${String(SNAPSHOT_VERSION)}, got ${String(snapshot.version)}`);
39
+ }
40
+
41
+ return snapshot;
42
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Project Snapshot -- format-agnostic project serialization.
3
+ *
4
+ * Gathers all project state into a single ProjectSnapshot object,
5
+ * and restores it from one. Format adapters (ZIP, directory) convert
6
+ * between ProjectSnapshot and on-disk representation.
7
+ */
8
+
9
+ // Canvas must be deserialized first -- layers and frames depend on dimensions.
10
+ import {
11
+ serializeCanvasState,
12
+ deserializeCanvasState,
13
+ } from '../canvas/canvas-state.svelte.js';
14
+
15
+ // Layer tree (structure, names, visibility, blend modes).
16
+ import {
17
+ serialize as serializeLayers,
18
+ deserialize as deserializeLayers,
19
+ } from '../layers/layer-tree.svelte.js';
20
+
21
+ // Per-frame pixel data, FPS, origin.
22
+ import {
23
+ serialize as serializeFrames,
24
+ deserialize as deserializeFrames,
25
+ } from '../animation/frame-model.svelte.js';
26
+
27
+ // Named frame ranges (animation tags / sub-clips).
28
+ import {
29
+ serialize as serializeFrameTags,
30
+ deserialize as deserializeFrameTags,
31
+ } from '../animation/frame-tags.svelte.js';
32
+
33
+ // Variant presets (palette swap overrides per layer group).
34
+ import {
35
+ serialize as serializeVariants,
36
+ deserialize as deserializeVariants,
37
+ } from '../variants/variant-state.svelte.js';
38
+
39
+ // Level editor map state (tile grid, entities, collision shapes).
40
+ import { mapState } from '../leveleditor/map-state.svelte.js';
41
+
42
+ // Foreground/background colors and recent-color history.
43
+ import {
44
+ serializeColorState,
45
+ deserializeColorState,
46
+ } from '../color/color-state.svelte.js';
47
+
48
+ // Project-level palette and lock mode.
49
+ import {
50
+ serialize as serializePalette,
51
+ deserialize as deserializePalette,
52
+ } from '../color/palette-state.svelte.js';
53
+
54
+ // Reset undo/redo stacks when loading a different project.
55
+ import { clearHistory } from '../core/dispatcher.js';
56
+
57
+ // Clear stale action-log entries so the panel doesn't show previous-session commands.
58
+ import { actionLog } from '../history/action-log.svelte.js';
59
+
60
+ /** Version of the snapshot format for future migration. */
61
+ export const SNAPSHOT_VERSION = 1;
62
+
63
+ /** Complete serialized project state. */
64
+ export interface ProjectSnapshot {
65
+ /** Format version -- bump when the shape changes. */
66
+ version: number;
67
+ /** Human-readable project name (displayed in title bar, file pickers, etc.). */
68
+ name: string;
69
+ /** Canvas pixel dimensions. */
70
+ canvas: { width: number; height: number };
71
+ /** Layer tree structure, blend modes, visibility, active layer. */
72
+ layers: unknown;
73
+ /** Animation frames with per-layer pixel data, FPS, origin. */
74
+ frames: unknown;
75
+ /** Named frame ranges / animation sub-clips. */
76
+ frameTags: unknown;
77
+ /** Variant presets (per-group palette swap overrides). */
78
+ variants: unknown;
79
+ /** Level editor map: tile grid, entities, collision shapes. */
80
+ map: unknown;
81
+ /** Foreground/background colors and recent-color history. */
82
+ colors: unknown;
83
+ /** Project-level palette and lock mode. */
84
+ palette: unknown;
85
+ }
86
+
87
+ /** Collect all project state into a snapshot. */
88
+ export function gatherSnapshot(projectName: string): ProjectSnapshot {
89
+ return {
90
+ version: SNAPSHOT_VERSION,
91
+ name: projectName,
92
+ canvas: serializeCanvasState(),
93
+ layers: serializeLayers(),
94
+ frames: serializeFrames(),
95
+ frameTags: serializeFrameTags(),
96
+ variants: serializeVariants(),
97
+ map: mapState.serialize(),
98
+ colors: serializeColorState(),
99
+ palette: serializePalette(),
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Apply a snapshot to restore project state.
105
+ *
106
+ * Ordering matters: canvas dimensions are restored first because layer
107
+ * and frame deserialization may reference canvas size. Undo/redo history
108
+ * is cleared last so that stale commands from the previous project cannot
109
+ * corrupt the freshly loaded state.
110
+ */
111
+ export function applySnapshot(snapshot: ProjectSnapshot): void {
112
+ // 1. Canvas dimensions first -- layers and frames depend on these.
113
+ deserializeCanvasState(snapshot.canvas as { width: number; height: number });
114
+
115
+ // 2. Layer structure before frames (frames reference layer IDs).
116
+ deserializeLayers(snapshot.layers as Parameters<typeof deserializeLayers>[0]);
117
+
118
+ // 3. Frame pixel data, FPS, origin.
119
+ deserializeFrames(snapshot.frames as Parameters<typeof deserializeFrames>[0]);
120
+
121
+ // 4. Frame tags (depend on frame indices being settled).
122
+ deserializeFrameTags(snapshot.frameTags as Parameters<typeof deserializeFrameTags>[0]);
123
+
124
+ // 5. Variant presets (reference layer group IDs, so layers must exist).
125
+ deserializeVariants(snapshot.variants as Parameters<typeof deserializeVariants>[0]);
126
+
127
+ // 6. Level editor map state.
128
+ mapState.deserialize(snapshot.map as Parameters<typeof mapState.deserialize>[0]);
129
+
130
+ // 7. Color state (independent, but restored after canvas for consistency).
131
+ deserializeColorState(snapshot.colors as Parameters<typeof deserializeColorState>[0]);
132
+
133
+ // 8. Palette state.
134
+ deserializePalette(snapshot.palette as Parameters<typeof deserializePalette>[0]);
135
+
136
+ // 9. Clear undo/redo so stale commands from the previous project are gone.
137
+ clearHistory();
138
+ actionLog.clearAll();
139
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Recent Projects -- persists a list of recently opened/saved projects
3
+ * in localStorage so the New Project dialog can offer quick access.
4
+ */
5
+
6
+ const STORAGE_KEY = 'pixelweaver:recent-projects';
7
+ const MAX_ENTRIES = 10;
8
+
9
+ export interface RecentProject {
10
+ /** Display name of the project. */
11
+ name: string;
12
+ /** File path (desktop) or file name (browser). */
13
+ path: string;
14
+ /** Unix timestamp (ms) of last save/open. */
15
+ timestamp: number;
16
+ /** Human-readable dimensions, e.g. "32x32". */
17
+ dimensions: string;
18
+ }
19
+
20
+ /** Read recent projects from localStorage, most recent first. */
21
+ export function getRecentProjects(): RecentProject[] {
22
+ try {
23
+ const raw = localStorage.getItem(STORAGE_KEY);
24
+ if (!raw) return [];
25
+ const parsed = JSON.parse(raw);
26
+ if (!Array.isArray(parsed)) return [];
27
+ return parsed as RecentProject[];
28
+ } catch {
29
+ return [];
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Add or update a project in the recent list.
35
+ * If a project with the same path already exists, it is moved to the top
36
+ * and its metadata is updated. The list is capped at MAX_ENTRIES.
37
+ */
38
+ export function addRecentProject(entry: RecentProject): void {
39
+ const list = getRecentProjects().filter((p) => p.path !== entry.path);
40
+ list.unshift(entry);
41
+ if (list.length > MAX_ENTRIES) list.length = MAX_ENTRIES;
42
+ try {
43
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
44
+ } catch {
45
+ // Storage full or unavailable
46
+ }
47
+ }
48
+
49
+ /** Remove all recent projects from localStorage. */
50
+ export function clearRecentProjects(): void {
51
+ try {
52
+ localStorage.removeItem(STORAGE_KEY);
53
+ } catch {
54
+ // Ignore
55
+ }
56
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Save State -- tracks the current project's save path and format.
3
+ *
4
+ * When a project is saved for the first time, savePath and saveFormat
5
+ * are set. Subsequent Ctrl+S re-saves to the same location silently.
6
+ * Ctrl+Shift+S re-opens the Save As dialog.
7
+ */
8
+
9
+ /** 'zip' = .pwv file, 'directory' = folder on disk */
10
+ export type SaveFormat = 'zip' | 'directory';
11
+
12
+ let savePath = $state<string | null>(null);
13
+ let saveFormat = $state<SaveFormat | null>(null);
14
+ let projectName = $state('Untitled');
15
+
16
+ export const saveState = {
17
+ get savePath() { return savePath; },
18
+ set savePath(v: string | null) { savePath = v; },
19
+
20
+ get saveFormat() { return saveFormat; },
21
+ set saveFormat(v: SaveFormat | null) { saveFormat = v; },
22
+
23
+ get projectName() { return projectName; },
24
+ set projectName(v: string) { projectName = v; },
25
+
26
+ /** Reset save state (e.g. when creating a new project). */
27
+ reset() {
28
+ savePath = null;
29
+ saveFormat = null;
30
+ projectName = 'Untitled';
31
+ },
32
+
33
+ /** Whether the project has been saved at least once. */
34
+ get hasSavePath() { return savePath !== null; },
35
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Storage -- platform-adaptive save/load facade.
3
+ *
4
+ * Detects whether the app runs inside a pywebview desktop window or a
5
+ * plain browser. Desktop uses native file dialogs and filesystem access
6
+ * via the pywebview bridge; browser falls back to download + file input.
7
+ */
8
+
9
+ import { packZip, unpackZip } from './zip-format.js';
10
+ import { snapshotToFileMap } from './directory-format.js';
11
+ import type { ProjectSnapshot } from './project-snapshot.js';
12
+ import type { SaveFormat } from './save-state.svelte.js';
13
+
14
+ // --- Base64 helpers ---
15
+
16
+ function uint8ArrayToBase64(bytes: Uint8Array): string {
17
+ let binary = '';
18
+ for (let i = 0; i < bytes.length; i++) {
19
+ binary += String.fromCharCode(bytes[i]!);
20
+ }
21
+ return btoa(binary);
22
+ }
23
+
24
+ function base64ToUint8Array(b64: string): Uint8Array {
25
+ const binary = atob(b64);
26
+ const bytes = new Uint8Array(binary.length);
27
+ for (let i = 0; i < binary.length; i++) {
28
+ bytes[i] = binary.charCodeAt(i);
29
+ }
30
+ return bytes;
31
+ }
32
+
33
+ // --- Desktop detection ---
34
+
35
+ /** Check if running inside a pywebview desktop window. */
36
+ function isDesktop(): boolean {
37
+ return !!window.pywebview;
38
+ }
39
+
40
+ // --- Save ---
41
+
42
+ export interface SaveResult {
43
+ path: string;
44
+ format: SaveFormat;
45
+ }
46
+
47
+ /**
48
+ * Show a Save As dialog and write the project.
49
+ * Returns the chosen path+format, or null if cancelled.
50
+ */
51
+ export async function saveAs(snapshot: ProjectSnapshot): Promise<SaveResult | null> {
52
+ if (isDesktop()) {
53
+ return saveAsDesktop(snapshot);
54
+ }
55
+ return saveAsBrowser(snapshot);
56
+ }
57
+
58
+ /**
59
+ * Save silently to a known path+format (re-save).
60
+ */
61
+ export async function saveToPath(snapshot: ProjectSnapshot, path: string, format: SaveFormat): Promise<void> {
62
+ if (isDesktop()) {
63
+ await writeDesktop(snapshot, path, format);
64
+ } else {
65
+ // Browser can't re-save to a path -- fall back to download
66
+ await downloadBrowser(snapshot, format, path);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Show an Open dialog and read a project file.
72
+ * Returns the snapshot + metadata, or null if cancelled.
73
+ */
74
+ export async function openProject(): Promise<{ snapshot: ProjectSnapshot; path: string; format: SaveFormat } | null> {
75
+ if (isDesktop()) {
76
+ return openDesktop();
77
+ }
78
+ return openBrowser();
79
+ }
80
+
81
+ // --- Desktop (pywebview) implementations ---
82
+
83
+ async function saveAsDesktop(snapshot: ProjectSnapshot): Promise<SaveResult | null> {
84
+ const blob = await packZip(snapshot);
85
+ const buffer = new Uint8Array(await blob.arrayBuffer());
86
+ const b64 = uint8ArrayToBase64(buffer);
87
+ const fileName = `${snapshot.name || 'Untitled'}.pwv`;
88
+
89
+ const path = await window.pywebview!.api.save_file(
90
+ b64, fileName, 'PixelWeaver Project', ['pwv'],
91
+ );
92
+ if (!path) return null;
93
+
94
+ return { path, format: 'zip' };
95
+ }
96
+
97
+ async function writeDesktop(snapshot: ProjectSnapshot, path: string, format: SaveFormat): Promise<void> {
98
+ if (format === 'zip') {
99
+ const blob = await packZip(snapshot);
100
+ const buffer = new Uint8Array(await blob.arrayBuffer());
101
+ const b64 = uint8ArrayToBase64(buffer);
102
+ await window.pywebview!.api.write_file(path, b64);
103
+ } else {
104
+ const fileMap = snapshotToFileMap(snapshot);
105
+ const files = Object.entries(fileMap).map(([name, data]) => ({
106
+ name,
107
+ data: uint8ArrayToBase64(data),
108
+ }));
109
+ await window.pywebview!.api.write_files_to_directory(path, files);
110
+ }
111
+ }
112
+
113
+ async function openDesktop(): Promise<{ snapshot: ProjectSnapshot; path: string; format: SaveFormat } | null> {
114
+ const result = await window.pywebview!.api.open_file(
115
+ 'PixelWeaver Project', ['pwv'],
116
+ );
117
+ if (!result) return null;
118
+
119
+ const bytes = base64ToUint8Array(result.data);
120
+ const blob = new Blob([bytes]);
121
+ const snapshot = await unpackZip(blob);
122
+ return { snapshot, path: result.path, format: 'zip' };
123
+ }
124
+
125
+ // --- Browser implementations ---
126
+
127
+ async function saveAsBrowser(snapshot: ProjectSnapshot): Promise<SaveResult | null> {
128
+ // Browser: always download as ZIP
129
+ const fileName = `${snapshot.name || 'Untitled'}.pwv`;
130
+ await downloadBrowser(snapshot, 'zip', fileName);
131
+ return { path: fileName, format: 'zip' };
132
+ }
133
+
134
+ async function downloadBrowser(snapshot: ProjectSnapshot, _format: SaveFormat, fileName: string): Promise<void> {
135
+ // In the browser, directory format is not possible -- always pack as ZIP
136
+ const blob = await packZip(snapshot);
137
+ const url = URL.createObjectURL(blob);
138
+ const a = document.createElement('a');
139
+ a.href = url;
140
+ a.download = fileName.endsWith('.pwv') ? fileName : `${fileName}.pwv`;
141
+ document.body.appendChild(a);
142
+ a.click();
143
+ document.body.removeChild(a);
144
+ URL.revokeObjectURL(url);
145
+ }
146
+
147
+ async function openBrowser(): Promise<{ snapshot: ProjectSnapshot; path: string; format: SaveFormat } | null> {
148
+ const input = document.createElement('input');
149
+ input.type = 'file';
150
+ input.accept = '.pwv';
151
+
152
+ const file = await new Promise<File | null>((resolve) => {
153
+ input.onchange = () => { resolve(input.files?.[0] ?? null); };
154
+ const onFocus = () => {
155
+ setTimeout(() => {
156
+ if (!input.files?.length) resolve(null);
157
+ window.removeEventListener('focus', onFocus);
158
+ }, 300);
159
+ };
160
+ window.addEventListener('focus', onFocus);
161
+ input.click();
162
+ });
163
+
164
+ if (!file) return null;
165
+ const snapshot = await unpackZip(file);
166
+ return { snapshot, path: file.name, format: 'zip' };
167
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ZIP Format Adapter -- reads/writes ProjectSnapshot as a .pwv ZIP archive.
3
+ *
4
+ * The .pwv file is a ZIP containing a single project.json with the
5
+ * full ProjectSnapshot. ZIP compression handles the bulk of pixel data
6
+ * (RGBA arrays compress well). Future versions may split pixel data
7
+ * into separate PNG files within the archive for efficiency.
8
+ */
9
+
10
+ import type { ProjectSnapshot } from './project-snapshot.js';
11
+ import { SNAPSHOT_VERSION } from './project-snapshot.js';
12
+
13
+ /** Lazy-load fflate to keep it out of the initial bundle. */
14
+ async function getfflate() {
15
+ return await import('fflate');
16
+ }
17
+
18
+ /** Pack a ProjectSnapshot into a .pwv ZIP blob. */
19
+ export async function packZip(snapshot: ProjectSnapshot): Promise<Blob> {
20
+ const { zipSync, strToU8 } = await getfflate();
21
+ const json = JSON.stringify(snapshot);
22
+ const data = strToU8(json);
23
+ const zipped = zipSync({ 'project.json': data }, { level: 6 });
24
+ // Copy into a fresh ArrayBuffer to satisfy strict BlobPart typing
25
+ // (fflate returns Uint8Array<ArrayBufferLike> which may be SharedArrayBuffer)
26
+ const buf = new ArrayBuffer(zipped.byteLength);
27
+ new Uint8Array(buf).set(zipped);
28
+ return new Blob([buf], { type: 'application/zip' });
29
+ }
30
+
31
+ /** Unpack a .pwv ZIP blob into a ProjectSnapshot. */
32
+ export async function unpackZip(blob: Blob): Promise<ProjectSnapshot> {
33
+ const { unzipSync, strFromU8 } = await getfflate();
34
+ const buffer = new Uint8Array(await blob.arrayBuffer());
35
+ const files = unzipSync(buffer);
36
+ const projectJson = files['project.json'];
37
+ if (!projectJson) throw new Error('Invalid .pwv file: missing project.json');
38
+ const json = strFromU8(projectJson);
39
+ const snapshot = JSON.parse(json) as ProjectSnapshot;
40
+ if (snapshot.version !== SNAPSHOT_VERSION) {
41
+ // Future: add migration logic here
42
+ console.warn(`Snapshot version mismatch: expected ${String(SNAPSHOT_VERSION)}, got ${String(snapshot.version)}`);
43
+ }
44
+ return snapshot;
45
+ }
File without changes