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,61 @@
1
+ /**
2
+ * Default keyboard bindings for PixelWeaver.
3
+ *
4
+ * Each entry has a unique name (used for profile export/import and rebinding),
5
+ * a default key combination, and a human-readable description.
6
+ *
7
+ * Key strings use the format: "Ctrl+Shift+Z", "B", etc.
8
+ * Modifier names: Ctrl, Shift, Alt, Meta.
9
+ */
10
+
11
+ export interface DefaultBinding {
12
+ /** Unique action name, e.g. "tool.pencil" or "edit.undo" */
13
+ name: string;
14
+ /** Default key combination */
15
+ key: string;
16
+ /** Human-readable description for UI display */
17
+ description: string;
18
+ }
19
+
20
+ export const DEFAULT_BINDINGS: DefaultBinding[] = [
21
+ // -- Tools --
22
+ { name: 'tool.pencil', key: 'B', description: 'Pencil tool' },
23
+ { name: 'tool.eraser', key: 'E', description: 'Eraser tool' },
24
+ { name: 'tool.eyedropper', key: 'I', description: 'Eyedropper tool' },
25
+ { name: 'tool.fill', key: 'G', description: 'Fill bucket' },
26
+ { name: 'tool.line', key: 'L', description: 'Line tool' },
27
+ { name: 'tool.rect', key: 'R', description: 'Rectangle tool' },
28
+ { name: 'tool.ellipse', key: 'C', description: 'Circle/Ellipse tool' },
29
+ { name: 'tool.diamond', key: 'D', description: 'Diamond tool' },
30
+
31
+ // -- Edit --
32
+ { name: 'edit.undo', key: 'Ctrl+Z', description: 'Undo' },
33
+ { name: 'edit.redo', key: 'Ctrl+Shift+Z', description: 'Redo' },
34
+
35
+ // -- Colors --
36
+ { name: 'color.swap', key: 'X', description: 'Swap foreground/background colors' },
37
+
38
+ // -- View --
39
+ { name: 'view.zoom-in', key: '=', description: 'Zoom in' },
40
+ { name: 'view.zoom-out', key: '-', description: 'Zoom out' },
41
+ { name: 'view.command-palette', key: 'Ctrl+P', description: 'Command palette' },
42
+ { name: 'view.toggle-editor-mode', key: 'Ctrl+Tab', description: 'Toggle editor mode' },
43
+
44
+ // -- Layers --
45
+ { name: 'layer.add', key: 'Ctrl+Shift+N', description: 'Add new layer' },
46
+ { name: 'layer.delete', key: 'Delete', description: 'Delete active layer' },
47
+ { name: 'layer.duplicate', key: 'Ctrl+J', description: 'Duplicate active layer' },
48
+ { name: 'layer.merge-down', key: 'Ctrl+E', description: 'Merge layer down' },
49
+ { name: 'layer.move-up', key: 'Alt+]', description: 'Move layer up' },
50
+ { name: 'layer.move-down', key: 'Alt+[', description: 'Move layer down' },
51
+
52
+ // -- Animation --
53
+ { name: 'anim.play-pause', key: 'Space', description: 'Play/Pause animation' },
54
+ { name: 'anim.step-prev', key: ',', description: 'Previous frame' },
55
+ { name: 'anim.step-next', key: '.', description: 'Next frame' },
56
+ { name: 'anim.first-frame', key: 'Home', description: 'Go to first frame' },
57
+ { name: 'anim.last-frame', key: 'End', description: 'Go to last frame' },
58
+
59
+ // -- File --
60
+ { name: 'file.save', key: 'Ctrl+S', description: 'Save project' },
61
+ ];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shortcut Editor Panel plugin -- registers the ShortcutEditorPanel with the
3
+ * central panel registry so it can be docked alongside other right-side panels.
4
+ *
5
+ * Discovery is automatic via the `*-plugin.ts` glob in bootstrap.ts.
6
+ */
7
+
8
+ import type { PluginModule } from '../core/plugin-loader.js';
9
+ import ShortcutEditorPanel from './ShortcutEditorPanel.svelte';
10
+
11
+ export const shortcutEditorPanelPlugin: PluginModule = {
12
+ name: 'ui/shortcut-editor',
13
+ version: '1.0.0',
14
+ description: 'Searchable table of keyboard shortcuts with rebind support',
15
+ dependencies: ['builtin/shortcuts'],
16
+ register(api) {
17
+ api.addPanel('shortcut-editor', {
18
+ title: 'Shortcuts',
19
+ component: ShortcutEditorPanel,
20
+ position: 'right',
21
+ minWidth: 220,
22
+ maxWidth: 420,
23
+ });
24
+ },
25
+ };
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Shortcut Initialization -- wires up the ShortcutManager with default bindings
3
+ * and connects shortcut actions to actual app functions.
4
+ *
5
+ * Call initializeShortcuts() once at app startup (e.g., in the root layout).
6
+ * Call teardownShortcuts() to remove the global listener (e.g., during HMR cleanup).
7
+ */
8
+
9
+ import { ShortcutManager, normalizeShortcut } from './shortcut-manager.js';
10
+ import { DEFAULT_BINDINGS } from './default-bindings.js';
11
+ import {
12
+ getBindings,
13
+ setBindings,
14
+ updateBindingKey,
15
+ setEnabled as setStateEnabled,
16
+ setActiveTool,
17
+ type BindingInfo,
18
+ } from './shortcut-state.svelte.js';
19
+ import { toolRegistry, commandRegistry } from '../core/registries.svelte.js';
20
+ import type { CommandDefinition } from '../core/commands.js';
21
+ import type { CommandType, ParamsOf } from '../core/command-params.js';
22
+ import { undoLast, redoLast } from '../core/dispatcher.js';
23
+ import { executeOrDispatch } from '../core/command-runner.js';
24
+
25
+ // The registry stores CommandDefinition<any> (a type-erasure boundary --
26
+ // see registries.svelte.ts). executeOrDispatch expects the default P, so
27
+ // cast through this helper at the read site.
28
+ type AnyCommand = CommandDefinition;
29
+ import { swapColors } from '../color/color-state.svelte.js';
30
+ import * as layerTree from '../layers/layer-tree.svelte.js';
31
+ import { isInteracting } from '../canvas/input-handler.js';
32
+ import * as animPreview from '../animation/animation-preview.svelte.js';
33
+ import * as frameModel from '../animation/frame-model.svelte.js';
34
+
35
+ // --- Singleton manager instance ---
36
+
37
+ let manager: ShortcutManager | null = null;
38
+ let keydownHandler: ((e: KeyboardEvent) => void) | null = null;
39
+ let focusInHandler: ((e: FocusEvent) => void) | null = null;
40
+ let focusOutHandler: ((e: FocusEvent) => void) | null = null;
41
+
42
+ /** Get the global ShortcutManager instance (null before initialization). */
43
+ export function getShortcutManager(): ShortcutManager | null {
44
+ return manager;
45
+ }
46
+
47
+ // --- Action resolvers ---
48
+ // These functions resolve action names to their implementations.
49
+ // Tool shortcuts activate the corresponding tool from the tool registry.
50
+ // Edit, color, and view shortcuts call the app subsystems directly.
51
+
52
+ function resolveToolAction(toolName: string): () => void {
53
+ return () => {
54
+ // Activate the tool via its onActivate callback if registered
55
+ const tool = toolRegistry.get(toolName);
56
+ if (tool?.onActivate) {
57
+ tool.onActivate();
58
+ }
59
+ setActiveTool(toolName);
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Dispatch a layer command via executeOrDispatch so undoable commands
65
+ * go through the dispatcher and non-undoable ones execute directly.
66
+ */
67
+ // Typed overload: compile-time param validation for known commands
68
+ function dispatchLayerCommand<T extends CommandType>(commandId: T, params: ParamsOf<T>): void;
69
+ // String fallback: for dynamic dispatch where command type is a variable
70
+ function dispatchLayerCommand(commandId: string, params?: Record<string, unknown>): void;
71
+ function dispatchLayerCommand(commandId: string, params: Record<string, unknown> = {}): void {
72
+ const def = commandRegistry.get(commandId) as AnyCommand | undefined;
73
+ if (!def) return;
74
+ executeOrDispatch(commandId, def, params);
75
+ }
76
+
77
+ /** Map of action name prefixes/patterns to their resolution logic */
78
+ function resolveAction(name: string): (() => void) | null {
79
+ // Tool shortcuts: "tool.pencil" -> activate tool "pencil"
80
+ if (name.startsWith('tool.')) {
81
+ const toolName = name.slice('tool.'.length);
82
+ return resolveToolAction(toolName);
83
+ }
84
+
85
+ // Edit shortcuts
86
+ if (name === 'edit.undo') return () => undoLast();
87
+ if (name === 'edit.redo') return () => redoLast();
88
+
89
+ // Color shortcuts
90
+ if (name === 'color.swap') return () => { swapColors(); };
91
+
92
+ // Layer shortcuts: dispatch layer commands through the command system.
93
+ // Commands that operate on the active layer resolve the ID at invocation
94
+ // time so the shortcut always targets the current selection.
95
+ if (name === 'layer.add') {
96
+ return () => {
97
+ const count = layerTree.getFlatList().length;
98
+ dispatchLayerCommand('add_layer', { name: `Layer ${String(count + 1)}` });
99
+ };
100
+ }
101
+ if (name === 'layer.delete') {
102
+ return () => {
103
+ const activeId = layerTree.getActiveLayerId();
104
+ if (!activeId) return;
105
+ // Prevent deleting the last pixel layer
106
+ const pixelCount = layerTree.getFlatList().filter((l) => l.type === 'pixel').length;
107
+ if (pixelCount <= 1) return;
108
+ dispatchLayerCommand('remove_layer', { id: activeId });
109
+ };
110
+ }
111
+ if (name === 'layer.duplicate') {
112
+ return () => {
113
+ const activeId = layerTree.getActiveLayerId();
114
+ if (!activeId) return;
115
+ dispatchLayerCommand('duplicate_layer', { id: activeId });
116
+ };
117
+ }
118
+ if (name === 'layer.merge-down') {
119
+ return () => { dispatchLayerCommand('merge_down', {}); };
120
+ }
121
+ if (name === 'layer.move-up') {
122
+ return () => { dispatchLayerCommand('move_layer_up', {}); };
123
+ }
124
+ if (name === 'layer.move-down') {
125
+ return () => { dispatchLayerCommand('move_layer_down', {}); };
126
+ }
127
+
128
+ // Animation shortcuts
129
+ // Play/pause is guarded by isInteracting() so Space does not toggle playback
130
+ // while the user is mid-stroke or panning (Space+click = pan in input-handler).
131
+ if (name === 'anim.play-pause') {
132
+ return () => {
133
+ if (isInteracting()) return;
134
+ const def = commandRegistry.get('play_animation') as AnyCommand | undefined;
135
+ if (def) {
136
+ executeOrDispatch('play_animation', def, {});
137
+ }
138
+ };
139
+ }
140
+ if (name === 'anim.step-prev') {
141
+ return () => { animPreview.step(-1); };
142
+ }
143
+ if (name === 'anim.step-next') {
144
+ return () => { animPreview.step(1); };
145
+ }
146
+ if (name === 'anim.first-frame') {
147
+ return () => {
148
+ const frames = frameModel.getFrames();
149
+ if (frames.length > 0) frameModel.setCurrentFrame(0);
150
+ };
151
+ }
152
+ if (name === 'anim.last-frame') {
153
+ return () => {
154
+ const frames = frameModel.getFrames();
155
+ if (frames.length > 0) frameModel.setCurrentFrame(frames.length - 1);
156
+ };
157
+ }
158
+
159
+ // View/file shortcuts -- placeholders that will be connected in later phases
160
+ if (name === 'view.zoom-in') return () => { /* connected in canvas phase */ };
161
+ if (name === 'view.zoom-out') return () => { /* connected in canvas phase */ };
162
+ if (name === 'view.command-palette') return () => { /* connected in UI phase */ };
163
+ if (name === 'file.save') return () => { /* connected in persistence phase */ };
164
+
165
+ // Toggle between Canvas and Level Editor center panels
166
+ if (name === 'view.toggle-editor-mode') {
167
+ return () => {
168
+ const def = commandRegistry.get('toggle_editor_mode') as AnyCommand | undefined;
169
+ if (def) executeOrDispatch('toggle_editor_mode', def, {});
170
+ };
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ // --- Focus tracking ---
177
+ // Disable shortcuts when a text input, textarea, or contenteditable has focus.
178
+
179
+ const TEXT_INPUT_TYPES = new Set([
180
+ 'text', 'password', 'email', 'number', 'search', 'tel', 'url',
181
+ ]);
182
+
183
+ function isTextInputElement(el: EventTarget | null): boolean {
184
+ if (!el || !(el instanceof HTMLElement)) return false;
185
+
186
+ if (el instanceof HTMLTextAreaElement) return true;
187
+ if (el.isContentEditable) return true;
188
+ if (el instanceof HTMLInputElement && TEXT_INPUT_TYPES.has(el.type)) return true;
189
+
190
+ return false;
191
+ }
192
+
193
+ // --- Initialization ---
194
+
195
+ /**
196
+ * Initialize the shortcut system: create manager, load default bindings,
197
+ * attach global listeners, and sync reactive state.
198
+ */
199
+ export function initializeShortcuts(): ShortcutManager {
200
+ // Guard against double initialization
201
+ if (manager) {
202
+ teardownShortcuts();
203
+ }
204
+
205
+ manager = new ShortcutManager();
206
+
207
+ // Load default bindings and resolve actions
208
+ const bindingInfos: BindingInfo[] = [];
209
+
210
+ for (const binding of DEFAULT_BINDINGS) {
211
+ const action = resolveAction(binding.name);
212
+ if (action) {
213
+ manager.bind(binding.key, action, binding.description, binding.name);
214
+ bindingInfos.push({
215
+ name: binding.name,
216
+ key: binding.key,
217
+ description: binding.description,
218
+ });
219
+ }
220
+ }
221
+
222
+ // Sync reactive state
223
+ setBindings(bindingInfos);
224
+
225
+ // Capture the non-null manager in closure so handlers don't need to re-check
226
+ // the module-level variable (which is typed as nullable).
227
+ const activeManager = manager;
228
+
229
+ // Attach global keydown listener
230
+ keydownHandler = (event: KeyboardEvent) => {
231
+ activeManager.handleKeyDown(event);
232
+ };
233
+ window.addEventListener('keydown', keydownHandler);
234
+
235
+ // Attach focus tracking to auto-disable shortcuts in text inputs
236
+ focusInHandler = (event: FocusEvent) => {
237
+ if (isTextInputElement(event.target)) {
238
+ activeManager.setEnabled(false);
239
+ setStateEnabled(false);
240
+ }
241
+ };
242
+
243
+ focusOutHandler = (event: FocusEvent) => {
244
+ if (isTextInputElement(event.target)) {
245
+ activeManager.setEnabled(true);
246
+ setStateEnabled(true);
247
+ }
248
+ };
249
+
250
+ window.addEventListener('focusin', focusInHandler);
251
+ window.addEventListener('focusout', focusOutHandler);
252
+
253
+ return manager;
254
+ }
255
+
256
+ // --- Rebinding / reset helpers ---
257
+
258
+ /**
259
+ * Result of attempting a rebind. On success, the manager and reactive state
260
+ * have been updated. On conflict, the caller receives the conflicting action
261
+ * name so it can offer a swap.
262
+ */
263
+ export interface RebindResult {
264
+ ok: boolean;
265
+ /** Name of the already-bound action that owns the requested key, if any. */
266
+ conflictName?: string;
267
+ /** Description of the conflicting action, for UI display. */
268
+ conflictDescription?: string;
269
+ /** Reason for failure when not a conflict (e.g. manager missing). */
270
+ reason?: string;
271
+ }
272
+
273
+ /**
274
+ * Rebind a named action to a new key combination.
275
+ * Keeps the ShortcutManager and the reactive state module in sync.
276
+ *
277
+ * If `newKey` is already bound to a different action, returns a conflict
278
+ * without mutating anything. Callers that want to swap must call
279
+ * `rebindShortcut` twice (once to clear the other action, once to set this).
280
+ */
281
+ export function rebindShortcut(name: string, newKey: string): RebindResult {
282
+ if (!manager) return { ok: false, reason: 'shortcut-manager-not-initialized' };
283
+
284
+ const currentBinding = getBindings().get(name);
285
+ if (!currentBinding) return { ok: false, reason: 'unknown-action' };
286
+
287
+ const normalizedNew = normalizeShortcut(newKey);
288
+ const normalizedCurrent = normalizeShortcut(currentBinding.key);
289
+
290
+ // No-op: already bound to that key
291
+ if (normalizedNew === normalizedCurrent) return { ok: true };
292
+
293
+ // Check for conflict with a different action
294
+ const existingShortcutName = findBindingNameForKey(normalizedNew);
295
+ if (existingShortcutName && existingShortcutName !== name) {
296
+ const conflicting = getBindings().get(existingShortcutName);
297
+ return {
298
+ ok: false,
299
+ conflictName: existingShortcutName,
300
+ conflictDescription: conflicting?.description ?? '',
301
+ };
302
+ }
303
+
304
+ // Apply the rebind to manager and reactive state
305
+ manager.rebind(currentBinding.key, newKey);
306
+ updateBindingKey(name, newKey);
307
+ return { ok: true };
308
+ }
309
+
310
+ /**
311
+ * Unbind a named action entirely (used as a building block for swap flows
312
+ * in the shortcut editor -- strip the incumbent before assigning the new key).
313
+ */
314
+ export function clearShortcut(name: string): void {
315
+ if (!manager) return;
316
+ const current = getBindings().get(name);
317
+ if (!current) return;
318
+ manager.unbind(current.key);
319
+ updateBindingKey(name, '');
320
+ }
321
+
322
+ /**
323
+ * Reset a single binding back to the default key shipped in DEFAULT_BINDINGS.
324
+ * If the default collides with another action's current key, the other action
325
+ * is cleared so the default can be restored cleanly.
326
+ */
327
+ export function resetShortcutToDefault(name: string): void {
328
+ if (!manager) return;
329
+ const defaultBinding = DEFAULT_BINDINGS.find((b) => b.name === name);
330
+ if (!defaultBinding) return;
331
+
332
+ // Clear any other action currently occupying the default key
333
+ const occupant = findBindingNameForKey(normalizeShortcut(defaultBinding.key));
334
+ if (occupant && occupant !== name) {
335
+ clearShortcut(occupant);
336
+ }
337
+
338
+ const current = getBindings().get(name);
339
+ if (current && current.key) {
340
+ manager.rebind(current.key, defaultBinding.key);
341
+ } else {
342
+ // Previously cleared -- re-bind from scratch using the resolved action
343
+ const action = resolveAction(name);
344
+ if (action) {
345
+ manager.bind(defaultBinding.key, action, defaultBinding.description, name);
346
+ }
347
+ }
348
+ updateBindingKey(name, defaultBinding.key);
349
+ }
350
+
351
+ /** Lookup the action name currently bound to a normalized key, if any. */
352
+ function findBindingNameForKey(normalizedKey: string): string | undefined {
353
+ for (const [actionName, info] of getBindings()) {
354
+ if (info.key && normalizeShortcut(info.key) === normalizedKey) {
355
+ return actionName;
356
+ }
357
+ }
358
+ return undefined;
359
+ }
360
+
361
+ /** Remove all global listeners and reset the manager. */
362
+ export function teardownShortcuts(): void {
363
+ if (keydownHandler) {
364
+ window.removeEventListener('keydown', keydownHandler);
365
+ keydownHandler = null;
366
+ }
367
+ if (focusInHandler) {
368
+ window.removeEventListener('focusin', focusInHandler);
369
+ focusInHandler = null;
370
+ }
371
+ if (focusOutHandler) {
372
+ window.removeEventListener('focusout', focusOutHandler);
373
+ focusOutHandler = null;
374
+ }
375
+
376
+ if (manager) {
377
+ manager.reset();
378
+ manager = null;
379
+ }
380
+ }