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,375 @@
1
+ <script lang="ts">
2
+ import { onMount, mount, unmount } from 'svelte';
3
+ import { DockviewComponent } from 'dockview-core';
4
+ import type {
5
+ IContentRenderer,
6
+ GroupPanelPartInitParameters,
7
+ SerializedDockview,
8
+ } from 'dockview-core';
9
+ import { dockState } from './dock-state.svelte.js';
10
+ import type { PanelConfig } from '../core/plugin-types.js';
11
+ import { saveLayout, loadLayout, clearLayout } from './dock-persistence.js';
12
+ import { themePixelWeaver } from './dockview-theme.js';
13
+ import { notificationState } from '../ui/notifications/notification-state.svelte.js';
14
+ import { createRightHeaderActionComponent } from './header-action-renderer.js';
15
+ import { dialogState } from '../ui/dialog-state.svelte.js';
16
+
17
+ // Dockview base styles + custom theme overrides
18
+ import 'dockview-core/dist/styles/dockview.css';
19
+ import './dockview-theme.css';
20
+
21
+ interface Props {
22
+ /** Saved layout JSON. When absent, panels are arranged by their declared positions. */
23
+ initialLayout?: object;
24
+ }
25
+
26
+ let { initialLayout }: Props = $props();
27
+
28
+ let containerEl: HTMLDivElement | undefined = $state();
29
+ let dockview: DockviewComponent | undefined;
30
+
31
+ // Track mounted Svelte component instances so we can unmount them on dispose.
32
+ // Plain Map (not SvelteMap) is intentional: this is internal bookkeeping
33
+ // read only inside dockview's createComponent/dispose callbacks, not in
34
+ // reactive template code, so reactivity is unnecessary.
35
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity -- see above
36
+ const mountedComponents = new Map<
37
+ string,
38
+ { instance: Record<string, unknown>; element: HTMLElement }
39
+ >();
40
+
41
+ onMount(() => {
42
+ if (!containerEl) return;
43
+
44
+ dockview = new DockviewComponent(containerEl, {
45
+ theme: themePixelWeaver,
46
+ createRightHeaderActionComponent,
47
+ createComponent(options): IContentRenderer {
48
+ const config = dockState.getPanel(options.id);
49
+
50
+ if (!config) {
51
+ // Fallback for unknown panel ids (e.g. stale layout JSON)
52
+ const el = document.createElement('div');
53
+ el.textContent = `Unknown panel "${options.id}"`;
54
+ return {
55
+ element: el,
56
+ init() {},
57
+ dispose() {},
58
+ };
59
+ }
60
+
61
+ const el = document.createElement('div');
62
+ el.style.width = '100%';
63
+ el.style.height = '100%';
64
+ el.style.overflow = 'auto';
65
+
66
+ // Svelte instance is created lazily in init() so the element is
67
+ // already attached to the DOM when mount runs.
68
+ let comp: Record<string, unknown> | undefined;
69
+
70
+ return {
71
+ element: el,
72
+
73
+ init(_params: GroupPanelPartInitParameters) {
74
+ comp = mount(config.component, {
75
+ target: el,
76
+ props: config.props ?? {},
77
+ });
78
+ mountedComponents.set(options.id, { instance: comp, element: el });
79
+ },
80
+
81
+ dispose() {
82
+ if (comp) {
83
+ void unmount(comp);
84
+ mountedComponents.delete(options.id);
85
+ }
86
+ },
87
+ };
88
+ },
89
+ });
90
+
91
+ // Expose API to the reactive dock state singleton
92
+ dockState.setApi(dockview.api);
93
+
94
+ // Build layout: try localStorage first, then initialLayout prop, then defaults
95
+ const savedLayout = loadLayout();
96
+ if (savedLayout) {
97
+ try {
98
+ dockview.fromJSON(savedLayout as SerializedDockview);
99
+ applyPanelConstraints();
100
+ checkForMissingPanels();
101
+ } catch (e) {
102
+ console.warn('Failed to restore saved dock layout, falling back to defaults:', e);
103
+ buildDefaultLayout();
104
+ }
105
+ } else if (initialLayout) {
106
+ try {
107
+ dockview.fromJSON(initialLayout as SerializedDockview);
108
+ applyPanelConstraints();
109
+ checkForMissingPanels();
110
+ } catch (e) {
111
+ console.warn('Failed to restore dock layout, falling back to defaults:', e);
112
+ buildDefaultLayout();
113
+ }
114
+ } else {
115
+ buildDefaultLayout();
116
+ }
117
+
118
+ // On first launch (no saved layout, never welcomed), auto-open the
119
+ // New Project dialog so the user can pick their canvas size.
120
+ const WELCOMED_KEY = 'pixelweaver-welcomed';
121
+ const isFirstLaunch = !savedLayout && !initialLayout && !localStorage.getItem(WELCOMED_KEY);
122
+ let welcomeTimeout: ReturnType<typeof setTimeout> | undefined;
123
+ if (isFirstLaunch) {
124
+ welcomeTimeout = setTimeout(() => {
125
+ dialogState.openNewProject();
126
+ localStorage.setItem(WELCOMED_KEY, '1');
127
+ }, 500);
128
+ }
129
+
130
+ // Auto-save layout on changes (debounced 500ms)
131
+ let saveTimeout: ReturnType<typeof setTimeout>;
132
+ const dv = dockview;
133
+ const layoutDisposable = dv.api.onDidLayoutChange(() => {
134
+ clearTimeout(saveTimeout);
135
+ saveTimeout = setTimeout(() => {
136
+ const layout = dv.toJSON();
137
+ saveLayout(layout);
138
+ }, 500);
139
+ });
140
+
141
+ return () => {
142
+ clearTimeout(welcomeTimeout);
143
+ clearTimeout(saveTimeout);
144
+ layoutDisposable.dispose();
145
+ // Tear down every mounted Svelte component
146
+ for (const { instance } of mountedComponents.values()) {
147
+ void unmount(instance);
148
+ }
149
+ mountedComponents.clear();
150
+ dockview?.dispose();
151
+ };
152
+ });
153
+
154
+ /**
155
+ * After restoring a saved layout, check if any panels that are now
156
+ * registered were missing from that layout (e.g. panels added by plugins
157
+ * since the layout was saved). If so, push an info notification so the
158
+ * user can reset the layout to pick them up.
159
+ */
160
+ function checkForMissingPanels(): void {
161
+ if (!dockview) return;
162
+
163
+ const registered = new Set(dockState.panels.keys());
164
+ const inLayout = new Set(dockview.api.panels.map((p) => p.id));
165
+ const missing = [...registered].filter((id) => !inLayout.has(id));
166
+
167
+ if (missing.length > 0) {
168
+ const names = missing
169
+ .map((id) => dockState.getPanel(id)?.title ?? id)
170
+ .join(', ');
171
+
172
+ notificationState.push({
173
+ id: 'missing-panels',
174
+ message: `New panels available: ${names}`,
175
+ type: 'info',
176
+ priority: 10,
177
+ action: {
178
+ label: 'Reset Layout',
179
+ callback: () => {
180
+ // Clear saved layout and rebuild from scratch
181
+ clearLayout();
182
+ if (dockview) dockview.clear();
183
+ buildDefaultLayout();
184
+ notificationState.dismiss('missing-panels');
185
+ },
186
+ },
187
+ });
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Arrange registered panels into the dock according to their declared
193
+ * `position` property. Non-closeable panels (toolbars) each get their
194
+ * own split; closeable panels in the same position are tabbed together.
195
+ */
196
+ /** Entry pairing a panel's registry key with its config */
197
+ type PanelEntry = [id: string, config: PanelConfig];
198
+
199
+ function buildDefaultLayout(): void {
200
+ if (!dockview) return;
201
+ // Alias so nested closures don't re-check the nullable outer binding.
202
+ const dv = dockview;
203
+
204
+ const entries: PanelEntry[] = [...dockState.panels.entries()];
205
+
206
+ // Bucket by position
207
+ const center: PanelEntry[] = [];
208
+ const right: PanelEntry[] = [];
209
+ const bottom: PanelEntry[] = [];
210
+ const left: PanelEntry[] = [];
211
+ const top: PanelEntry[] = [];
212
+
213
+ for (const entry of entries) {
214
+ switch (entry[1].position) {
215
+ case 'right':
216
+ right.push(entry);
217
+ break;
218
+ case 'bottom':
219
+ bottom.push(entry);
220
+ break;
221
+ case 'left':
222
+ left.push(entry);
223
+ break;
224
+ case 'top':
225
+ top.push(entry);
226
+ break;
227
+ default:
228
+ // 'center' or unspecified
229
+ center.push(entry);
230
+ }
231
+ }
232
+
233
+ // Helper: add a group of panels in a given direction.
234
+ // Non-closeable panels (toolbar-like) each get their own split.
235
+ // Closeable panels are tabbed together in one group.
236
+ const addGroup = (
237
+ group: PanelEntry[],
238
+ direction: 'left' | 'right' | 'above' | 'below',
239
+ ) => {
240
+ if (group.length === 0) return;
241
+
242
+ // Separate non-closeable (toolbar-like) from closeable (regular panels)
243
+ const fixed = group.filter(([, c]) => c.closeable === false);
244
+ const tabbed = group.filter(([, c]) => c.closeable !== false);
245
+
246
+ // Each fixed panel gets its own split
247
+ for (const [id, config] of fixed) {
248
+ dv.api.addPanel({
249
+ id,
250
+ title: config.title,
251
+ component: id,
252
+ position: { direction },
253
+ initialWidth: config.minWidth,
254
+ initialHeight: config.minHeight,
255
+ minimumWidth: config.minWidth,
256
+ maximumWidth: config.maxWidth,
257
+ minimumHeight: config.minHeight,
258
+ maximumHeight: config.maxHeight,
259
+ });
260
+ }
261
+
262
+ // Closeable panels tab together in one group
263
+ const firstEntry = tabbed[0];
264
+ if (firstEntry !== undefined) {
265
+ const [firstId, firstConfig] = firstEntry;
266
+ const isHorizontal = direction === 'left' || direction === 'right';
267
+ dv.api.addPanel({
268
+ id: firstId,
269
+ title: firstConfig.title,
270
+ component: firstId,
271
+ position: { direction },
272
+ initialWidth: isHorizontal ? 200 : undefined,
273
+ initialHeight: !isHorizontal ? 120 : undefined,
274
+ minimumWidth: firstConfig.minWidth,
275
+ maximumWidth: firstConfig.maxWidth,
276
+ minimumHeight: firstConfig.minHeight,
277
+ maximumHeight: firstConfig.maxHeight,
278
+ });
279
+
280
+ for (let i = 1; i < tabbed.length; i++) {
281
+ const entry = tabbed[i];
282
+ if (entry === undefined) continue;
283
+ const [id, config] = entry;
284
+ dv.api.addPanel({
285
+ id,
286
+ title: config.title,
287
+ component: id,
288
+ position: { referencePanel: firstId, direction: 'within' },
289
+ minimumWidth: config.minWidth,
290
+ maximumWidth: config.maxWidth,
291
+ minimumHeight: config.minHeight,
292
+ maximumHeight: config.maxHeight,
293
+ });
294
+ }
295
+ }
296
+ };
297
+
298
+ // Center panels first (they become the main area)
299
+ for (const [id, config] of center) {
300
+ dv.api.addPanel({
301
+ id,
302
+ title: config.title,
303
+ component: id,
304
+ });
305
+ }
306
+
307
+ // Add groups in order: top, right, bottom, then left LAST.
308
+ // Left toolbar is added last so it spans the full dock height
309
+ // (including the bottom area), rather than being a sibling of
310
+ // only the center row.
311
+ addGroup(top, 'above');
312
+ addGroup(right, 'right');
313
+ addGroup(bottom, 'below');
314
+ addGroup(left, 'left');
315
+
316
+ // Apply group-level constraints after all panels are added
317
+ applyPanelConstraints();
318
+ }
319
+
320
+ /**
321
+ * Apply group-level constraints to panels based on their PanelConfig.
322
+ * Called after both buildDefaultLayout() and fromJSON() since dockview
323
+ * does NOT serialize constraints.
324
+ *
325
+ * Toolbar panels (fixed dimensions where min === max) get their header
326
+ * hidden and group locked. The canvas panel (closeable: false but NOT
327
+ * fixed-dimension) only gets its header hidden -- it must NOT be locked
328
+ * or it blocks resizing of adjacent panels.
329
+ */
330
+ function applyPanelConstraints(): void {
331
+ if (!dockview) return;
332
+
333
+ for (const panel of dockview.api.panels) {
334
+ const config = dockState.getPanel(panel.id);
335
+ if (!config) continue;
336
+
337
+ // A panel is "toolbar-like" if it has fixed dimensions (min === max)
338
+ const isFixedWidth = config.minWidth !== undefined && config.maxWidth !== undefined && config.minWidth === config.maxWidth;
339
+ const isFixedHeight = config.minHeight !== undefined && config.maxHeight !== undefined && config.minHeight === config.maxHeight;
340
+ const isToolbar = isFixedWidth || isFixedHeight;
341
+
342
+ if (isToolbar) {
343
+ panel.group.header.hidden = true;
344
+ // 'no-drop-target' prevents other panels being dropped into this group
345
+ panel.group.locked = 'no-drop-target';
346
+ }
347
+
348
+ // Hide header on canvas (closeable: false, not a toolbar) but DON'T lock it
349
+ if (config.closeable === false && !isToolbar) {
350
+ panel.group.header.hidden = true;
351
+ }
352
+
353
+ // Apply size constraints to the group
354
+ const constraints: Record<string, number | undefined> = {};
355
+ if (config.minWidth !== undefined) constraints.minimumWidth = config.minWidth;
356
+ if (config.maxWidth !== undefined) constraints.maximumWidth = config.maxWidth;
357
+ if (config.minHeight !== undefined) constraints.minimumHeight = config.minHeight;
358
+ if (config.maxHeight !== undefined) constraints.maximumHeight = config.maxHeight;
359
+
360
+ if (Object.keys(constraints).length > 0) {
361
+ panel.group.api.setConstraints(constraints);
362
+ }
363
+ }
364
+ }
365
+ </script>
366
+
367
+ <div class="dock-container" bind:this={containerEl}></div>
368
+
369
+ <style>
370
+ .dock-container {
371
+ width: 100%;
372
+ height: 100%;
373
+ overflow: hidden;
374
+ }
375
+ </style>
@@ -0,0 +1,90 @@
1
+ <!--
2
+ TabAddMenu -- context menu shown when the "+" button in a dockview tab bar
3
+ is clicked. Displays actions relevant to adding new content (New Project,
4
+ Open File, etc.). Positioned at the provided (x, y) coordinates.
5
+ -->
6
+ <script lang="ts">
7
+ import { executeOrDispatch, getCommandForDispatch } from '../core/command-runner.js';
8
+
9
+ interface Props {
10
+ /** Pixel position for the menu */
11
+ x: number;
12
+ y: number;
13
+ /** Close callback */
14
+ onClose: () => void;
15
+ }
16
+
17
+ let { x, y, onClose }: Props = $props();
18
+
19
+ // Static menu items that invoke registered commands
20
+ const items = [
21
+ { label: 'New Project', commandId: 'new_project_dialog' },
22
+ { label: 'Open File', commandId: 'open_file_dialog' },
23
+ ];
24
+
25
+ function handleClick(commandId: string) {
26
+ const cmd = getCommandForDispatch(commandId);
27
+ if (cmd) {
28
+ executeOrDispatch(commandId, cmd);
29
+ }
30
+ onClose();
31
+ }
32
+
33
+ function handleKeydown(e: KeyboardEvent) {
34
+ if (e.key === 'Escape') {
35
+ e.preventDefault();
36
+ onClose();
37
+ }
38
+ }
39
+ </script>
40
+
41
+ <svelte:window onkeydown={handleKeydown} />
42
+
43
+ <!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
44
+ <div class="menu-backdrop" onclick={onClose}>
45
+ <!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
46
+ <div class="menu" style:left="{x}px" style:top="{y}px" onclick={(e) => { e.stopPropagation(); }}>
47
+ {#each items as item (item.commandId)}
48
+ <button class="menu-item" onclick={() => { handleClick(item.commandId); }}>
49
+ {item.label}
50
+ </button>
51
+ {/each}
52
+ </div>
53
+ </div>
54
+
55
+ <style>
56
+ .menu-backdrop {
57
+ position: fixed;
58
+ inset: 0;
59
+ z-index: 2000;
60
+ }
61
+
62
+ .menu {
63
+ position: fixed;
64
+ min-width: 160px;
65
+ background: var(--bg-panel);
66
+ border: 1px solid var(--border);
67
+ border-radius: var(--radius-md);
68
+ box-shadow: var(--shadow-md);
69
+ padding: 4px 0;
70
+ z-index: 2001;
71
+ }
72
+
73
+ .menu-item {
74
+ display: flex;
75
+ align-items: center;
76
+ width: 100%;
77
+ padding: 5px 12px;
78
+ background: none;
79
+ border: none;
80
+ color: var(--text-primary);
81
+ font-size: var(--text-lg);
82
+ cursor: pointer;
83
+ text-align: left;
84
+ white-space: nowrap;
85
+ }
86
+
87
+ .menu-item:hover {
88
+ background: var(--bg-secondary);
89
+ }
90
+ </style>
@@ -0,0 +1,46 @@
1
+ const STORAGE_KEY = 'pixelweaver:dock-layout';
2
+
3
+ /**
4
+ * Bump this number whenever the default layout changes significantly
5
+ * (new panels, changed positions/sizes). Stale saved layouts with an
6
+ * older version are discarded on load so buildDefaultLayout() runs.
7
+ */
8
+ const LAYOUT_VERSION = 5;
9
+
10
+ /** Save the current layout to localStorage */
11
+ export function saveLayout(layout: object): void {
12
+ try {
13
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ version: LAYOUT_VERSION, layout }));
14
+ } catch {
15
+ // Silently fail on quota/security errors
16
+ }
17
+ }
18
+
19
+ /** Load saved layout from localStorage, or null if none/stale */
20
+ export function loadLayout(): object | null {
21
+ try {
22
+ const raw = localStorage.getItem(STORAGE_KEY);
23
+ if (!raw) return null;
24
+ const parsed = JSON.parse(raw) as { version?: unknown; layout?: unknown } | null;
25
+ // Reject layouts from older versions
26
+ if (parsed && typeof parsed === 'object' && 'version' in parsed) {
27
+ if (parsed.version !== LAYOUT_VERSION) return null;
28
+ const layout = parsed.layout;
29
+ if (layout && typeof layout === 'object') return layout;
30
+ return null;
31
+ }
32
+ // Legacy format (no version wrapper) -- discard
33
+ return null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /** Clear saved layout */
40
+ export function clearLayout(): void {
41
+ try {
42
+ localStorage.removeItem(STORAGE_KEY);
43
+ } catch {
44
+ // Silently fail
45
+ }
46
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Dock registration plugin -- registers all dockable panels with the
3
+ * central panel registry during bootstrap. The DockLayout component reads
4
+ * these registrations to build the default layout.
5
+ */
6
+
7
+ import type { PluginModule } from '../core/plugin-loader.js';
8
+ import CanvasViewport from '../canvas/CanvasViewport.svelte';
9
+ import ColorPicker from '../ui/ColorPicker.svelte';
10
+ import LayerPanel from '../ui/LayerPanel.svelte';
11
+ import FrameStrip from '../ui/FrameStrip.svelte';
12
+
13
+ export const dockPlugin: PluginModule = {
14
+ name: 'ui/dock',
15
+ version: '1.0.0',
16
+ dependencies: [],
17
+ register(api) {
18
+ api.addPanel('canvas', {
19
+ title: 'Canvas',
20
+ component: CanvasViewport,
21
+ position: 'center',
22
+ closeable: false,
23
+ });
24
+
25
+ api.addPanel('colors', {
26
+ title: 'Colors',
27
+ component: ColorPicker,
28
+ position: 'right',
29
+ minWidth: 180,
30
+ maxWidth: 300,
31
+ });
32
+
33
+ api.addPanel('layers', {
34
+ title: 'Layers',
35
+ component: LayerPanel,
36
+ position: 'right',
37
+ minWidth: 160,
38
+ maxWidth: 300,
39
+ });
40
+
41
+ api.addPanel('frames', {
42
+ title: 'Animation',
43
+ component: FrameStrip,
44
+ position: 'bottom',
45
+ minHeight: 80,
46
+ maxHeight: 150,
47
+ });
48
+ },
49
+ };