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,383 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import * as tree from './layer-tree.svelte.js';
3
+
4
+ // --- Setup ---
5
+
6
+ beforeEach(() => {
7
+ tree._resetForTesting();
8
+ });
9
+
10
+ // --- Add / Remove ---
11
+
12
+ describe('addLayer', () => {
13
+ it('should add a pixel layer to the tree', () => {
14
+ const layer = tree.addLayer('Background');
15
+ expect(layer.type).toBe('pixel');
16
+ expect(layer.name).toBe('Background');
17
+ expect(tree.getLayers()).toHaveLength(1);
18
+ expect(tree.getLayers()[0]).toBe(layer);
19
+ });
20
+
21
+ it('should set the new layer as active', () => {
22
+ const layer = tree.addLayer('Layer 1');
23
+ expect(tree.getActiveLayerId()).toBe(layer.id);
24
+ });
25
+
26
+ it('should add above the active layer by default', () => {
27
+ const l1 = tree.addLayer('Bottom');
28
+ const l2 = tree.addLayer('Top');
29
+ // l1 is at index 0 (bottom), l2 at index 1 (above)
30
+ expect(tree.getLayers()[0]).toBe(l1);
31
+ expect(tree.getLayers()[1]).toBe(l2);
32
+ });
33
+
34
+ it('should add a layer into a group', () => {
35
+ tree.addLayer('Outside');
36
+ const group = tree.addGroup('Group');
37
+ const inside = tree.addLayer('Inside', { parentId: group.id });
38
+ expect(group.children).toHaveLength(1);
39
+ expect(group.children?.[0]).toBe(inside);
40
+ });
41
+
42
+ it('should add at a specific index', () => {
43
+ tree.addLayer('First');
44
+ tree.addLayer('Third');
45
+ const second = tree.addLayer('Second', { index: 1 });
46
+ expect(tree.getLayers()[1]).toBe(second);
47
+ });
48
+ });
49
+
50
+ describe('addGroup', () => {
51
+ it('should add a group layer to the tree', () => {
52
+ const group = tree.addGroup('Skin');
53
+ expect(group.type).toBe('group');
54
+ expect(group.children).toEqual([]);
55
+ expect(tree.getLayers()).toHaveLength(1);
56
+ });
57
+
58
+ it('should not change active layer (groups cannot be active)', () => {
59
+ const pixel = tree.addLayer('Pixel');
60
+ tree.addGroup('Group');
61
+ expect(tree.getActiveLayerId()).toBe(pixel.id);
62
+ });
63
+ });
64
+
65
+ describe('removeLayer', () => {
66
+ it('should remove a layer from the tree', () => {
67
+ const layer = tree.addLayer('Doomed');
68
+ tree.removeLayer(layer.id);
69
+ expect(tree.getLayers()).toHaveLength(0);
70
+ });
71
+
72
+ it('should select nearest pixel layer when the active layer is removed', () => {
73
+ const l1 = tree.addLayer('Layer 1');
74
+ const l2 = tree.addLayer('Layer 2');
75
+ expect(tree.getActiveLayerId()).toBe(l2.id);
76
+
77
+ tree.removeLayer(l2.id);
78
+ expect(tree.getActiveLayerId()).toBe(l1.id);
79
+ });
80
+
81
+ it('should clear active layer ID when all layers are removed', () => {
82
+ const layer = tree.addLayer('Only');
83
+ tree.removeLayer(layer.id);
84
+ expect(tree.getActiveLayerId()).toBe('');
85
+ });
86
+
87
+ it('should remove a group and reassign active if active was inside', () => {
88
+ // Create an outside layer, then a group with a child inside it
89
+ const outside = tree.addLayer('Outside');
90
+ const group = tree.addGroup('Group');
91
+ const inside = tree.addLayer('Inside', { parentId: group.id });
92
+ tree.setActiveLayer(inside.id);
93
+
94
+ tree.removeLayer(group.id);
95
+ // Active should have switched to the nearest remaining pixel layer
96
+ expect(tree.getActiveLayerId()).toBe(outside.id);
97
+ });
98
+ });
99
+
100
+ // --- Duplicate ---
101
+
102
+ describe('duplicateLayer', () => {
103
+ it('should create a copy with a new ID', () => {
104
+ const original = tree.addLayer('Original');
105
+ const clone = tree.duplicateLayer(original.id);
106
+ expect(clone.id).not.toBe(original.id);
107
+ expect(clone.name).toBe('Original copy');
108
+ expect(tree.getLayers()).toHaveLength(2);
109
+ });
110
+
111
+ it('should place the copy above the original', () => {
112
+ const original = tree.addLayer('Original');
113
+ const clone = tree.duplicateLayer(original.id);
114
+ expect(tree.getLayers()[0]).toBe(original);
115
+ expect(tree.getLayers()[1]).toBe(clone);
116
+ });
117
+
118
+ it('should duplicate a group with all children', () => {
119
+ const group = tree.addGroup('Group');
120
+ tree.addLayer('Child', { parentId: group.id });
121
+ const clone = tree.duplicateLayer(group.id);
122
+ expect(clone.type).toBe('group');
123
+ expect(clone.children).toHaveLength(1);
124
+ expect(clone.children?.[0]?.id).not.toBe(group.children?.[0]?.id);
125
+ });
126
+ });
127
+
128
+ // --- Move ---
129
+
130
+ describe('moveLayer', () => {
131
+ it('should move a layer into a group', () => {
132
+ const layer = tree.addLayer('Movable');
133
+ const group = tree.addGroup('Target');
134
+ tree.moveLayer(layer.id, group.id, 0);
135
+ expect(tree.getLayers()).toHaveLength(1); // only the group at root
136
+ expect(group.children).toHaveLength(1);
137
+ expect(group.children?.[0]).toBe(layer);
138
+ });
139
+
140
+ it('should move a layer out of a group to root', () => {
141
+ const group = tree.addGroup('Group');
142
+ const layer = tree.addLayer('Inside', { parentId: group.id });
143
+ tree.moveLayer(layer.id, null, 0);
144
+ expect(tree.getLayers()[0]).toBe(layer);
145
+ expect(group.children).toHaveLength(0);
146
+ });
147
+
148
+ it('should reorder layers at root level', () => {
149
+ const l1 = tree.addLayer('First');
150
+ const l2 = tree.addLayer('Second');
151
+ // Move l2 to index 0 (below l1)
152
+ tree.moveLayer(l2.id, null, 0);
153
+ expect(tree.getLayers()[0]).toBe(l2);
154
+ expect(tree.getLayers()[1]).toBe(l1);
155
+ });
156
+ });
157
+
158
+ // --- Property mutations ---
159
+
160
+ describe('renameLayer', () => {
161
+ it('should change the layer name', () => {
162
+ const layer = tree.addLayer('Old Name');
163
+ tree.renameLayer(layer.id, 'New Name');
164
+ expect(layer.name).toBe('New Name');
165
+ });
166
+ });
167
+
168
+ describe('setVisibility', () => {
169
+ it('should toggle visibility', () => {
170
+ const layer = tree.addLayer('Layer');
171
+ expect(layer.visible).toBe(true);
172
+ tree.setVisibility(layer.id, false);
173
+ expect(layer.visible).toBe(false);
174
+ });
175
+ });
176
+
177
+ describe('setOpacity', () => {
178
+ it('should set opacity and clamp to 0-100', () => {
179
+ const layer = tree.addLayer('Layer');
180
+ tree.setOpacity(layer.id, 50);
181
+ expect(layer.opacity).toBe(50);
182
+ tree.setOpacity(layer.id, -10);
183
+ expect(layer.opacity).toBe(0);
184
+ tree.setOpacity(layer.id, 200);
185
+ expect(layer.opacity).toBe(100);
186
+ });
187
+ });
188
+
189
+ describe('setBlendMode', () => {
190
+ it('should change blend mode', () => {
191
+ const layer = tree.addLayer('Layer');
192
+ tree.setBlendMode(layer.id, 'multiply');
193
+ expect(layer.blendMode).toBe('multiply');
194
+ });
195
+ });
196
+
197
+ describe('setLocked', () => {
198
+ it('should set locked state', () => {
199
+ const layer = tree.addLayer('Layer');
200
+ tree.setLocked(layer.id, true);
201
+ expect(layer.locked).toBe(true);
202
+ });
203
+ });
204
+
205
+ describe('setActiveLayer', () => {
206
+ it('should set the active layer to a pixel layer', () => {
207
+ const l1 = tree.addLayer('Layer 1');
208
+ tree.addLayer('Layer 2');
209
+ tree.setActiveLayer(l1.id);
210
+ expect(tree.getActiveLayerId()).toBe(l1.id);
211
+ });
212
+
213
+ it('should throw when setting active to a group', () => {
214
+ tree.addLayer('Pixel');
215
+ const group = tree.addGroup('Group');
216
+ expect(() => { tree.setActiveLayer(group.id); }).toThrow();
217
+ });
218
+ });
219
+
220
+ describe('toggleExpanded', () => {
221
+ it('should toggle expanded state on a group', () => {
222
+ const group = tree.addGroup('Group');
223
+ expect(group.expanded).toBe(true);
224
+ tree.toggleExpanded(group.id);
225
+ expect(group.expanded).toBe(false);
226
+ tree.toggleExpanded(group.id);
227
+ expect(group.expanded).toBe(true);
228
+ });
229
+ });
230
+
231
+ // --- Group / Ungroup ---
232
+
233
+ describe('groupLayers', () => {
234
+ it('should wrap layers in a new group', () => {
235
+ const l1 = tree.addLayer('A');
236
+ const l2 = tree.addLayer('B');
237
+ const group = tree.groupLayers([l1.id, l2.id], 'My Group');
238
+ expect(group.type).toBe('group');
239
+ expect(group.children).toHaveLength(2);
240
+ expect(group.children?.[0]).toBe(l1);
241
+ expect(group.children?.[1]).toBe(l2);
242
+ expect(tree.getLayers()).toHaveLength(1);
243
+ expect(tree.getLayers()[0]).toBe(group);
244
+ });
245
+
246
+ it('should throw if layers are not siblings', () => {
247
+ // Place outside explicitly at root level so it's not a sibling of inside
248
+ const outside = tree.addLayer('Outside', { index: 0 });
249
+ const group = tree.addGroup('Group');
250
+ const inside = tree.addLayer('Inside', { parentId: group.id });
251
+ expect(() => tree.groupLayers([inside.id, outside.id], 'Bad')).toThrow('siblings');
252
+ });
253
+ });
254
+
255
+ describe('ungroupLayer', () => {
256
+ it('should dissolve a group, moving children to parent', () => {
257
+ const l1 = tree.addLayer('A');
258
+ const l2 = tree.addLayer('B');
259
+ const group = tree.groupLayers([l1.id, l2.id], 'Group');
260
+
261
+ tree.ungroupLayer(group.id);
262
+ expect(tree.getLayers()).toHaveLength(2);
263
+ expect(tree.getLayers()[0]).toBe(l1);
264
+ expect(tree.getLayers()[1]).toBe(l2);
265
+ });
266
+ });
267
+
268
+ // --- Queries ---
269
+
270
+ describe('getLayer', () => {
271
+ it('should find a layer anywhere in the tree', () => {
272
+ const group = tree.addGroup('Group');
273
+ const child = tree.addLayer('Child', { parentId: group.id });
274
+ expect(tree.getLayer(child.id)).toBe(child);
275
+ });
276
+
277
+ it('should return undefined for nonexistent ID', () => {
278
+ expect(tree.getLayer('nonexistent')).toBeUndefined();
279
+ });
280
+ });
281
+
282
+ describe('getParent', () => {
283
+ it('should return the parent group of a nested layer', () => {
284
+ const group = tree.addGroup('Group');
285
+ const child = tree.addLayer('Child', { parentId: group.id });
286
+ expect(tree.getParent(child.id)).toBe(group);
287
+ });
288
+
289
+ it('should return undefined for root-level layers', () => {
290
+ const layer = tree.addLayer('Root');
291
+ expect(tree.getParent(layer.id)).toBeUndefined();
292
+ });
293
+ });
294
+
295
+ describe('getPath', () => {
296
+ it('should return path from root to a nested layer', () => {
297
+ const group = tree.addGroup('Group');
298
+ const child = tree.addLayer('Child', { parentId: group.id });
299
+ const path = tree.getPath(child.id);
300
+ expect(path).toHaveLength(2);
301
+ expect(path[0]).toBe(group);
302
+ expect(path[1]).toBe(child);
303
+ });
304
+ });
305
+
306
+ describe('getFlatList', () => {
307
+ it('should return layers in depth-first order', () => {
308
+ const l1 = tree.addLayer('Bottom');
309
+ const group = tree.addGroup('Group');
310
+ const child = tree.addLayer('GroupChild', { parentId: group.id });
311
+ const l2 = tree.addLayer('Top');
312
+ const flat = tree.getFlatList();
313
+ expect(flat.map((l) => l.id)).toEqual([l1.id, group.id, child.id, l2.id]);
314
+ });
315
+ });
316
+
317
+ describe('getAllPixelLayers', () => {
318
+ it('should return only pixel layers', () => {
319
+ const l1 = tree.addLayer('Pixel 1');
320
+ tree.addGroup('Group');
321
+ const l2 = tree.addLayer('Pixel 2');
322
+ const pixels = tree.getAllPixelLayers();
323
+ expect(pixels.map((l) => l.id)).toEqual([l1.id, l2.id]);
324
+ });
325
+ });
326
+
327
+ describe('getVisiblePixelLayers', () => {
328
+ it('should skip hidden layers', () => {
329
+ const l1 = tree.addLayer('Visible');
330
+ const l2 = tree.addLayer('Hidden');
331
+ tree.setVisibility(l2.id, false);
332
+ const visible = tree.getVisiblePixelLayers();
333
+ expect(visible.map((l) => l.id)).toEqual([l1.id]);
334
+ });
335
+
336
+ it('should skip children of hidden groups', () => {
337
+ // Add a root-level layer first so it becomes active
338
+ const outside = tree.addLayer('Outside');
339
+ const group = tree.addGroup('Group');
340
+ tree.addLayer('Inside', { parentId: group.id });
341
+ tree.setVisibility(group.id, false);
342
+ const visible = tree.getVisiblePixelLayers();
343
+ expect(visible.map((l) => l.id)).toEqual([outside.id]);
344
+ });
345
+ });
346
+
347
+ // --- Serialization ---
348
+
349
+ describe('serialize / deserialize', () => {
350
+ it('should roundtrip the tree structure', () => {
351
+ const l1 = tree.addLayer('Bottom');
352
+ const group = tree.addGroup('Group');
353
+ tree.addLayer('Child', { parentId: group.id });
354
+ // Explicitly add Top at root level (active is currently Child inside the group)
355
+ tree.addLayer('Top', { index: tree.getLayers().length });
356
+ tree.setActiveLayer(l1.id);
357
+
358
+ const snapshot = tree.serialize();
359
+
360
+ // Verify it's a plain object (not reactive references)
361
+ expect(JSON.parse(JSON.stringify(snapshot))).toEqual(snapshot);
362
+
363
+ tree._resetForTesting();
364
+ expect(tree.getLayers()).toHaveLength(0);
365
+
366
+ tree.deserialize(snapshot);
367
+ expect(tree.getLayers()).toHaveLength(3);
368
+ expect(tree.getActiveLayerId()).toBe(l1.id);
369
+ expect(tree.getLayers()[1]?.type).toBe('group');
370
+ expect(tree.getLayers()[1]?.children).toHaveLength(1);
371
+ });
372
+
373
+ it('should not share references between original and deserialized tree', () => {
374
+ tree.addLayer('Layer');
375
+ const snapshot = tree.serialize();
376
+ tree.deserialize(snapshot);
377
+ // Mutating the deserialized tree should not affect the snapshot
378
+ const firstId = tree.getLayers()[0]?.id;
379
+ if (!firstId) throw new Error('missing first layer');
380
+ tree.renameLayer(firstId, 'Changed');
381
+ expect(snapshot.layers[0]?.name).toBe('Layer');
382
+ });
383
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Layer type definitions for the PixelWeaver layer system.
3
+ *
4
+ * Layers are the primary organizational unit for pixel data.
5
+ * They form a tree: groups contain children (which may be layers or nested groups).
6
+ * The ordering convention is bottom-to-top: first element in an array is drawn first.
7
+ */
8
+
9
+ export type BlendMode = 'normal' | 'multiply' | 'screen' | 'overlay';
10
+
11
+ export interface Layer {
12
+ id: string;
13
+ name: string;
14
+ type: 'pixel' | 'group';
15
+ visible: boolean;
16
+ opacity: number; // 0-100
17
+ blendMode: BlendMode;
18
+ locked: boolean;
19
+
20
+ // Group-only properties:
21
+ children?: Layer[];
22
+ expanded?: boolean; // UI state: is the group expanded in the layer panel?
23
+ }
24
+
25
+ export interface LayerTreeState {
26
+ layers: Layer[];
27
+ activeLayerId: string;
28
+ }
29
+
30
+ /** Create a new pixel layer with sensible defaults. */
31
+ export function createPixelLayer(name: string, id?: string): Layer {
32
+ return {
33
+ id: id ?? crypto.randomUUID(),
34
+ name,
35
+ type: 'pixel',
36
+ visible: true,
37
+ opacity: 100,
38
+ blendMode: 'normal',
39
+ locked: false,
40
+ };
41
+ }
42
+
43
+ /** Create a new group layer with sensible defaults. */
44
+ export function createGroupLayer(name: string, id?: string): Layer {
45
+ return {
46
+ id: id ?? crypto.randomUUID(),
47
+ name,
48
+ type: 'group',
49
+ visible: true,
50
+ opacity: 100,
51
+ blendMode: 'normal',
52
+ locked: false,
53
+ children: [],
54
+ expanded: true,
55
+ };
56
+ }