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,418 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ActionLogPanel -- displays the semantic action log history.
4
+ *
5
+ * Each entry shows a description, command type, relative timestamp, and
6
+ * an enable/disable checkbox that skips the action during replay. The
7
+ * header exposes controls for exporting/importing specs, clearing the
8
+ * log, and recording macros. Header buttons dispatch registered
9
+ * commands (export_spec, import_spec, clear_action_log,
10
+ * record_macro_start, record_macro_stop) via the command runner.
11
+ */
12
+
13
+ import type { CommandType, ParamsOf } from '../core/command-params.js';
14
+ import { dispatch, onExecute } from '../core/dispatcher.js';
15
+ import { executeOrDispatch, getCommandForDispatch } from '../core/command-runner.js';
16
+ import { actionLog } from './action-log.svelte.js';
17
+ import { macroSystem } from './macros.svelte.js';
18
+ import { notificationState } from '../ui/notifications/notification-state.svelte.js';
19
+ import type { SpecDocument } from './spec-format.js';
20
+ import DownloadIcon from '~icons/lucide/download';
21
+ import UploadIcon from '~icons/lucide/upload';
22
+ import TrashIcon from '~icons/lucide/trash-2';
23
+ import CircleDotIcon from '~icons/lucide/circle-dot';
24
+ import SquareIcon from '~icons/lucide/square';
25
+
26
+ // --- Local reactive tick ---
27
+ // Incremented by a 1s interval so relative timestamps stay fresh without
28
+ // requiring reactivity in the underlying action-log state.
29
+ let nowTick = $state(Date.now());
30
+
31
+ // --- Sync action log with dispatcher ---
32
+ // Initial population plus re-sync on every dispatched command so the
33
+ // panel reflects the semantic log in real time.
34
+ $effect(() => {
35
+ actionLog.syncFromDispatcher();
36
+ const unsubscribe = onExecute(() => {
37
+ actionLog.syncFromDispatcher();
38
+ });
39
+ const timer = setInterval(() => {
40
+ nowTick = Date.now();
41
+ }, 1000);
42
+ return () => {
43
+ unsubscribe();
44
+ clearInterval(timer);
45
+ };
46
+ });
47
+
48
+ // --- Derived state ---
49
+
50
+ const entries = $derived(actionLog.entries);
51
+
52
+ // --- Relative time formatter ---
53
+
54
+ /**
55
+ * Format a timestamp relative to the current tick.
56
+ * Examples: "just now", "5s ago", "2m ago", "1h ago", "3d ago".
57
+ */
58
+ function formatRelative(ts: number): string {
59
+ const diffMs = Math.max(0, nowTick - ts);
60
+ const s = Math.floor(diffMs / 1000);
61
+ if (s < 5) return 'just now';
62
+ if (s < 60) return `${String(s)}s ago`;
63
+ const m = Math.floor(s / 60);
64
+ if (m < 60) return `${String(m)}m ago`;
65
+ const h = Math.floor(m / 60);
66
+ if (h < 24) return `${String(h)}h ago`;
67
+ const d = Math.floor(h / 24);
68
+ return `${String(d)}d ago`;
69
+ }
70
+
71
+ // --- Command dispatch helper ---
72
+
73
+ // Typed overload: compile-time param validation for known commands
74
+ function dispatchCmd<T extends CommandType>(type: T, params: ParamsOf<T>): void;
75
+ // String fallback: for dynamic dispatch where command type is a variable
76
+ function dispatchCmd(type: string, params?: Record<string, unknown>): void;
77
+ function dispatchCmd(type: string, params: Record<string, unknown> = {}) {
78
+ dispatch({
79
+ type,
80
+ plugin: 'ui/history',
81
+ version: '1.0.0',
82
+ params,
83
+ id: crypto.randomUUID(),
84
+ timestamp: Date.now(),
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Invoke a non-undoable command via the command-runner. Looks up the
90
+ * definition from the registry and routes through executeOrDispatch()
91
+ * so the command runs directly (non-undoable commands should not enter
92
+ * the undo stack).
93
+ */
94
+ // Typed overload: compile-time param validation for known commands
95
+ function runCmd<T extends CommandType>(name: T, params?: ParamsOf<T>): void;
96
+ // String fallback: for dynamic dispatch where command type is a variable
97
+ function runCmd(name: string, params?: Record<string, unknown>): void;
98
+ function runCmd(name: string, params: Record<string, unknown> = {}): void {
99
+ const def = getCommandForDispatch(name);
100
+ if (!def) {
101
+ notificationState.push({
102
+ id: `missing-cmd-${name}`,
103
+ message: `Command "${name}" is not registered`,
104
+ type: 'error',
105
+ autoDismissMs: 4000,
106
+ });
107
+ return;
108
+ }
109
+ executeOrDispatch(name, def, params);
110
+ }
111
+
112
+ // --- Entry actions ---
113
+
114
+ function handleToggle(entryId: string) {
115
+ // toggle_action is registered by history-commands.ts
116
+ dispatchCmd('toggle_action', { entryId });
117
+ }
118
+
119
+ // --- Header actions ---
120
+ // Wired to dispatchable commands registered by history-commands.ts.
121
+ // Export/import/clear/macro commands are all non-undoable so we invoke
122
+ // them via executeOrDispatch(); the command bodies handle their own
123
+ // side-effects (downloads, notifications, dispatched child commands).
124
+
125
+ // Hidden file input used for import_spec. Bound via bind:this below.
126
+ let importFileInput: HTMLInputElement | null = $state(null);
127
+
128
+ function handleExportSpec() {
129
+ runCmd('export_spec');
130
+ }
131
+
132
+ function handleImportSpec() {
133
+ // Open the hidden <input type="file"> picker. On change, the file
134
+ // contents are parsed and passed to the import_spec command.
135
+ importFileInput?.click();
136
+ }
137
+
138
+ async function onImportFileChange(event: Event) {
139
+ const input = event.currentTarget as HTMLInputElement;
140
+ const file = input.files?.[0];
141
+ if (!file) return;
142
+ try {
143
+ const text = await file.text();
144
+ const spec = JSON.parse(text) as SpecDocument;
145
+ runCmd('import_spec', { spec });
146
+ } catch (err) {
147
+ notificationState.push({
148
+ id: 'import-spec-parse-error',
149
+ message: `Import spec: failed to parse file (${(err as Error).message})`,
150
+ type: 'error',
151
+ autoDismissMs: 5000,
152
+ });
153
+ } finally {
154
+ // Reset so the same file can be re-selected to trigger another import.
155
+ input.value = '';
156
+ }
157
+ }
158
+
159
+ function handleClearLog() {
160
+ runCmd('clear_action_log');
161
+ }
162
+
163
+ function handleRecordMacro() {
164
+ // Toggle between start and stop based on current recording state.
165
+ if (macroSystem.recording) {
166
+ runCmd('record_macro_stop', {});
167
+ } else {
168
+ runCmd('record_macro_start');
169
+ }
170
+ }
171
+ </script>
172
+
173
+ <div class="action-log-panel">
174
+ <!-- Hidden file input for import_spec picker -->
175
+ <input
176
+ bind:this={importFileInput}
177
+ type="file"
178
+ accept="application/json,.json"
179
+ style="display: none;"
180
+ onchange={onImportFileChange}
181
+ />
182
+ <!-- Header -->
183
+ <div class="log-header">
184
+ <span class="log-title">History</span>
185
+ <div class="log-actions">
186
+ <button
187
+ class="action-btn"
188
+ title="Export spec"
189
+ aria-label="Export spec"
190
+ onclick={handleExportSpec}
191
+ ><DownloadIcon /></button>
192
+ <button
193
+ class="action-btn"
194
+ title="Import spec"
195
+ aria-label="Import spec"
196
+ onclick={handleImportSpec}
197
+ ><UploadIcon /></button>
198
+ <button
199
+ class="action-btn"
200
+ class:action-btn--recording={macroSystem.recording}
201
+ title={macroSystem.recording ? 'Stop recording macro' : 'Record macro'}
202
+ aria-label={macroSystem.recording ? 'Stop recording macro' : 'Record macro'}
203
+ onclick={handleRecordMacro}
204
+ >{#if macroSystem.recording}<SquareIcon />{:else}<CircleDotIcon />{/if}</button>
205
+ <button
206
+ class="action-btn"
207
+ title="Clear log"
208
+ aria-label="Clear log"
209
+ onclick={handleClearLog}
210
+ disabled={entries.length === 0}
211
+ ><TrashIcon /></button>
212
+ </div>
213
+ </div>
214
+
215
+ <!-- Entry list -->
216
+ <div class="log-list">
217
+ {#if entries.length === 0}
218
+ <div class="empty-state">
219
+ <span class="empty-title">No actions yet</span>
220
+ <span class="empty-hint">Draw or edit to build history</span>
221
+ </div>
222
+ {:else}
223
+ {#each entries as entry (entry.commandId)}
224
+ <div
225
+ class="log-row"
226
+ class:log-row--disabled={!entry.enabled}
227
+ >
228
+ <input
229
+ class="enable-checkbox"
230
+ type="checkbox"
231
+ checked={entry.enabled}
232
+ title={entry.enabled ? 'Disable action' : 'Enable action'}
233
+ aria-label={entry.enabled ? `Disable action ${entry.description}` : `Enable action ${entry.description}`}
234
+ onchange={() => { handleToggle(entry.commandId); }}
235
+ />
236
+ <div class="log-entry-body">
237
+ <span class="log-description">{entry.description}</span>
238
+ <div class="log-meta">
239
+ <span class="log-type">{entry.type}</span>
240
+ <span class="log-timestamp">{formatRelative(entry.timestamp)}</span>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ {/each}
245
+ {/if}
246
+ </div>
247
+ </div>
248
+
249
+ <style>
250
+ .action-log-panel {
251
+ display: flex;
252
+ flex-direction: column;
253
+ width: 100%;
254
+ height: 100%;
255
+ background: var(--bg-panel);
256
+ color: var(--text-primary);
257
+ font-size: var(--text-base);
258
+ user-select: none;
259
+ }
260
+
261
+ .log-header {
262
+ display: flex;
263
+ align-items: center;
264
+ justify-content: space-between;
265
+ padding: 6px var(--space-3);
266
+ border-bottom: 1px solid var(--border);
267
+ background: var(--bg-toolbar);
268
+ flex-shrink: 0;
269
+ }
270
+
271
+ .log-title {
272
+ font-weight: 600;
273
+ font-size: var(--text-sm);
274
+ text-transform: uppercase;
275
+ letter-spacing: 0.5px;
276
+ color: var(--text-secondary);
277
+ }
278
+
279
+ .log-actions {
280
+ display: flex;
281
+ gap: var(--space-1);
282
+ }
283
+
284
+ .action-btn {
285
+ background: none;
286
+ border: 1px solid transparent;
287
+ border-radius: var(--radius-sm);
288
+ color: var(--text-secondary);
289
+ cursor: pointer;
290
+ width: 24px;
291
+ height: 24px;
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ font-size: var(--text-xl);
296
+ padding: 0;
297
+ line-height: 1;
298
+ }
299
+
300
+ .action-btn:hover:not(:disabled) {
301
+ background: var(--bg-primary);
302
+ color: var(--text-primary);
303
+ border-color: var(--border);
304
+ }
305
+
306
+ .action-btn:disabled {
307
+ opacity: 0.3;
308
+ cursor: default;
309
+ }
310
+
311
+ /* Highlight the record button while a macro recording is in progress */
312
+ .action-btn--recording {
313
+ color: var(--accent);
314
+ border-color: var(--accent);
315
+ }
316
+
317
+ .action-btn :global(svg) {
318
+ width: 14px;
319
+ height: 14px;
320
+ }
321
+
322
+ /* List container -- scrollable */
323
+ .log-list {
324
+ flex: 1;
325
+ overflow-y: auto;
326
+ overflow-x: hidden;
327
+ }
328
+
329
+ /* Empty state */
330
+ .empty-state {
331
+ display: flex;
332
+ flex-direction: column;
333
+ align-items: center;
334
+ justify-content: center;
335
+ gap: var(--space-1);
336
+ padding: var(--space-4);
337
+ color: var(--text-secondary);
338
+ text-align: center;
339
+ }
340
+
341
+ .empty-title {
342
+ font-size: var(--text-sm);
343
+ font-weight: 600;
344
+ }
345
+
346
+ .empty-hint {
347
+ font-size: var(--text-xs);
348
+ opacity: 0.7;
349
+ }
350
+
351
+ /* Individual entry row */
352
+ .log-row {
353
+ display: flex;
354
+ align-items: flex-start;
355
+ gap: var(--space-2);
356
+ padding: 6px var(--space-3);
357
+ border-bottom: 1px solid transparent;
358
+ transition: background var(--transition-fast);
359
+ }
360
+
361
+ .log-row:hover {
362
+ background: rgba(255, 255, 255, 0.04);
363
+ }
364
+
365
+ :global([data-theme="light"]) .log-row:hover {
366
+ background: rgba(0, 0, 0, 0.04);
367
+ }
368
+
369
+ .log-row--disabled {
370
+ opacity: 0.5;
371
+ }
372
+
373
+ .enable-checkbox {
374
+ margin-top: 2px;
375
+ accent-color: var(--accent);
376
+ cursor: pointer;
377
+ flex-shrink: 0;
378
+ }
379
+
380
+ .log-entry-body {
381
+ flex: 1;
382
+ min-width: 0;
383
+ display: flex;
384
+ flex-direction: column;
385
+ gap: 2px;
386
+ }
387
+
388
+ .log-description {
389
+ font-size: var(--text-base);
390
+ overflow: hidden;
391
+ text-overflow: ellipsis;
392
+ white-space: nowrap;
393
+ }
394
+
395
+ .log-row--disabled .log-description {
396
+ text-decoration: line-through;
397
+ }
398
+
399
+ .log-meta {
400
+ display: flex;
401
+ align-items: center;
402
+ gap: var(--space-2);
403
+ font-size: var(--text-xs);
404
+ color: var(--text-secondary);
405
+ }
406
+
407
+ .log-type {
408
+ font-family: var(--font-mono, monospace);
409
+ padding: 1px var(--space-1);
410
+ background: var(--bg-primary);
411
+ border: 1px solid var(--border);
412
+ border-radius: var(--radius-sm);
413
+ }
414
+
415
+ .log-timestamp {
416
+ opacity: 0.8;
417
+ }
418
+ </style>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Action Log Panel Plugin -- registers the ActionLogPanel as a dockable
3
+ * panel so the semantic history can be viewed and manipulated from the UI.
4
+ */
5
+
6
+ import type { PluginModule } from '../core/plugin-loader.js';
7
+ import ActionLogPanel from './ActionLogPanel.svelte';
8
+
9
+ export const actionLogPanelPlugin: PluginModule = {
10
+ name: 'ui/history',
11
+ version: '1.0.0',
12
+ description: 'Semantic action log panel',
13
+ dependencies: [],
14
+
15
+ register(api) {
16
+ api.addPanel('history', {
17
+ title: 'History',
18
+ component: ActionLogPanel,
19
+ position: 'right',
20
+ minWidth: 200,
21
+ maxWidth: 360,
22
+ });
23
+ },
24
+ };
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Action Log State -- reactive state for the history/action log panel.
3
+ *
4
+ * Maintains a list of ActionLogEntries synced from the dispatcher's semantic
5
+ * log. Entries can be toggled (disabled/enabled), deleted, filtered, and
6
+ * selected. The action log is the user-facing representation of the command
7
+ * history that powers the semantic replay system.
8
+ *
9
+ * Module-level singleton using Svelte 5 $state runes.
10
+ */
11
+
12
+ import { getSemanticLog, getUndoStack } from '../core/dispatcher.js';
13
+
14
+ // --- Types ---
15
+
16
+ export interface ActionLogEntry {
17
+ commandId: string;
18
+ type: string; // command type, e.g. "draw_pixel"
19
+ plugin: string; // plugin that registered the command
20
+ description: string; // human-readable description from describe()
21
+ timestamp: number;
22
+ enabled: boolean; // can be disabled (skipped during replay) without removing
23
+ params: Record<string, unknown>;
24
+ }
25
+
26
+ // --- Reactive state ---
27
+
28
+ let entries = $state<ActionLogEntry[]>([]);
29
+ let selectedEntryId = $state<string | null>(null);
30
+ let filterQuery = $state<string>('');
31
+
32
+ // --- Derived ---
33
+
34
+ /**
35
+ * Entries filtered by the current query string.
36
+ * Matches against description and command type (case-insensitive).
37
+ */
38
+ const filteredEntries = $derived.by(() => {
39
+ if (!filterQuery.trim()) return entries;
40
+ const q = filterQuery.toLowerCase();
41
+ return entries.filter(
42
+ (e) =>
43
+ e.description.toLowerCase().includes(q) ||
44
+ e.type.toLowerCase().includes(q) ||
45
+ e.plugin.toLowerCase().includes(q),
46
+ );
47
+ });
48
+
49
+ // --- Operations ---
50
+
51
+ /** Select an entry by its commandId. */
52
+ function selectEntry(id: string): void {
53
+ selectedEntryId = id;
54
+ }
55
+
56
+ /** Toggle an entry's enabled state (for replay skipping). */
57
+ function toggleEntry(id: string): void {
58
+ const entry = entries.find((e) => e.commandId === id);
59
+ if (entry) {
60
+ entry.enabled = !entry.enabled;
61
+ }
62
+ }
63
+
64
+ /** Remove an entry from the action log entirely. */
65
+ function deleteEntry(id: string): void {
66
+ const idx = entries.findIndex((e) => e.commandId === id);
67
+ if (idx !== -1) {
68
+ entries.splice(idx, 1);
69
+ }
70
+ if (selectedEntryId === id) {
71
+ selectedEntryId = null;
72
+ }
73
+ }
74
+
75
+ /** Look up a single entry by commandId. */
76
+ function getEntry(id: string): ActionLogEntry | undefined {
77
+ return entries.find((e) => e.commandId === id);
78
+ }
79
+
80
+ /**
81
+ * Rebuild the action log from the dispatcher's semantic log and undo stack.
82
+ * The semantic log provides descriptions/timestamps/ids; the undo stack
83
+ * provides the full Command objects with type, plugin, and params.
84
+ */
85
+ function syncFromDispatcher(): void {
86
+ const log = getSemanticLog();
87
+ const stack = getUndoStack();
88
+
89
+ // Build a lookup from commandId -> UndoEntry for quick access
90
+ const undoMap = new Map(stack.map((e) => [e.command.id, e]));
91
+
92
+ // Preserve existing enabled states across sync
93
+ const enabledMap = new Map(entries.map((e) => [e.commandId, e.enabled]));
94
+
95
+ const synced: ActionLogEntry[] = [];
96
+ for (const logEntry of log) {
97
+ const undoEntry = undoMap.get(logEntry.commandId);
98
+ if (!undoEntry) continue; // entry was dropped from undo stack (size limit)
99
+
100
+ synced.push({
101
+ commandId: logEntry.commandId,
102
+ type: undoEntry.command.type,
103
+ plugin: undoEntry.command.plugin,
104
+ description: logEntry.description,
105
+ timestamp: logEntry.timestamp,
106
+ enabled: enabledMap.get(logEntry.commandId) ?? true,
107
+ // Clone params to avoid shared mutation with the command system
108
+ params: { ...undoEntry.command.params },
109
+ });
110
+ }
111
+
112
+ entries = synced;
113
+ }
114
+
115
+ /** Set the filter query string. */
116
+ function setFilterQuery(query: string): void {
117
+ filterQuery = query;
118
+ }
119
+
120
+ /** Get a snapshot of all entries (for replay engine, macros, etc.). */
121
+ function getEntries(): ActionLogEntry[] {
122
+ return entries;
123
+ }
124
+
125
+ /** Replace the entire entries array (used by replay/reorder operations). */
126
+ function setEntries(newEntries: ActionLogEntry[]): void {
127
+ entries = newEntries;
128
+ }
129
+
130
+ /**
131
+ * Clear all entries and reset selection state.
132
+ * Note: this only clears the local action-log view. The dispatcher's
133
+ * semantic log and undo stack are left intact, so the next call to
134
+ * syncFromDispatcher() (triggered by any new command dispatch) will
135
+ * rebuild the entries from the underlying log. This matches the
136
+ * behavior of deleteEntry().
137
+ */
138
+ function clearAll(): void {
139
+ entries = [];
140
+ selectedEntryId = null;
141
+ }
142
+
143
+ /** Get the currently selected entry ID. */
144
+ function getSelectedEntryId(): string | null {
145
+ return selectedEntryId;
146
+ }
147
+
148
+ /** Get the filtered entries. */
149
+ function getFilteredEntries(): ActionLogEntry[] {
150
+ return filteredEntries;
151
+ }
152
+
153
+ // --- Public API ---
154
+
155
+ export const actionLog = {
156
+ get entries() { return entries; },
157
+ get selectedEntryId() { return selectedEntryId; },
158
+ get filterQuery() { return filterQuery; },
159
+ get filteredEntries() { return filteredEntries; },
160
+
161
+ selectEntry,
162
+ toggleEntry,
163
+ deleteEntry,
164
+ getEntry,
165
+ getEntries,
166
+ setEntries,
167
+ clearAll,
168
+ syncFromDispatcher,
169
+ setFilterQuery,
170
+ getSelectedEntryId,
171
+ getFilteredEntries,
172
+ };