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,53 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import {
3
+ enablePlugin,
4
+ disablePlugin,
5
+ isPluginEnabled,
6
+ _resetForTesting,
7
+ } from './plugin-discovery.js';
8
+
9
+ describe('plugin-discovery', () => {
10
+ beforeEach(() => {
11
+ _resetForTesting();
12
+ });
13
+
14
+ it('should report a plugin as disabled by default', () => {
15
+ expect(isPluginEnabled('test-plugin')).toBe(false);
16
+ });
17
+
18
+ it('should enable a plugin', () => {
19
+ enablePlugin('test-plugin');
20
+ expect(isPluginEnabled('test-plugin')).toBe(true);
21
+ });
22
+
23
+ it('should disable an enabled plugin', () => {
24
+ enablePlugin('test-plugin');
25
+ disablePlugin('test-plugin');
26
+ expect(isPluginEnabled('test-plugin')).toBe(false);
27
+ });
28
+
29
+ it('should track multiple plugins independently', () => {
30
+ enablePlugin('plugin-a');
31
+ enablePlugin('plugin-b');
32
+ expect(isPluginEnabled('plugin-a')).toBe(true);
33
+ expect(isPluginEnabled('plugin-b')).toBe(true);
34
+
35
+ disablePlugin('plugin-a');
36
+ expect(isPluginEnabled('plugin-a')).toBe(false);
37
+ expect(isPluginEnabled('plugin-b')).toBe(true);
38
+ });
39
+
40
+ it('should handle enabling an already-enabled plugin (idempotent)', () => {
41
+ enablePlugin('test-plugin');
42
+ enablePlugin('test-plugin');
43
+ expect(isPluginEnabled('test-plugin')).toBe(true);
44
+ disablePlugin('test-plugin');
45
+ expect(isPluginEnabled('test-plugin')).toBe(false);
46
+ });
47
+
48
+ it('should handle disabling a non-enabled plugin gracefully', () => {
49
+ // Should not throw
50
+ disablePlugin('nonexistent');
51
+ expect(isPluginEnabled('nonexistent')).toBe(false);
52
+ });
53
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Plugin Discovery -- discovers plugins from a server endpoint and manages
3
+ * enable/disable state at runtime.
4
+ *
5
+ * The server is expected to respond with a JSON array of discovered plugin
6
+ * metadata at the /api/plugins endpoint.
7
+ */
8
+
9
+ /** Metadata for a discovered plugin. */
10
+ export interface DiscoveredPlugin {
11
+ path: string;
12
+ name: string;
13
+ version: string;
14
+ description?: string;
15
+ author?: string;
16
+ loaded: boolean;
17
+ enabled: boolean;
18
+ error?: string;
19
+ }
20
+
21
+ // --- Module state: enabled/disabled tracking ---
22
+
23
+ const enabledPlugins = new Set<string>();
24
+
25
+ /**
26
+ * Scan for plugins from the server's plugin directory.
27
+ *
28
+ * Fetches plugin metadata from `{serverUrl}/api/plugins` and returns
29
+ * the list with each plugin's `enabled` field reflecting local state.
30
+ */
31
+ export async function discoverPlugins(serverUrl: string): Promise<DiscoveredPlugin[]> {
32
+ const url = `${serverUrl.replace(/\/$/, '')}/api/plugins`;
33
+ const response = await fetch(url);
34
+ if (!response.ok) {
35
+ throw new Error(`Failed to discover plugins: ${String(response.status)} ${response.statusText}`);
36
+ }
37
+ const plugins = (await response.json()) as DiscoveredPlugin[];
38
+ // Reconcile server data with local enabled state
39
+ return plugins.map((p) => ({
40
+ ...p,
41
+ enabled: enabledPlugins.has(p.name),
42
+ }));
43
+ }
44
+
45
+ /** Enable a plugin by name. */
46
+ export function enablePlugin(name: string): void {
47
+ enabledPlugins.add(name);
48
+ }
49
+
50
+ /** Disable a plugin by name. */
51
+ export function disablePlugin(name: string): void {
52
+ enabledPlugins.delete(name);
53
+ }
54
+
55
+ /** Check whether a plugin is currently enabled. */
56
+ export function isPluginEnabled(name: string): boolean {
57
+ return enabledPlugins.has(name);
58
+ }
59
+
60
+ /**
61
+ * Reset discovery state. Intended for tests only.
62
+ */
63
+ export function _resetForTesting(): void {
64
+ enabledPlugins.clear();
65
+ }
@@ -0,0 +1,159 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import type { PluginModule } from './plugin-loader.js';
3
+ import { loadPlugins, getLoadedPlugin, getAllLoadedPlugins, _resetForTesting } from './plugin-loader.js';
4
+ import { _resetForTesting as resetDispatcher } from './dispatcher.js';
5
+
6
+ // --- Helpers ---
7
+
8
+ function makePlugin(
9
+ name: string,
10
+ deps: string[] = [],
11
+ register?: PluginModule['register'],
12
+ ): PluginModule {
13
+ return {
14
+ name,
15
+ version: '1.0.0',
16
+ description: `Test plugin: ${name}`,
17
+ author: 'Test Author',
18
+ dependencies: deps,
19
+ register: register ?? vi.fn(),
20
+ };
21
+ }
22
+
23
+ // --- Tests ---
24
+
25
+ describe('Plugin Loader', () => {
26
+ beforeEach(() => {
27
+ _resetForTesting();
28
+ resetDispatcher();
29
+ });
30
+
31
+ it('should call register() on a single plugin', () => {
32
+ const plugin = makePlugin('alpha');
33
+ loadPlugins([plugin]);
34
+
35
+ expect(plugin.register).toHaveBeenCalledOnce();
36
+ // The register callback receives a PluginAPI object
37
+ // vitest's asymmetric matchers return `any`, which leaks through to
38
+ // no-unsafe-assignment. Wrap each in an explicit unknown cast.
39
+ expect(plugin.register).toHaveBeenCalledWith(
40
+ expect.objectContaining({
41
+ addCommand: expect.any(Function) as unknown,
42
+ addTool: expect.any(Function) as unknown,
43
+ dispatch: expect.any(Function) as unknown,
44
+ }),
45
+ );
46
+ });
47
+
48
+ it('should load plugins in dependency order (B before A when A depends on B)', () => {
49
+ const order: string[] = [];
50
+
51
+ const pluginB = makePlugin('B', [], () => order.push('B'));
52
+ const pluginA = makePlugin('A', ['B'], () => order.push('A'));
53
+
54
+ loadPlugins([pluginA, pluginB]);
55
+
56
+ expect(order).toEqual(['B', 'A']);
57
+ });
58
+
59
+ it('should handle a longer dependency chain', () => {
60
+ const order: string[] = [];
61
+
62
+ const pluginC = makePlugin('C', [], () => order.push('C'));
63
+ const pluginB = makePlugin('B', ['C'], () => order.push('B'));
64
+ const pluginA = makePlugin('A', ['B'], () => order.push('A'));
65
+
66
+ // Pass them in reverse order to prove sorting works
67
+ loadPlugins([pluginA, pluginB, pluginC]);
68
+
69
+ expect(order).toEqual(['C', 'B', 'A']);
70
+ });
71
+
72
+ it('should handle a diamond dependency pattern', () => {
73
+ const order: string[] = [];
74
+
75
+ const pluginD = makePlugin('D', [], () => order.push('D'));
76
+ const pluginB = makePlugin('B', ['D'], () => order.push('B'));
77
+ const pluginC = makePlugin('C', ['D'], () => order.push('C'));
78
+ const pluginA = makePlugin('A', ['B', 'C'], () => order.push('A'));
79
+
80
+ loadPlugins([pluginA, pluginC, pluginB, pluginD]);
81
+
82
+ // D must come first; B and C after D; A last
83
+ expect(order.indexOf('D')).toBe(0);
84
+ expect(order.indexOf('A')).toBe(3);
85
+ expect(order.indexOf('B')).toBeGreaterThan(order.indexOf('D'));
86
+ expect(order.indexOf('C')).toBeGreaterThan(order.indexOf('D'));
87
+ });
88
+
89
+ it('should throw on circular dependency', () => {
90
+ const pluginA = makePlugin('A', ['B']);
91
+ const pluginB = makePlugin('B', ['A']);
92
+
93
+ expect(() => loadPlugins([pluginA, pluginB])).toThrow('Circular dependency');
94
+ });
95
+
96
+ it('should throw on missing dependency', () => {
97
+ const plugin = makePlugin('A', ['nonexistent']);
98
+
99
+ expect(() => loadPlugins([plugin])).toThrow(
100
+ 'Plugin "A" depends on "nonexistent", which is not in the plugin list.',
101
+ );
102
+ });
103
+
104
+ it('should store and retrieve plugin metadata', () => {
105
+ const plugin = makePlugin('my-plugin');
106
+ plugin.version = '2.3.1';
107
+ plugin.description = 'My cool plugin';
108
+ plugin.author = 'Jane Doe';
109
+
110
+ loadPlugins([plugin]);
111
+
112
+ const meta = getLoadedPlugin('my-plugin');
113
+ expect(meta).toBeDefined();
114
+ expect(meta?.name).toBe('my-plugin');
115
+ expect(meta?.version).toBe('2.3.1');
116
+ expect(meta?.description).toBe('My cool plugin');
117
+ expect(meta?.author).toBe('Jane Doe');
118
+ });
119
+
120
+ it('should list all loaded plugins', () => {
121
+ const pluginA = makePlugin('A');
122
+ const pluginB = makePlugin('B');
123
+
124
+ loadPlugins([pluginA, pluginB]);
125
+
126
+ const all = getAllLoadedPlugins();
127
+ expect(all.size).toBe(2);
128
+ expect(all.has('A')).toBe(true);
129
+ expect(all.has('B')).toBe(true);
130
+ });
131
+
132
+ it('should return the loaded plugins in load order', () => {
133
+ const pluginB = makePlugin('B');
134
+ const pluginA = makePlugin('A', ['B']);
135
+
136
+ const result = loadPlugins([pluginA, pluginB]);
137
+
138
+ expect(result).toHaveLength(2);
139
+ expect(result[0]?.name).toBe('B');
140
+ expect(result[1]?.name).toBe('A');
141
+ });
142
+
143
+ it('should load plugins with no dependencies in any order without error', () => {
144
+ const pluginA = makePlugin('A');
145
+ const pluginB = makePlugin('B');
146
+ const pluginC = makePlugin('C');
147
+
148
+ const result = loadPlugins([pluginA, pluginB, pluginC]);
149
+ expect(result).toHaveLength(3);
150
+ });
151
+
152
+ it('should throw on a three-node circular dependency', () => {
153
+ const pluginA = makePlugin('A', ['C']);
154
+ const pluginB = makePlugin('B', ['A']);
155
+ const pluginC = makePlugin('C', ['B']);
156
+
157
+ expect(() => loadPlugins([pluginA, pluginB, pluginC])).toThrow('Circular dependency');
158
+ });
159
+ });
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Plugin Loader -- discovers, orders, and initialises plugins.
3
+ *
4
+ * Responsibilities:
5
+ * - Accepts an array of plugin modules
6
+ * - Resolves load order via topological sort on declared dependencies
7
+ * - Calls register(api) on each plugin in dependency order
8
+ * - Reports circular or missing dependencies
9
+ * - Maintains a registry of loaded plugin metadata
10
+ */
11
+
12
+ import type { PluginAPI } from './plugin-api.js';
13
+ import { createPluginAPI } from './plugin-api.js';
14
+ import {
15
+ commandRegistry,
16
+ toolRegistry,
17
+ panelRegistry,
18
+ exporterRegistry,
19
+ importerRegistry,
20
+ shortcutRegistry,
21
+ menuRegistry,
22
+ toolbarRegistry,
23
+ } from './registries.svelte.js';
24
+ import { pluginState } from '../ui/plugin-state.svelte.js';
25
+ import type { LoadedPluginRecord } from '../ui/plugin-state.svelte.js';
26
+
27
+ // --- Plugin module interface ---
28
+
29
+ export interface PluginModule {
30
+ name: string;
31
+ version: string;
32
+ description?: string;
33
+ author?: string;
34
+ dependencies?: string[];
35
+ register: (api: PluginAPI) => void;
36
+ }
37
+
38
+ // --- Loaded plugin metadata ---
39
+
40
+ export interface LoadedPlugin {
41
+ name: string;
42
+ version: string;
43
+ description?: string;
44
+ author?: string;
45
+ }
46
+
47
+ // --- Module state ---
48
+
49
+ const loadedPlugins: Map<string, LoadedPlugin> = new Map();
50
+
51
+ /**
52
+ * Topological sort using Kahn's algorithm.
53
+ * Returns the sorted plugin names or throws on circular / missing dependencies.
54
+ */
55
+ function topologicalSort(plugins: PluginModule[]): string[] {
56
+ const nameSet = new Set(plugins.map((p) => p.name));
57
+
58
+ // Check for missing dependencies first
59
+ for (const plugin of plugins) {
60
+ for (const dep of plugin.dependencies ?? []) {
61
+ if (!nameSet.has(dep)) {
62
+ throw new Error(
63
+ `Plugin "${plugin.name}" depends on "${dep}", which is not in the plugin list.`,
64
+ );
65
+ }
66
+ }
67
+ }
68
+
69
+ // Build adjacency list and in-degree map
70
+ const inDegree = new Map<string, number>();
71
+ const dependents = new Map<string, string[]>(); // dep -> plugins that depend on it
72
+
73
+ for (const plugin of plugins) {
74
+ inDegree.set(plugin.name, 0);
75
+ dependents.set(plugin.name, []);
76
+ }
77
+
78
+ for (const plugin of plugins) {
79
+ for (const dep of plugin.dependencies ?? []) {
80
+ inDegree.set(plugin.name, (inDegree.get(plugin.name) ?? 0) + 1);
81
+ // Missing dependency: register an entry so `current`-driven traversal
82
+ // can still complete; the missing dep just never satisfies, which
83
+ // surfaces as a cycle error downstream.
84
+ const list = dependents.get(dep) ?? (() => {
85
+ const fresh: string[] = [];
86
+ dependents.set(dep, fresh);
87
+ return fresh;
88
+ })();
89
+ list.push(plugin.name);
90
+ }
91
+ }
92
+
93
+ // Seed queue with plugins that have no dependencies
94
+ const queue: string[] = [];
95
+ for (const [name, degree] of inDegree) {
96
+ if (degree === 0) queue.push(name);
97
+ }
98
+
99
+ const sorted: string[] = [];
100
+
101
+ while (queue.length > 0) {
102
+ const current = queue.shift();
103
+ if (current === undefined) break;
104
+ sorted.push(current);
105
+
106
+ for (const dependent of dependents.get(current) ?? []) {
107
+ const newDegree = (inDegree.get(dependent) ?? 1) - 1;
108
+ inDegree.set(dependent, newDegree);
109
+ if (newDegree === 0) queue.push(dependent);
110
+ }
111
+ }
112
+
113
+ if (sorted.length !== plugins.length) {
114
+ // Some plugins were never added -- they form a cycle
115
+ const stuck = plugins
116
+ .map((p) => p.name)
117
+ .filter((n) => !sorted.includes(n));
118
+ throw new Error(`Circular dependency detected among plugins: ${stuck.join(', ')}`);
119
+ }
120
+
121
+ return sorted;
122
+ }
123
+
124
+ /**
125
+ * Load an array of plugin modules in dependency order.
126
+ * Each plugin's register() receives a PluginAPI scoped to that plugin.
127
+ *
128
+ * Returns the ordered list of loaded plugin metadata.
129
+ */
130
+ export function loadPlugins(plugins: PluginModule[]): LoadedPlugin[] {
131
+ const order = topologicalSort(plugins);
132
+ const byName = new Map(plugins.map((p) => [p.name, p]));
133
+ const result: LoadedPlugin[] = [];
134
+
135
+ for (const name of order) {
136
+ const plugin = byName.get(name);
137
+ if (!plugin) continue; // topologicalSort only emits names from `plugins`
138
+ const api = createPluginAPI(plugin.name);
139
+
140
+ // Snapshot registry keys before register() so we can diff afterwards to
141
+ // attribute contributions to this specific plugin. Works because plugin
142
+ // order is deterministic (topological) and registrations happen inline.
143
+ const before = snapshotRegistryKeys();
144
+
145
+ let registerError: string | undefined;
146
+ try {
147
+ plugin.register(api);
148
+ } catch (err) {
149
+ registerError = err instanceof Error ? err.message : String(err);
150
+ }
151
+
152
+ const after = snapshotRegistryKeys();
153
+ const contributions = diffKeys(before, after);
154
+
155
+ // Omit description/author when absent (exactOptionalPropertyTypes)
156
+ const meta: LoadedPlugin = {
157
+ name: plugin.name,
158
+ version: plugin.version,
159
+ };
160
+ if (plugin.description !== undefined) meta.description = plugin.description;
161
+ if (plugin.author !== undefined) meta.author = plugin.author;
162
+ loadedPlugins.set(plugin.name, meta);
163
+ result.push(meta);
164
+
165
+ // Publish into the reactive plugin-state store so the Plugin Manager
166
+ // panel can display each plugin and its contributions.
167
+ const record: LoadedPluginRecord = {
168
+ name: plugin.name,
169
+ version: plugin.version,
170
+ dependencies: plugin.dependencies ?? [],
171
+ loaded: registerError === undefined,
172
+ contributions,
173
+ };
174
+ if (plugin.description !== undefined) record.description = plugin.description;
175
+ if (plugin.author !== undefined) record.author = plugin.author;
176
+ if (registerError !== undefined) record.error = registerError;
177
+ pluginState.addPlugin(record);
178
+ }
179
+
180
+ return result;
181
+ }
182
+
183
+ // --- Registry snapshot helpers (for attributing contributions to a plugin) ---
184
+
185
+ interface RegistrySnapshot {
186
+ commands: Set<string>;
187
+ tools: Set<string>;
188
+ panels: Set<string>;
189
+ exporters: Set<string>;
190
+ importers: Set<string>;
191
+ shortcuts: Set<string>;
192
+ menuItems: Set<string>;
193
+ toolbarItems: Set<string>;
194
+ }
195
+
196
+ function snapshotRegistryKeys(): RegistrySnapshot {
197
+ return {
198
+ commands: new Set(commandRegistry.getAll().keys()),
199
+ tools: new Set(toolRegistry.getAll().keys()),
200
+ panels: new Set(panelRegistry.getAll().keys()),
201
+ exporters: new Set(exporterRegistry.getAll().keys()),
202
+ importers: new Set(importerRegistry.getAll().keys()),
203
+ shortcuts: new Set(shortcutRegistry.getAll().keys()),
204
+ menuItems: new Set(menuRegistry.getAll().keys()),
205
+ toolbarItems: new Set(toolbarRegistry.getAll().keys()),
206
+ };
207
+ }
208
+
209
+ function diffKeys(before: RegistrySnapshot, after: RegistrySnapshot) {
210
+ const added = (b: Set<string>, a: Set<string>) =>
211
+ [...a].filter((k) => !b.has(k)).sort();
212
+ return {
213
+ commands: added(before.commands, after.commands),
214
+ tools: added(before.tools, after.tools),
215
+ panels: added(before.panels, after.panels),
216
+ exporters: added(before.exporters, after.exporters),
217
+ importers: added(before.importers, after.importers),
218
+ shortcuts: added(before.shortcuts, after.shortcuts),
219
+ menuItems: added(before.menuItems, after.menuItems),
220
+ toolbarItems: added(before.toolbarItems, after.toolbarItems),
221
+ };
222
+ }
223
+
224
+ /** Get metadata for a loaded plugin by name */
225
+ export function getLoadedPlugin(name: string): LoadedPlugin | undefined {
226
+ return loadedPlugins.get(name);
227
+ }
228
+
229
+ /** Get all loaded plugins */
230
+ export function getAllLoadedPlugins(): ReadonlyMap<string, LoadedPlugin> {
231
+ return loadedPlugins;
232
+ }
233
+
234
+ /**
235
+ * Reset loader state. Intended for tests only.
236
+ */
237
+ export function _resetForTesting(): void {
238
+ loadedPlugins.clear();
239
+ pluginState._resetForTesting();
240
+ }