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,278 @@
1
+ /**
2
+ * Color Effects -- per-pixel color manipulation commands.
3
+ *
4
+ * Registers:
5
+ * - Command: `invert` (tier: 'frame') -- inverts RGB channels
6
+ * - Command: `grayscale` (tier: 'frame') -- converts to luminance-based gray
7
+ * - Command: `colorize` (tier: 'frame') -- tints all non-transparent pixels
8
+ * - Command: `brightness_contrast` (tier: 'frame') -- adjusts brightness/contrast
9
+ * - Command: `hue_shift` (tier: 'frame') -- shifts hue by degrees
10
+ *
11
+ * All effects operate on every pixel in the active buffer.
12
+ */
13
+
14
+ import type { PluginModule } from '../../../src/lib/core/plugin-loader.js';
15
+ import type { PixelBuffer } from '../../../src/lib/canvas/pixel-buffer.js';
16
+ import type { PixelData, Point } from '../drawing-utils.js';
17
+ import { snapshotPixels, applyPixels, hexToRgba, makeSnapshotUndo } from '../drawing-utils.js';
18
+ import { rgbToHsv, hsvToRgb } from '../../../src/lib/color/color-utils.js';
19
+ import Contrast from '~icons/lucide/contrast';
20
+ import Eclipse from '~icons/lucide/eclipse';
21
+ import Paintbrush from '~icons/lucide/paintbrush';
22
+ import SunMoon from '~icons/lucide/sun-moon';
23
+ import Rainbow from '~icons/lucide/rainbow';
24
+
25
+ // --- Helpers ---
26
+
27
+ /** Snapshot the entire buffer and return it. */
28
+ function snapshotAll(buffer: PixelBuffer): PixelData[] {
29
+ const allPoints: Point[] = [];
30
+ for (let y = 0; y < buffer.height; y++) {
31
+ for (let x = 0; x < buffer.width; x++) {
32
+ allPoints.push({ x, y });
33
+ }
34
+ }
35
+ return snapshotPixels(buffer, allPoints);
36
+ }
37
+
38
+ /** Clamp a value to 0-255. */
39
+ function clamp255(v: number): number {
40
+ return Math.max(0, Math.min(255, Math.round(v)));
41
+ }
42
+
43
+ // --- Pixel transform functions (exported for testability) ---
44
+
45
+ /** Invert RGB channels, preserve alpha. */
46
+ export function invertPixels(buffer: PixelBuffer): PixelData[] {
47
+ const pixels: PixelData[] = [];
48
+ for (let y = 0; y < buffer.height; y++) {
49
+ for (let x = 0; x < buffer.width; x++) {
50
+ const [r, g, b, a] = buffer.getPixel(x, y);
51
+ pixels.push({ x, y, r: 255 - r, g: 255 - g, b: 255 - b, a });
52
+ }
53
+ }
54
+ return pixels;
55
+ }
56
+
57
+ /** Convert to luminance-based grayscale (ITU-R BT.601), preserve alpha. */
58
+ export function grayscalePixels(buffer: PixelBuffer): PixelData[] {
59
+ const pixels: PixelData[] = [];
60
+ for (let y = 0; y < buffer.height; y++) {
61
+ for (let x = 0; x < buffer.width; x++) {
62
+ const [r, g, b, a] = buffer.getPixel(x, y);
63
+ const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
64
+ pixels.push({ x, y, r: gray, g: gray, b: gray, a });
65
+ }
66
+ }
67
+ return pixels;
68
+ }
69
+
70
+ /** Tint non-transparent pixels with a color, blending via luminance. */
71
+ export function colorizePixels(buffer: PixelBuffer, color: string): PixelData[] {
72
+ const tint = hexToRgba(color);
73
+ const pixels: PixelData[] = [];
74
+ for (let y = 0; y < buffer.height; y++) {
75
+ for (let x = 0; x < buffer.width; x++) {
76
+ const [r, g, b, a] = buffer.getPixel(x, y);
77
+ if (a === 0) {
78
+ pixels.push({ x, y, r, g, b, a });
79
+ continue;
80
+ }
81
+ // Blend: use original luminance with tint hue/saturation
82
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
83
+ pixels.push({
84
+ x, y,
85
+ r: clamp255(tint.r * lum),
86
+ g: clamp255(tint.g * lum),
87
+ b: clamp255(tint.b * lum),
88
+ a,
89
+ });
90
+ }
91
+ }
92
+ return pixels;
93
+ }
94
+
95
+ /** Adjust brightness (-255 to 255) and contrast (-100 to 100). */
96
+ export function brightnessContrastPixels(
97
+ buffer: PixelBuffer,
98
+ brightness: number,
99
+ contrast: number,
100
+ ): PixelData[] {
101
+ // Contrast factor: maps [-100, 100] to a multiplier
102
+ const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
103
+ const pixels: PixelData[] = [];
104
+
105
+ for (let y = 0; y < buffer.height; y++) {
106
+ for (let x = 0; x < buffer.width; x++) {
107
+ const [r, g, b, a] = buffer.getPixel(x, y);
108
+ pixels.push({
109
+ x, y,
110
+ r: clamp255(factor * (r + brightness - 128) + 128),
111
+ g: clamp255(factor * (g + brightness - 128) + 128),
112
+ b: clamp255(factor * (b + brightness - 128) + 128),
113
+ a,
114
+ });
115
+ }
116
+ }
117
+ return pixels;
118
+ }
119
+
120
+ /** Shift hue by a number of degrees (0-360). Preserve saturation, value, and alpha. */
121
+ export function hueShiftPixels(buffer: PixelBuffer, degrees: number): PixelData[] {
122
+ const pixels: PixelData[] = [];
123
+ for (let y = 0; y < buffer.height; y++) {
124
+ for (let x = 0; x < buffer.width; x++) {
125
+ const [r, g, b, a] = buffer.getPixel(x, y);
126
+ if (a === 0) {
127
+ pixels.push({ x, y, r, g, b, a });
128
+ continue;
129
+ }
130
+ const hsv = rgbToHsv(r, g, b);
131
+ const newH = ((hsv.h + degrees) % 360 + 360) % 360;
132
+ const rgb = hsvToRgb(newH, hsv.s, hsv.v);
133
+ pixels.push({ x, y, r: rgb.r, g: rgb.g, b: rgb.b, a });
134
+ }
135
+ }
136
+ return pixels;
137
+ }
138
+
139
+ // --- Plugin ---
140
+
141
+ export const colorEffectsPlugin: PluginModule = {
142
+ name: 'builtin/effects/color-effects',
143
+ version: '1.0.0',
144
+ dependencies: [],
145
+ register(api) {
146
+ // -- Invert --
147
+ api.addCommand('invert', {
148
+ tier: 'frame',
149
+ label: 'Invert',
150
+ category: 'Effects',
151
+ icon: Contrast,
152
+ execute(_params, ctx) {
153
+ const buffer = ctx.getActiveBuffer?.();
154
+ if (!buffer) return;
155
+ const snapshot = snapshotAll(buffer);
156
+ applyPixels(buffer, invertPixels(buffer));
157
+ return snapshot;
158
+ },
159
+ undo: makeSnapshotUndo(),
160
+ describe() { return 'Inverted colors'; },
161
+ });
162
+
163
+ // -- Grayscale --
164
+ api.addCommand('grayscale', {
165
+ tier: 'frame',
166
+ label: 'Grayscale',
167
+ category: 'Effects',
168
+ icon: Eclipse,
169
+ execute(_params, ctx) {
170
+ const buffer = ctx.getActiveBuffer?.();
171
+ if (!buffer) return;
172
+ const snapshot = snapshotAll(buffer);
173
+ applyPixels(buffer, grayscalePixels(buffer));
174
+ return snapshot;
175
+ },
176
+ undo: makeSnapshotUndo(),
177
+ describe() { return 'Converted to grayscale'; },
178
+ });
179
+
180
+ // -- Colorize --
181
+ api.addCommand('colorize', {
182
+ tier: 'frame',
183
+ label: 'Colorize',
184
+ category: 'Effects',
185
+ icon: Paintbrush,
186
+ execute(params, ctx) {
187
+ const buffer = ctx.getActiveBuffer?.();
188
+ if (!buffer) return;
189
+ const snapshot = snapshotAll(buffer);
190
+ applyPixels(buffer, colorizePixels(buffer, params["color"]));
191
+ return snapshot;
192
+ },
193
+ undo: makeSnapshotUndo(),
194
+ describe(params) { return `Colorized with ${String(params["color"])}`; },
195
+ });
196
+
197
+ // -- Brightness/Contrast --
198
+ api.addCommand('brightness_contrast', {
199
+ tier: 'frame',
200
+ label: 'Brightness/Contrast',
201
+ category: 'Effects',
202
+ icon: SunMoon,
203
+ execute(params, ctx) {
204
+ const buffer = ctx.getActiveBuffer?.();
205
+ if (!buffer) return;
206
+ const snapshot = snapshotAll(buffer);
207
+ applyPixels(buffer, brightnessContrastPixels(
208
+ buffer,
209
+ params["brightness"],
210
+ params["contrast"],
211
+ ));
212
+ return snapshot;
213
+ },
214
+ undo: makeSnapshotUndo(),
215
+ describe(params) {
216
+ return `Adjusted brightness=${String(params["brightness"])} contrast=${String(params["contrast"])}`;
217
+ },
218
+ });
219
+
220
+ // -- Hue Shift --
221
+ api.addCommand('hue_shift', {
222
+ tier: 'frame',
223
+ label: 'Hue Shift',
224
+ category: 'Effects',
225
+ icon: Rainbow,
226
+ execute(params, ctx) {
227
+ const buffer = ctx.getActiveBuffer?.();
228
+ if (!buffer) return;
229
+ const snapshot = snapshotAll(buffer);
230
+ applyPixels(buffer, hueShiftPixels(buffer, params["degrees"]));
231
+ return snapshot;
232
+ },
233
+ undo: makeSnapshotUndo(),
234
+ describe(params) { return `Shifted hue by ${String(params["degrees"])} degrees`; },
235
+ });
236
+
237
+ // -- Menu items for all color effects --
238
+ api.addMenuItem('menu:effects:invert', {
239
+ commandId: 'invert',
240
+ menuPath: 'effects',
241
+ group: 'adjustments',
242
+ order: 10,
243
+ label: 'Invert',
244
+ });
245
+
246
+ api.addMenuItem('menu:effects:grayscale', {
247
+ commandId: 'grayscale',
248
+ menuPath: 'effects',
249
+ group: 'adjustments',
250
+ order: 20,
251
+ label: 'Grayscale',
252
+ });
253
+
254
+ api.addMenuItem('menu:effects:colorize', {
255
+ commandId: 'colorize',
256
+ menuPath: 'effects',
257
+ group: 'adjustments',
258
+ order: 30,
259
+ label: 'Colorize',
260
+ });
261
+
262
+ api.addMenuItem('menu:effects:brightness-contrast', {
263
+ commandId: 'brightness_contrast',
264
+ menuPath: 'effects',
265
+ group: 'adjustments',
266
+ order: 40,
267
+ label: 'Brightness/Contrast',
268
+ });
269
+
270
+ api.addMenuItem('menu:effects:hue-shift', {
271
+ commandId: 'hue_shift',
272
+ menuPath: 'effects',
273
+ group: 'adjustments',
274
+ order: 50,
275
+ label: 'Hue Shift',
276
+ });
277
+ },
278
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Flip Effect -- flips the active layer horizontally or vertically.
3
+ *
4
+ * Registers:
5
+ * - Command: `flip` (tier: 'frame')
6
+ */
7
+
8
+ import type { PluginModule } from '../../../src/lib/core/plugin-loader.js';
9
+ import type { PixelBuffer } from '../../../src/lib/canvas/pixel-buffer.js';
10
+ import type { PixelData, Point } from '../drawing-utils.js';
11
+ import { snapshotPixels, applyPixels, makeSnapshotUndo } from '../drawing-utils.js';
12
+ import FlipHorizontal2 from '~icons/lucide/flip-horizontal-2';
13
+
14
+ /**
15
+ * Compute flipped pixel data for a buffer.
16
+ *
17
+ * @param direction 'h' for horizontal (left-right), 'v' for vertical (top-bottom)
18
+ */
19
+ export function computeFlip(
20
+ buffer: PixelBuffer,
21
+ direction: 'h' | 'v',
22
+ ): PixelData[] {
23
+ const w = buffer.width;
24
+ const h = buffer.height;
25
+ const pixels: PixelData[] = [];
26
+
27
+ for (let dy = 0; dy < h; dy++) {
28
+ for (let dx = 0; dx < w; dx++) {
29
+ const sx = direction === 'h' ? (w - 1 - dx) : dx;
30
+ const sy = direction === 'v' ? (h - 1 - dy) : dy;
31
+ const [r, g, b, a] = buffer.getPixel(sx, sy);
32
+ pixels.push({ x: dx, y: dy, r, g, b, a });
33
+ }
34
+ }
35
+
36
+ return pixels;
37
+ }
38
+
39
+ export const flipEffectPlugin: PluginModule = {
40
+ name: 'builtin/effects/flip',
41
+ version: '1.0.0',
42
+ dependencies: [],
43
+ register(api) {
44
+ api.addCommand('flip', {
45
+ tier: 'frame',
46
+ label: 'Flip',
47
+ category: 'Effects',
48
+ icon: FlipHorizontal2,
49
+
50
+ execute(params, ctx) {
51
+ const buffer = ctx.getActiveBuffer?.();
52
+ if (!buffer) return;
53
+
54
+ const allPoints: Point[] = [];
55
+ for (let y = 0; y < buffer.height; y++) {
56
+ for (let x = 0; x < buffer.width; x++) {
57
+ allPoints.push({ x, y });
58
+ }
59
+ }
60
+ const snapshot = snapshotPixels(buffer, allPoints);
61
+
62
+ const pixels = computeFlip(buffer, params["direction"]);
63
+ applyPixels(buffer, pixels);
64
+ return snapshot;
65
+ },
66
+
67
+ undo: makeSnapshotUndo(),
68
+
69
+ describe(params) {
70
+ const dir = params["direction"] === 'h' ? 'horizontally' : 'vertically';
71
+ return `Flipped layer ${dir}`;
72
+ },
73
+ });
74
+
75
+ api.addMenuItem('menu:effects:flip', {
76
+ commandId: 'flip',
77
+ menuPath: 'effects',
78
+ group: 'transform',
79
+ order: 10,
80
+ label: 'Flip',
81
+ });
82
+ },
83
+ };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Glow Effect -- adds a soft glow around non-transparent pixels.
3
+ *
4
+ * Registers:
5
+ * - Command: `glow` (tier: 'frame')
6
+ *
7
+ * Expands non-transparent pixels by `radius`, blurs the expansion,
8
+ * and blends it with the given color at reduced opacity.
9
+ * The glow only fills currently transparent pixels.
10
+ */
11
+
12
+ import type { PluginModule } from '../../../src/lib/core/plugin-loader.js';
13
+ import type { PixelBuffer } from '../../../src/lib/canvas/pixel-buffer.js';
14
+ import type { PixelData } from '../drawing-utils.js';
15
+ import { snapshotPixels, applyPixels, hexToRgba, makeSnapshotUndo } from '../drawing-utils.js';
16
+ import Sun from '~icons/lucide/sun';
17
+
18
+ /**
19
+ * Compute glow pixels for a buffer.
20
+ * For each transparent pixel within `radius` Chebyshev distance of a non-transparent pixel,
21
+ * set it to the glow color with opacity that fades with distance.
22
+ */
23
+ export function computeGlow(
24
+ buffer: PixelBuffer,
25
+ radius: number,
26
+ color: string,
27
+ opacity: number,
28
+ ): PixelData[] {
29
+ const w = buffer.width;
30
+ const h = buffer.height;
31
+ const c = hexToRgba(color);
32
+ const pixels: PixelData[] = [];
33
+
34
+ for (let y = 0; y < h; y++) {
35
+ for (let x = 0; x < w; x++) {
36
+ const [, , , a] = buffer.getPixel(x, y);
37
+ // Only add glow to transparent pixels
38
+ if (a > 0) continue;
39
+
40
+ // Find the nearest non-transparent pixel within radius (Chebyshev distance)
41
+ let minDist = Infinity;
42
+ for (let dy = -radius; dy <= radius; dy++) {
43
+ for (let dx = -radius; dx <= radius; dx++) {
44
+ if (dx === 0 && dy === 0) continue;
45
+ const nx = x + dx;
46
+ const ny = y + dy;
47
+ if (!buffer.inBounds(nx, ny)) continue;
48
+
49
+ const [, , , na] = buffer.getPixel(nx, ny);
50
+ if (na > 0) {
51
+ // Use Euclidean distance for smoother falloff
52
+ const dist = Math.sqrt(dx * dx + dy * dy);
53
+ if (dist < minDist) minDist = dist;
54
+ }
55
+ }
56
+ }
57
+
58
+ if (minDist <= radius) {
59
+ // Fade opacity linearly with distance
60
+ const falloff = 1 - (minDist / (radius + 1));
61
+ const glowAlpha = Math.round(opacity * 255 * falloff);
62
+ if (glowAlpha > 0) {
63
+ pixels.push({ x, y, r: c.r, g: c.g, b: c.b, a: glowAlpha });
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ return pixels;
70
+ }
71
+
72
+ export const glowEffectPlugin: PluginModule = {
73
+ name: 'builtin/effects/glow',
74
+ version: '1.0.0',
75
+ dependencies: [],
76
+ register(api) {
77
+ api.addCommand('glow', {
78
+ tier: 'frame',
79
+ label: 'Glow',
80
+ category: 'Effects',
81
+ icon: Sun,
82
+
83
+ execute(params, ctx) {
84
+ const buffer = ctx.getActiveBuffer?.();
85
+ if (!buffer) return;
86
+
87
+ const glowPixels = computeGlow(
88
+ buffer,
89
+ params["radius"],
90
+ params["color"],
91
+ params["opacity"],
92
+ );
93
+
94
+ const snapshot = snapshotPixels(
95
+ buffer,
96
+ glowPixels.map((p) => ({ x: p.x, y: p.y })),
97
+ );
98
+
99
+ applyPixels(buffer, glowPixels);
100
+ return snapshot;
101
+ },
102
+
103
+ undo: makeSnapshotUndo(),
104
+
105
+ describe(params) {
106
+ return `Added glow radius=${String(params["radius"])} ${String(params["color"])} at ${String(params["opacity"])} opacity`;
107
+ },
108
+ });
109
+
110
+ api.addMenuItem('menu:effects:glow', {
111
+ commandId: 'glow',
112
+ menuPath: 'effects',
113
+ group: 'stylize',
114
+ order: 10,
115
+ label: 'Glow',
116
+ });
117
+ },
118
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Outline Effect -- adds an N-pixel outline around non-transparent pixels.
3
+ *
4
+ * Registers:
5
+ * - Command: `outline` (tier: 'frame')
6
+ *
7
+ * For each transparent pixel that has a non-transparent neighbor within
8
+ * `thickness` distance, it is filled with the outline color.
9
+ */
10
+
11
+ import type { PluginModule } from '../../../src/lib/core/plugin-loader.js';
12
+ import type { PixelBuffer } from '../../../src/lib/canvas/pixel-buffer.js';
13
+ import type { PixelData } from '../drawing-utils.js';
14
+ import { snapshotPixels, applyPixels, hexToRgba, makeSnapshotUndo } from '../drawing-utils.js';
15
+ import Frame from '~icons/lucide/frame';
16
+
17
+ /**
18
+ * Compute outline pixels for a buffer.
19
+ * Finds all transparent pixels within `thickness` Chebyshev distance
20
+ * of any non-transparent pixel, and sets them to the outline color.
21
+ */
22
+ export function computeOutline(
23
+ buffer: PixelBuffer,
24
+ thickness: number,
25
+ color: string,
26
+ ): PixelData[] {
27
+ const w = buffer.width;
28
+ const h = buffer.height;
29
+ const c = hexToRgba(color);
30
+ const pixels: PixelData[] = [];
31
+
32
+ for (let y = 0; y < h; y++) {
33
+ for (let x = 0; x < w; x++) {
34
+ const [, , , a] = buffer.getPixel(x, y);
35
+ // Only add outline to currently transparent pixels
36
+ if (a > 0) continue;
37
+
38
+ // Check if any non-transparent pixel is within thickness distance
39
+ let shouldOutline = false;
40
+ outer:
41
+ for (let dy = -thickness; dy <= thickness; dy++) {
42
+ for (let dx = -thickness; dx <= thickness; dx++) {
43
+ if (dx === 0 && dy === 0) continue;
44
+ const nx = x + dx;
45
+ const ny = y + dy;
46
+ if (!buffer.inBounds(nx, ny)) continue;
47
+
48
+ const [, , , na] = buffer.getPixel(nx, ny);
49
+ if (na > 0) {
50
+ shouldOutline = true;
51
+ break outer;
52
+ }
53
+ }
54
+ }
55
+
56
+ if (shouldOutline) {
57
+ pixels.push({ x, y, r: c.r, g: c.g, b: c.b, a: c.a });
58
+ }
59
+ }
60
+ }
61
+
62
+ return pixels;
63
+ }
64
+
65
+ export const outlineEffectPlugin: PluginModule = {
66
+ name: 'builtin/effects/outline',
67
+ version: '1.0.0',
68
+ dependencies: [],
69
+ register(api) {
70
+ api.addCommand('outline', {
71
+ tier: 'frame',
72
+ label: 'Outline',
73
+ category: 'Effects',
74
+ icon: Frame,
75
+
76
+ execute(params, ctx) {
77
+ const buffer = ctx.getActiveBuffer?.();
78
+ if (!buffer) return;
79
+
80
+ const outlinePixels = computeOutline(
81
+ buffer,
82
+ params["thickness"],
83
+ params["color"],
84
+ );
85
+
86
+ const snapshot = snapshotPixels(
87
+ buffer,
88
+ outlinePixels.map((p) => ({ x: p.x, y: p.y })),
89
+ );
90
+
91
+ applyPixels(buffer, outlinePixels);
92
+ return snapshot;
93
+ },
94
+
95
+ undo: makeSnapshotUndo(),
96
+
97
+ describe(params) {
98
+ return `Added ${String(params["thickness"])}px outline in ${String(params["color"])}`;
99
+ },
100
+ });
101
+
102
+ api.addMenuItem('menu:effects:outline', {
103
+ commandId: 'outline',
104
+ menuPath: 'effects',
105
+ group: 'stylize',
106
+ order: 30,
107
+ label: 'Outline',
108
+ });
109
+ },
110
+ };