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,243 @@
1
+ /**
2
+ * Color conversion utilities for PixelWeaver.
3
+ *
4
+ * Thin wrappers around `culori` that normalize to PixelWeaver's conventions:
5
+ * - RGB values: 0-255 integers
6
+ * - HSV/HSL: H 0-360, S 0-100, V/L 0-100
7
+ * - Hex: uppercase 6-digit "#RRGGBB"
8
+ * - Perceptual operations use OKLab color space
9
+ */
10
+
11
+ import {
12
+ parse,
13
+ formatHex,
14
+ formatHex8,
15
+ interpolate as culoriInterpolate,
16
+ differenceEuclidean,
17
+ converter,
18
+ type Rgb,
19
+ } from 'culori';
20
+
21
+ const toRgb = converter('rgb');
22
+ const toHsv = converter('hsv');
23
+ const toHsl = converter('hsl');
24
+ // Pre-built OKLab distance function (reused across calls)
25
+ const oklabDistance = differenceEuclidean('oklab');
26
+
27
+ // --- Types ---
28
+
29
+ export interface RgbColor {
30
+ r: number;
31
+ g: number;
32
+ b: number;
33
+ }
34
+
35
+ export interface RgbaColor extends RgbColor {
36
+ a: number;
37
+ }
38
+
39
+ export interface HsvColor {
40
+ h: number;
41
+ s: number;
42
+ v: number;
43
+ }
44
+
45
+ export interface HslColor {
46
+ h: number;
47
+ s: number;
48
+ l: number;
49
+ }
50
+
51
+ // --- Helpers ---
52
+
53
+ /** Clamp a value to [min, max] and round to integer */
54
+ function clampRound(value: number, min: number, max: number): number {
55
+ return Math.round(Math.min(max, Math.max(min, value)));
56
+ }
57
+
58
+ // --- Public API ---
59
+
60
+ /**
61
+ * Parse any CSS color string to an RGBA object.
62
+ * Returns null if the input cannot be parsed.
63
+ */
64
+ export function parseColor(input: string): RgbaColor | null {
65
+ const parsed = parse(input);
66
+ if (!parsed) return null;
67
+
68
+ // After the parsed check, toRgb returns a non-null Rgb (culori's
69
+ // converter is only nullable when its input may be undefined).
70
+ const rgb = toRgb(parsed) as Rgb & { alpha?: number };
71
+
72
+ return {
73
+ r: clampRound(rgb.r * 255, 0, 255),
74
+ g: clampRound(rgb.g * 255, 0, 255),
75
+ b: clampRound(rgb.b * 255, 0, 255),
76
+ a: rgb.alpha !== undefined ? rgb.alpha : 1,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Convert a hex color string to RGB (0-255).
82
+ * Assumes valid 6-digit hex input (e.g. "#FF0000").
83
+ */
84
+ export function hexToRgb(hex: string): RgbColor {
85
+ const parsed = parse(hex);
86
+ if (!parsed) throw new Error(`Invalid hex color: ${hex}`);
87
+
88
+ const rgb = toRgb(parsed);
89
+ return {
90
+ r: clampRound(rgb.r * 255, 0, 255),
91
+ g: clampRound(rgb.g * 255, 0, 255),
92
+ b: clampRound(rgb.b * 255, 0, 255),
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Convert RGB (0-255) to uppercase 6-digit hex.
98
+ */
99
+ export function rgbToHex(r: number, g: number, b: number): string {
100
+ const hex = formatHex({ mode: 'rgb', r: r / 255, g: g / 255, b: b / 255 });
101
+ return hex.toUpperCase();
102
+ }
103
+
104
+ /**
105
+ * Convert RGBA (0-255) to uppercase 8-digit hex "#RRGGBBAA".
106
+ */
107
+ export function rgbaToHex(r: number, g: number, b: number, a: number): string {
108
+ const hex = formatHex8({
109
+ mode: 'rgb',
110
+ r: r / 255,
111
+ g: g / 255,
112
+ b: b / 255,
113
+ alpha: a / 255,
114
+ });
115
+ return hex.toUpperCase();
116
+ }
117
+
118
+ /**
119
+ * Convert a hex color string to RGBA (0-255).
120
+ * Accepts 3-digit, 6-digit, and 8-digit hex inputs (with or without alpha).
121
+ * When the input carries no alpha channel, defaults to `a: 255`.
122
+ */
123
+ export function hexToRgba(hex: string): RgbaColor {
124
+ const parsed = parse(hex);
125
+ if (!parsed) throw new Error(`Invalid hex color: ${hex}`);
126
+
127
+ const rgb = toRgb(parsed) as Rgb & { alpha?: number };
128
+ return {
129
+ r: clampRound(rgb.r * 255, 0, 255),
130
+ g: clampRound(rgb.g * 255, 0, 255),
131
+ b: clampRound(rgb.b * 255, 0, 255),
132
+ // culori omits `alpha` when the source had no alpha channel; default to 255
133
+ a: rgb.alpha !== undefined ? clampRound(rgb.alpha * 255, 0, 255) : 255,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Convert RGB (0-255) to HSV (H: 0-360, S: 0-100, V: 0-100).
139
+ */
140
+ export function rgbToHsv(r: number, g: number, b: number): HsvColor {
141
+ const hsv = toHsv({ mode: 'rgb', r: r / 255, g: g / 255, b: b / 255 });
142
+ return {
143
+ // culori omits h for achromatic colors; default to 0
144
+ h: clampRound(hsv.h ?? 0, 0, 360),
145
+ s: clampRound(hsv.s * 100, 0, 100),
146
+ v: clampRound(hsv.v * 100, 0, 100),
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Convert HSV (H: 0-360, S: 0-100, V: 0-100) to RGB (0-255).
152
+ */
153
+ export function hsvToRgb(h: number, s: number, v: number): RgbColor {
154
+ const rgb = toRgb({ mode: 'hsv', h, s: s / 100, v: v / 100 });
155
+ return {
156
+ r: clampRound(rgb.r * 255, 0, 255),
157
+ g: clampRound(rgb.g * 255, 0, 255),
158
+ b: clampRound(rgb.b * 255, 0, 255),
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Convert RGB (0-255) to HSL (H: 0-360, S: 0-100, L: 0-100).
164
+ */
165
+ export function rgbToHsl(r: number, g: number, b: number): HslColor {
166
+ const hsl = toHsl({ mode: 'rgb', r: r / 255, g: g / 255, b: b / 255 });
167
+ return {
168
+ // culori omits h for achromatic colors; default to 0
169
+ h: clampRound(hsl.h ?? 0, 0, 360),
170
+ s: clampRound(hsl.s * 100, 0, 100),
171
+ l: clampRound(hsl.l * 100, 0, 100),
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Convert HSL (H: 0-360, S: 0-100, L: 0-100) to RGB (0-255).
177
+ */
178
+ export function hslToRgb(h: number, s: number, l: number): RgbColor {
179
+ const rgb = toRgb({ mode: 'hsl', h, s: s / 100, l: l / 100 });
180
+ return {
181
+ r: clampRound(rgb.r * 255, 0, 255),
182
+ g: clampRound(rgb.g * 255, 0, 255),
183
+ b: clampRound(rgb.b * 255, 0, 255),
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Interpolate between two hex colors in OKLab space.
189
+ * t=0 returns color1, t=1 returns color2.
190
+ * Returns uppercase 6-digit hex.
191
+ */
192
+ export function lerpColor(color1: string, color2: string, t: number): string {
193
+ const interp = culoriInterpolate([color1, color2], 'oklab');
194
+ const result = formatHex(interp(t));
195
+ return result.toUpperCase();
196
+ }
197
+
198
+ /**
199
+ * Find the nearest color in a palette to the given hex color.
200
+ * Uses Euclidean distance in OKLab space for perceptual accuracy.
201
+ * Returns the closest palette color (or the input if palette is empty).
202
+ */
203
+ export function nearestPaletteColor(hex: string, palette: string[]): string {
204
+ if (palette.length === 0) return hex;
205
+
206
+ const target = parse(hex);
207
+ if (!target) return hex;
208
+
209
+ // Safe: palette.length === 0 is checked above
210
+ const first = palette[0];
211
+ if (first === undefined) return hex;
212
+ let bestColor = first;
213
+ let bestDist = Infinity;
214
+
215
+ for (const candidate of palette) {
216
+ const parsed = parse(candidate);
217
+ if (!parsed) continue;
218
+
219
+ const dist = oklabDistance(target, parsed);
220
+ if (dist < bestDist) {
221
+ bestDist = dist;
222
+ bestColor = candidate;
223
+ }
224
+ }
225
+
226
+ return bestColor;
227
+ }
228
+
229
+ /**
230
+ * Compute perceptual color distance between two hex colors.
231
+ * Uses Euclidean distance in OKLab space, normalized to 0-1 range.
232
+ * Same color returns 0; maximally different colors approach 1.
233
+ */
234
+ export function colorDistance(hex1: string, hex2: string): number {
235
+ const c1 = parse(hex1);
236
+ const c2 = parse(hex2);
237
+ if (!c1 || !c2) return 1;
238
+
239
+ const raw = oklabDistance(c1, c2);
240
+ // OKLab Euclidean distance for maximally different colors (black vs white)
241
+ // is roughly ~1.0. We clamp to [0,1] for a clean API.
242
+ return Math.min(1, raw);
243
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Reactive palette state for PixelWeaver.
3
+ *
4
+ * Module-level singleton using Svelte 5 $state runes.
5
+ * Manages project/global palettes, palette lock mode,
6
+ * and auto-collected colors from the canvas.
7
+ */
8
+
9
+ import type { Palette } from './palette.js';
10
+ import { BUILTIN_PALETTES } from './palette.js';
11
+ import { nearestPaletteColor } from './color-utils.js';
12
+
13
+ // --- Reactive state ---
14
+
15
+ let projectPalette = $state<Palette | null>(null);
16
+
17
+ // Global palettes start with built-in presets; user can add more
18
+ let globalPalettes = $state<Palette[]>([...BUILTIN_PALETTES]);
19
+
20
+ let paletteLocked = $state(false);
21
+
22
+ // The palette used for locking (project palette or a selected global palette)
23
+ let activePalette = $state<Palette | null>(null);
24
+
25
+ // All unique colors found in the current project (auto-updated by drawing tools)
26
+ let autoCollectedColors = $state<string[]>([]);
27
+
28
+ // --- Public API ---
29
+
30
+ export function getProjectPalette(): Palette | null {
31
+ return projectPalette;
32
+ }
33
+
34
+ export function setProjectPalette(palette: Palette | null): void {
35
+ projectPalette = palette;
36
+ }
37
+
38
+ export function getGlobalPalettes(): Palette[] {
39
+ return globalPalettes;
40
+ }
41
+
42
+ export function addGlobalPalette(palette: Palette): void {
43
+ globalPalettes = [...globalPalettes, palette];
44
+ }
45
+
46
+ export function removeGlobalPalette(name: string): void {
47
+ globalPalettes = globalPalettes.filter((p) => p.name !== name);
48
+ }
49
+
50
+ export function isPaletteLocked(): boolean {
51
+ return paletteLocked;
52
+ }
53
+
54
+ export function setPaletteLocked(locked: boolean): void {
55
+ paletteLocked = locked;
56
+ }
57
+
58
+ export function getActivePalette(): Palette | null {
59
+ return activePalette;
60
+ }
61
+
62
+ export function setActivePalette(palette: Palette | null): void {
63
+ activePalette = palette;
64
+ }
65
+
66
+ export function getAutoCollectedColors(): string[] {
67
+ return autoCollectedColors;
68
+ }
69
+
70
+ /**
71
+ * Register a color as used on the canvas.
72
+ * Deduplicates automatically.
73
+ */
74
+ export function addAutoCollectedColor(hex: string): void {
75
+ if (!autoCollectedColors.includes(hex)) {
76
+ autoCollectedColors = [...autoCollectedColors, hex];
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Snap a color to the nearest color in the active palette.
82
+ * Returns the input unchanged if palette lock is off or no active palette is set.
83
+ */
84
+ export function snapToActivePalette(hex: string): string {
85
+ if (!paletteLocked || !activePalette || activePalette.colors.length === 0) {
86
+ return hex;
87
+ }
88
+ return nearestPaletteColor(hex, activePalette.colors);
89
+ }
90
+
91
+ // --- Serialization ---
92
+
93
+ /** Serialized shape of project-level palette state. */
94
+ export interface PaletteStateSnapshot {
95
+ projectPalette: Palette | null;
96
+ paletteLocked: boolean;
97
+ }
98
+
99
+ /** Export project-level palette state as a plain JSON-safe object. */
100
+ export function serialize(): PaletteStateSnapshot {
101
+ const pp = getProjectPalette();
102
+ return {
103
+ projectPalette: pp ? { name: pp.name, colors: [...pp.colors] } : null,
104
+ paletteLocked: isPaletteLocked(),
105
+ };
106
+ }
107
+
108
+ /** Restore project-level palette state from a serialized snapshot. */
109
+ export function deserialize(data: PaletteStateSnapshot): void {
110
+ setProjectPalette(
111
+ data.projectPalette
112
+ ? { name: data.projectPalette.name, colors: [...data.projectPalette.colors] }
113
+ : null,
114
+ );
115
+ setPaletteLocked(data.paletteLocked);
116
+ }
117
+
118
+ /**
119
+ * Reset all palette state to defaults. Intended for tests only.
120
+ */
121
+ export function _resetForTesting(): void {
122
+ projectPalette = null;
123
+ globalPalettes = [...BUILTIN_PALETTES];
124
+ paletteLocked = false;
125
+ activePalette = null;
126
+ autoCollectedColors = [];
127
+ }
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import {
3
+ getProjectPalette,
4
+ setProjectPalette,
5
+ getGlobalPalettes,
6
+ addGlobalPalette,
7
+ removeGlobalPalette,
8
+ isPaletteLocked,
9
+ setPaletteLocked,
10
+ getActivePalette,
11
+ setActivePalette,
12
+ getAutoCollectedColors,
13
+ addAutoCollectedColor,
14
+ snapToActivePalette,
15
+ _resetForTesting,
16
+ } from './palette-state.svelte.js';
17
+ import { BUILTIN_PALETTES } from './palette.js';
18
+ import type { Palette } from './palette.js';
19
+
20
+ describe('Palette State', () => {
21
+ beforeEach(() => {
22
+ _resetForTesting();
23
+ });
24
+
25
+ // --- Built-in palettes ---
26
+
27
+ it('should include built-in palettes in global palettes', () => {
28
+ const globals = getGlobalPalettes();
29
+ for (const builtin of BUILTIN_PALETTES) {
30
+ const found = globals.find((p) => p.name === builtin.name);
31
+ expect(found).toBeDefined();
32
+ expect(found?.readonly).toBe(true);
33
+ }
34
+ });
35
+
36
+ it('built-in palettes should have valid hex colors', () => {
37
+ for (const palette of BUILTIN_PALETTES) {
38
+ for (const color of palette.colors) {
39
+ expect(color).toMatch(/^#[0-9A-F]{6}$/);
40
+ }
41
+ }
42
+ });
43
+
44
+ // --- Project palette ---
45
+
46
+ it('should default project palette to null', () => {
47
+ expect(getProjectPalette()).toBeNull();
48
+ });
49
+
50
+ it('should set and get project palette', () => {
51
+ const pal: Palette = { name: 'Test', colors: ['#FF0000'] };
52
+ setProjectPalette(pal);
53
+ expect(getProjectPalette()).toStrictEqual(pal);
54
+ });
55
+
56
+ // --- Global palette management ---
57
+
58
+ it('should add a global palette', () => {
59
+ const before = getGlobalPalettes().length;
60
+ addGlobalPalette({ name: 'Custom', colors: ['#123456'] });
61
+ expect(getGlobalPalettes()).toHaveLength(before + 1);
62
+ expect(getGlobalPalettes().find((p) => p.name === 'Custom')).toBeDefined();
63
+ });
64
+
65
+ it('should remove a global palette by name', () => {
66
+ addGlobalPalette({ name: 'ToRemove', colors: ['#000000'] });
67
+ removeGlobalPalette('ToRemove');
68
+ expect(getGlobalPalettes().find((p) => p.name === 'ToRemove')).toBeUndefined();
69
+ });
70
+
71
+ // --- Palette lock ---
72
+
73
+ it('should default palette lock to false', () => {
74
+ expect(isPaletteLocked()).toBe(false);
75
+ });
76
+
77
+ it('should toggle palette lock', () => {
78
+ setPaletteLocked(true);
79
+ expect(isPaletteLocked()).toBe(true);
80
+ setPaletteLocked(false);
81
+ expect(isPaletteLocked()).toBe(false);
82
+ });
83
+
84
+ // --- Active palette ---
85
+
86
+ it('should default active palette to null', () => {
87
+ expect(getActivePalette()).toBeNull();
88
+ });
89
+
90
+ it('should set active palette', () => {
91
+ const pal: Palette = { name: 'Active', colors: ['#FF0000', '#00FF00'] };
92
+ setActivePalette(pal);
93
+ expect(getActivePalette()).toStrictEqual(pal);
94
+ });
95
+
96
+ // --- snapToActivePalette ---
97
+
98
+ it('should return input unchanged when palette is not locked', () => {
99
+ setActivePalette({ name: 'Test', colors: ['#FF0000'] });
100
+ setPaletteLocked(false);
101
+ expect(snapToActivePalette('#00FF00')).toBe('#00FF00');
102
+ });
103
+
104
+ it('should return input unchanged when no active palette is set', () => {
105
+ setPaletteLocked(true);
106
+ setActivePalette(null);
107
+ expect(snapToActivePalette('#00FF00')).toBe('#00FF00');
108
+ });
109
+
110
+ it('should snap to nearest palette color when locked', () => {
111
+ const palette: Palette = {
112
+ name: 'RGB',
113
+ colors: ['#FF0000', '#00FF00', '#0000FF'],
114
+ };
115
+ setActivePalette(palette);
116
+ setPaletteLocked(true);
117
+
118
+ // A dark red should snap to red
119
+ expect(snapToActivePalette('#CC1111')).toBe('#FF0000');
120
+ // A dark green should snap to green
121
+ expect(snapToActivePalette('#11CC11')).toBe('#00FF00');
122
+ // A dark blue should snap to blue
123
+ expect(snapToActivePalette('#1111CC')).toBe('#0000FF');
124
+ });
125
+
126
+ it('should snap exact palette color to itself', () => {
127
+ const palette: Palette = {
128
+ name: 'Test',
129
+ colors: ['#FF0000', '#00FF00'],
130
+ };
131
+ setActivePalette(palette);
132
+ setPaletteLocked(true);
133
+
134
+ expect(snapToActivePalette('#FF0000')).toBe('#FF0000');
135
+ });
136
+
137
+ // --- Auto-collected colors ---
138
+
139
+ it('should start with empty auto-collected colors', () => {
140
+ expect(getAutoCollectedColors()).toEqual([]);
141
+ });
142
+
143
+ it('should add auto-collected colors', () => {
144
+ addAutoCollectedColor('#FF0000');
145
+ addAutoCollectedColor('#00FF00');
146
+ expect(getAutoCollectedColors()).toEqual(['#FF0000', '#00FF00']);
147
+ });
148
+
149
+ it('should deduplicate auto-collected colors', () => {
150
+ addAutoCollectedColor('#FF0000');
151
+ addAutoCollectedColor('#FF0000');
152
+ expect(getAutoCollectedColors()).toEqual(['#FF0000']);
153
+ });
154
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Palette definitions and built-in presets for PixelWeaver.
3
+ *
4
+ * All colors are uppercase 6-digit hex strings (#RRGGBB).
5
+ */
6
+
7
+ export interface Palette {
8
+ name: string;
9
+ colors: string[];
10
+ readonly?: boolean;
11
+ }
12
+
13
+ /**
14
+ * Built-in palette presets. These are marked readonly so users
15
+ * cannot accidentally modify them (they can duplicate and edit copies).
16
+ */
17
+ export const BUILTIN_PALETTES: Palette[] = [
18
+ {
19
+ name: 'PICO-8',
20
+ colors: [
21
+ '#000000', '#1D2B53', '#7E2553', '#008751',
22
+ '#AB5236', '#5F574F', '#C2C3C7', '#FFF1E8',
23
+ '#FF004D', '#FFA300', '#FFEC27', '#00E436',
24
+ '#29ADFF', '#83769C', '#FF77A8', '#FFCCAA',
25
+ ],
26
+ readonly: true,
27
+ },
28
+ {
29
+ name: 'GameBoy',
30
+ colors: ['#0F380F', '#306230', '#8BAC0F', '#9BBC0F'],
31
+ readonly: true,
32
+ },
33
+ {
34
+ name: 'NES',
35
+ colors: [
36
+ '#666666', '#002A88', '#1412A7', '#3B00A4',
37
+ '#5C007E', '#6E0040', '#6C0600', '#561D00',
38
+ '#333500', '#0B4800', '#005200', '#004F08',
39
+ '#00404D', '#000000', '#000000', '#000000',
40
+ '#ADADAD', '#155FD9', '#4240FF', '#7527FE',
41
+ '#A01ACC', '#B71E7B', '#B53120', '#994E00',
42
+ '#6B6D00', '#388700', '#0C9300', '#008F32',
43
+ '#007C8D', '#000000', '#000000', '#000000',
44
+ '#FFFEFF', '#64B0FF', '#9290FF', '#C676FF',
45
+ '#F36AFF', '#FE6ECC', '#FE8170', '#EA9E22',
46
+ '#BCBE00', '#88D800', '#5CE430', '#45E082',
47
+ '#48CDDE', '#4F4F4F', '#000000', '#000000',
48
+ '#FFFEFF', '#C0DFFF', '#D3D2FF', '#E8C8FF',
49
+ '#FBC2FF', '#FEC4EA', '#FECCC5', '#F7D8A5',
50
+ '#E4E594', '#CFEF96', '#BDF4AB', '#B3F3CC',
51
+ '#B5EBF2', '#B8B8B8', '#000000', '#000000',
52
+ ],
53
+ readonly: true,
54
+ },
55
+ {
56
+ name: 'Sweetie 16',
57
+ colors: [
58
+ '#1A1C2C', '#5D275D', '#B13E53', '#EF7D57',
59
+ '#FFCD75', '#A7F070', '#38B764', '#257179',
60
+ '#29366F', '#3B5DC9', '#41A6F6', '#73EFF7',
61
+ '#F4F4F4', '#94B0C2', '#566C86', '#333C57',
62
+ ],
63
+ readonly: true,
64
+ },
65
+ {
66
+ name: 'Endesga 32',
67
+ colors: [
68
+ '#BE4A2F', '#D77643', '#EAD4AA', '#E4A672',
69
+ '#B86F50', '#733E39', '#3E2731', '#A22633',
70
+ '#E43B44', '#F77622', '#FEAE34', '#FEE761',
71
+ '#63C74D', '#3E8948', '#265C42', '#193C3E',
72
+ '#124E89', '#0099DB', '#2CE8F5', '#FFFFFF',
73
+ '#C0CBDC', '#8B9BB4', '#5A6988', '#3A4466',
74
+ '#262B44', '#181425', '#FF0044', '#68386C',
75
+ '#B55088', '#F6757A', '#E8B796', '#C28569',
76
+ ],
77
+ readonly: true,
78
+ },
79
+ ];
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Bootstrap -- discovers all plugin modules via Vite's import.meta.glob
3
+ * and loads them through the plugin loader in dependency order.
4
+ *
5
+ * Must run synchronously before Svelte mounts so that tools, commands,
6
+ * shortcuts, and panels are available on first render.
7
+ */
8
+
9
+ import { loadPlugins } from './plugin-loader.js';
10
+ import type { PluginModule } from './plugin-loader.js';
11
+
12
+ // Discover all plugin modules via Vite's import.meta.glob.
13
+ // Patterns target three categories:
14
+ // 1. Builtin tools and effects in plugins/builtin/
15
+ // 2. System/UI plugins matching *-plugin.ts in src/lib/
16
+ // 3. Command plugins matching *-commands.ts in src/lib/
17
+ const pluginModules = import.meta.glob(
18
+ [
19
+ '/plugins/builtin/**/*.ts',
20
+ '!/plugins/builtin/**/*.test.ts',
21
+ '/src/lib/**/*-plugin.ts',
22
+ '/src/lib/**/*-commands.ts',
23
+ ],
24
+ { eager: true }
25
+ );
26
+
27
+ // Type guard: checks if a value has the shape of a PluginModule
28
+ function isPluginModule(value: unknown): value is PluginModule {
29
+ return (
30
+ typeof value === 'object' &&
31
+ value !== null &&
32
+ 'name' in value &&
33
+ typeof (value as Record<string, unknown>)["name"] === 'string' &&
34
+ 'version' in value &&
35
+ typeof (value as Record<string, unknown>)["version"] === 'string' &&
36
+ 'register' in value &&
37
+ typeof (value as Record<string, unknown>)["register"] === 'function'
38
+ );
39
+ }
40
+
41
+ // Extract PluginModule exports from all discovered modules.
42
+ // The type guard filters out non-plugin modules (like drawing-utils.ts)
43
+ // that match the glob patterns but don't export a PluginModule. The seen
44
+ // set prevents duplicate registration if a plugin somehow appears in
45
+ // multiple glob patterns.
46
+ function discoverPlugins(): PluginModule[] {
47
+ const plugins: PluginModule[] = [];
48
+ const seen = new Set<string>();
49
+
50
+ for (const mod of Object.values(pluginModules)) {
51
+ const exports = mod as Record<string, unknown>;
52
+ for (const value of Object.values(exports)) {
53
+ if (isPluginModule(value) && !seen.has(value.name)) {
54
+ plugins.push(value);
55
+ seen.add(value.name);
56
+ }
57
+ }
58
+ }
59
+
60
+ return plugins;
61
+ }
62
+
63
+ export function bootstrap(): void {
64
+ const plugins = discoverPlugins();
65
+ loadPlugins(plugins);
66
+ }