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.
- package/.env.development +1 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc +16 -0
- package/.python-version +1 -0
- package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
- package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
- package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/bases/CHANGELOG.md +5 -0
- package/.rlsbl/bases/LICENSE +21 -0
- package/.rlsbl/changes/.validated +1 -0
- package/.rlsbl/changes/0.1.0.jsonl +85 -0
- package/.rlsbl/changes/0.1.0.md +13 -0
- package/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/config.json +6 -0
- package/.rlsbl/hashes.json +14 -0
- package/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/releases/unreleased.toml +0 -0
- package/.rlsbl/releases/v0.1.0.toml +3 -0
- package/.rlsbl/version +1 -0
- package/.selfdoc/hashes/hashes.json +146 -0
- package/.strictcli/schema.json +227 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +100 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/assets/icon.png +0 -0
- package/docs/_README.md +117 -0
- package/docs/cli-config.md +35 -0
- package/docs/cli-dev.md +21 -0
- package/docs/cli-diagnose.md +12 -0
- package/docs/cli-index.md +30 -0
- package/docs/cli-list.md +18 -0
- package/docs/cli-mcp.md +18 -0
- package/docs/cli-new.md +26 -0
- package/docs/cli-open.md +21 -0
- package/docs/cli-serve.md +21 -0
- package/docs/cli-stop.md +12 -0
- package/docs/gen-index.md +36 -0
- package/docs/index.md +13 -0
- package/docs/server-src-pixelweaver-__main__.md +12 -0
- package/docs/server-src-pixelweaver-autosave.md +12 -0
- package/docs/server-src-pixelweaver-bridge.md +12 -0
- package/docs/server-src-pixelweaver-cli.md +12 -0
- package/docs/server-src-pixelweaver-config.md +12 -0
- package/docs/server-src-pixelweaver-connections.md +12 -0
- package/docs/server-src-pixelweaver-main.md +12 -0
- package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
- package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
- package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
- package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
- package/docs/server-src-pixelweaver-mcp_server.md +12 -0
- package/docs/server-src-pixelweaver-protocol.md +12 -0
- package/docs/server-src-pixelweaver-state.md +12 -0
- package/docs/server-src-pixelweaver-storage.md +12 -0
- package/docs/server-src-pixelweaver-websocket.md +12 -0
- package/docs/server-src-pixelweaver.md +12 -0
- package/e2e/app-launch.test.ts +35 -0
- package/e2e/menus.test.ts +26 -0
- package/e2e/tools.test.ts +27 -0
- package/e2e/undo-redo.test.ts +11 -0
- package/eslint.config.js +62 -0
- package/index.html +13 -0
- package/package.json +48 -0
- package/playwright.config.ts +19 -0
- package/plugins/builtin/.gitkeep +0 -0
- package/plugins/builtin/advanced-fill-tool.ts +146 -0
- package/plugins/builtin/circle-tool.ts +186 -0
- package/plugins/builtin/diamond-tool.ts +182 -0
- package/plugins/builtin/dither-tool.ts +186 -0
- package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
- package/plugins/builtin/drawing-utils.test.ts +495 -0
- package/plugins/builtin/drawing-utils.ts +431 -0
- package/plugins/builtin/effects/blur.ts +97 -0
- package/plugins/builtin/effects/color-effects.ts +278 -0
- package/plugins/builtin/effects/flip.ts +83 -0
- package/plugins/builtin/effects/glow.ts +118 -0
- package/plugins/builtin/effects/outline.ts +110 -0
- package/plugins/builtin/effects/rotate.ts +357 -0
- package/plugins/builtin/effects/scale.ts +258 -0
- package/plugins/builtin/effects/shadow.ts +111 -0
- package/plugins/builtin/effects/sharpen.ts +102 -0
- package/plugins/builtin/effects.test.ts +715 -0
- package/plugins/builtin/eraser-tool.ts +23 -0
- package/plugins/builtin/eyedropper-tool.ts +83 -0
- package/plugins/builtin/fill-tool.ts +93 -0
- package/plugins/builtin/gradient-tool.ts +204 -0
- package/plugins/builtin/gradient.test.ts +142 -0
- package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
- package/plugins/builtin/importers/aseprite-parser.ts +497 -0
- package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
- package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
- package/plugins/builtin/line-tool.ts +267 -0
- package/plugins/builtin/make-stroke-tool.ts +271 -0
- package/plugins/builtin/noise-dither.test.ts +151 -0
- package/plugins/builtin/noise-tool.ts +131 -0
- package/plugins/builtin/pattern-stamp-tool.ts +162 -0
- package/plugins/builtin/pencil-tool.ts +25 -0
- package/plugins/builtin/rect-tool.ts +179 -0
- package/plugins/builtin/selection-tool.ts +388 -0
- package/plugins/builtin/selection.test.ts +195 -0
- package/plugins/builtin/tool-plugins.test.ts +529 -0
- package/public/favicon.svg +7 -0
- package/public/sw.js +91 -0
- package/pyproject.toml +49 -0
- package/scripts/eslint-wave-a-list.sh +24 -0
- package/scripts/eslint-wave-a-status.sh +25 -0
- package/scripts/fix-index-signature-access.py +127 -0
- package/scripts/fix-unchecked-index.py +128 -0
- package/scripts/fix-unchecked-vars.py +127 -0
- package/scripts/fix-wave-a-bangs.py +36 -0
- package/scripts/fix-wave-a-templates.py +108 -0
- package/scripts/fix-wave-b-void-expr.py +167 -0
- package/scripts/generate-command-params.py +540 -0
- package/scripts/migrate-single-frame-to-multi.py +171 -0
- package/scripts/smoke-test.sh +77 -0
- package/selfdoc.json +10 -0
- package/server/README.md +0 -0
- package/server/src/pixelweaver/__init__.py +1 -0
- package/server/src/pixelweaver/__main__.py +4 -0
- package/server/src/pixelweaver/autosave.py +114 -0
- package/server/src/pixelweaver/bridge.py +127 -0
- package/server/src/pixelweaver/cli.py +199 -0
- package/server/src/pixelweaver/config.py +24 -0
- package/server/src/pixelweaver/connections.py +54 -0
- package/server/src/pixelweaver/main.py +271 -0
- package/server/src/pixelweaver/mcp_bridge.py +189 -0
- package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
- package/server/src/pixelweaver/mcp_export_tools.py +291 -0
- package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
- package/server/src/pixelweaver/mcp_history_tools.py +106 -0
- package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
- package/server/src/pixelweaver/mcp_lock.py +37 -0
- package/server/src/pixelweaver/mcp_project_tools.py +302 -0
- package/server/src/pixelweaver/mcp_read_tools.py +163 -0
- package/server/src/pixelweaver/mcp_registry.py +247 -0
- package/server/src/pixelweaver/mcp_resources.py +312 -0
- package/server/src/pixelweaver/mcp_server.py +234 -0
- package/server/src/pixelweaver/protocol.py +219 -0
- package/server/src/pixelweaver/state.py +267 -0
- package/server/src/pixelweaver/storage.py +293 -0
- package/server/src/pixelweaver/websocket.py +282 -0
- package/server/tests/__init__.py +0 -0
- package/server/tests/conftest.py +17 -0
- package/server/tests/test_api.py +96 -0
- package/server/tests/test_bridge.py +161 -0
- package/server/tests/test_health.py +9 -0
- package/server/tests/test_integration.py +86 -0
- package/server/tests/test_mcp_bridge.py +293 -0
- package/server/tests/test_mcp_lock.py +34 -0
- package/server/tests/test_mcp_registry.py +679 -0
- package/server/tests/test_mcp_resources.py +648 -0
- package/server/tests/test_protocol.py +125 -0
- package/server/tests/test_state.py +87 -0
- package/server/tests/test_storage.py +306 -0
- package/server/tests/test_websocket.py +275 -0
- package/src/App.svelte +107 -0
- package/src/app.css +215 -0
- package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
- package/src/lib/animation/animation-commands.test.ts +228 -0
- package/src/lib/animation/animation-commands.ts +540 -0
- package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
- package/src/lib/animation/animation-preview.svelte.ts +151 -0
- package/src/lib/animation/clipboard.ts +134 -0
- package/src/lib/animation/frame-model.svelte.ts +437 -0
- package/src/lib/animation/frame-model.test.ts +314 -0
- package/src/lib/animation/frame-selection.svelte.ts +77 -0
- package/src/lib/animation/frame-tags.svelte.ts +238 -0
- package/src/lib/animation/frame-tags.test.ts +136 -0
- package/src/lib/animation/import.test.ts +141 -0
- package/src/lib/animation/import.ts +112 -0
- package/src/lib/animation/spritesheet-export.test.ts +239 -0
- package/src/lib/animation/spritesheet-export.ts +314 -0
- package/src/lib/canvas/CanvasViewport.svelte +216 -0
- package/src/lib/canvas/canvas-init-plugin.ts +178 -0
- package/src/lib/canvas/canvas-renderer.ts +408 -0
- package/src/lib/canvas/canvas-state.svelte.ts +232 -0
- package/src/lib/canvas/canvas-state.test.ts +139 -0
- package/src/lib/canvas/input-handler.ts +221 -0
- package/src/lib/canvas/input-plugin.ts +150 -0
- package/src/lib/canvas/onion-skin.ts +94 -0
- package/src/lib/canvas/pixel-buffer.test.ts +249 -0
- package/src/lib/canvas/pixel-buffer.ts +151 -0
- package/src/lib/canvas/render-state.svelte.ts +18 -0
- package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
- package/src/lib/canvas/tile-mode.test.ts +53 -0
- package/src/lib/canvas/tile-mode.ts +92 -0
- package/src/lib/canvas/viewport-utils.ts +24 -0
- package/src/lib/canvas/zoom-utils.ts +31 -0
- package/src/lib/color/.gitkeep +0 -0
- package/src/lib/color/color-commands.ts +87 -0
- package/src/lib/color/color-state.svelte.ts +98 -0
- package/src/lib/color/color-state.test.ts +91 -0
- package/src/lib/color/color-utils.test.ts +220 -0
- package/src/lib/color/color-utils.ts +243 -0
- package/src/lib/color/palette-state.svelte.ts +127 -0
- package/src/lib/color/palette-state.test.ts +154 -0
- package/src/lib/color/palette.ts +79 -0
- package/src/lib/core/bootstrap.ts +66 -0
- package/src/lib/core/command-params.generated.ts +1549 -0
- package/src/lib/core/command-params.ts +20 -0
- package/src/lib/core/command-runner.ts +79 -0
- package/src/lib/core/commands.ts +134 -0
- package/src/lib/core/dispatcher.test.ts +548 -0
- package/src/lib/core/dispatcher.ts +361 -0
- package/src/lib/core/index.test.ts +7 -0
- package/src/lib/core/notification-state.svelte.ts +119 -0
- package/src/lib/core/plugin-api.ts +210 -0
- package/src/lib/core/plugin-discovery.test.ts +53 -0
- package/src/lib/core/plugin-discovery.ts +65 -0
- package/src/lib/core/plugin-loader.test.ts +159 -0
- package/src/lib/core/plugin-loader.ts +240 -0
- package/src/lib/core/plugin-types.ts +286 -0
- package/src/lib/core/registries.svelte.ts +74 -0
- package/src/lib/core/tool-options-state.svelte.ts +61 -0
- package/src/lib/dock/DockLayout.svelte +375 -0
- package/src/lib/dock/TabAddMenu.svelte +90 -0
- package/src/lib/dock/dock-persistence.ts +46 -0
- package/src/lib/dock/dock-plugin.ts +49 -0
- package/src/lib/dock/dock-presets.ts +156 -0
- package/src/lib/dock/dock-state.svelte.ts +77 -0
- package/src/lib/dock/dock-types.ts +2 -0
- package/src/lib/dock/dockview-theme.css +226 -0
- package/src/lib/dock/dockview-theme.ts +17 -0
- package/src/lib/dock/header-action-renderer.ts +77 -0
- package/src/lib/edit/clipboard-state.ts +34 -0
- package/src/lib/export/download.ts +201 -0
- package/src/lib/export/png-metadata.ts +181 -0
- package/src/lib/history/ActionLogPanel.svelte +418 -0
- package/src/lib/history/action-log-panel-plugin.ts +24 -0
- package/src/lib/history/action-log.svelte.ts +172 -0
- package/src/lib/history/action-log.test.ts +168 -0
- package/src/lib/history/history-commands.ts +403 -0
- package/src/lib/history/macros.svelte.ts +320 -0
- package/src/lib/history/macros.test.ts +224 -0
- package/src/lib/history/replay-engine.test.ts +241 -0
- package/src/lib/history/replay-engine.ts +149 -0
- package/src/lib/history/spec-format.test.ts +250 -0
- package/src/lib/history/spec-format.ts +210 -0
- package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
- package/src/lib/iso/iso-math.test.ts +77 -0
- package/src/lib/iso/iso-math.ts +77 -0
- package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
- package/src/lib/iso/seam-checker.test.ts +126 -0
- package/src/lib/iso/seam-checker.ts +93 -0
- package/src/lib/iso/tessellation.ts +67 -0
- package/src/lib/layers/compositor.test.ts +193 -0
- package/src/lib/layers/compositor.ts +175 -0
- package/src/lib/layers/layer-commands.test.ts +263 -0
- package/src/lib/layers/layer-commands.ts +429 -0
- package/src/lib/layers/layer-tree.svelte.ts +516 -0
- package/src/lib/layers/layer-tree.test.ts +383 -0
- package/src/lib/layers/layer-types.ts +56 -0
- package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
- package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
- package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
- package/src/lib/leveleditor/depth-sort.test.ts +70 -0
- package/src/lib/leveleditor/depth-sort.ts +39 -0
- package/src/lib/leveleditor/level-editor-commands.ts +353 -0
- package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
- package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/map-state.svelte.ts +372 -0
- package/src/lib/leveleditor/map-state.test.ts +243 -0
- package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
- package/src/lib/leveleditor/tiled-export.test.ts +144 -0
- package/src/lib/leveleditor/tiled-export.ts +374 -0
- package/src/lib/pywebview.d.ts +15 -0
- package/src/lib/recovery/.gitkeep +0 -0
- package/src/lib/recovery/idb-store.ts +118 -0
- package/src/lib/recovery/recovery-manager.test.ts +321 -0
- package/src/lib/recovery/recovery-manager.ts +115 -0
- package/src/lib/recovery/recovery-plugin.ts +55 -0
- package/src/lib/recovery/recovery-state.svelte.ts +21 -0
- package/src/lib/recovery/sw-plugin.ts +18 -0
- package/src/lib/recovery/sw-register.ts +17 -0
- package/src/lib/save/directory-format.ts +42 -0
- package/src/lib/save/project-snapshot.ts +139 -0
- package/src/lib/save/recent-projects.ts +56 -0
- package/src/lib/save/save-state.svelte.ts +35 -0
- package/src/lib/save/storage.ts +167 -0
- package/src/lib/save/zip-format.ts +45 -0
- package/src/lib/shortcuts/.gitkeep +0 -0
- package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
- package/src/lib/shortcuts/default-bindings.ts +61 -0
- package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
- package/src/lib/shortcuts/shortcut-init.ts +380 -0
- package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
- package/src/lib/shortcuts/shortcut-manager.ts +281 -0
- package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
- package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
- package/src/lib/sync/patch-applicator.ts +300 -0
- package/src/lib/sync/patch-types.ts +65 -0
- package/src/lib/sync/project-manager.ts +108 -0
- package/src/lib/sync/sync-init.ts +152 -0
- package/src/lib/sync/sync-plugin.ts +19 -0
- package/src/lib/sync/sync-state.svelte.ts +56 -0
- package/src/lib/sync/ws-client.test.ts +604 -0
- package/src/lib/sync/ws-client.ts +574 -0
- package/src/lib/tools/.gitkeep +0 -0
- package/src/lib/ui/.gitkeep +0 -0
- package/src/lib/ui/AboutDialog.svelte +113 -0
- package/src/lib/ui/ColorPicker.svelte +761 -0
- package/src/lib/ui/ContextMenu.svelte +216 -0
- package/src/lib/ui/ExportDialog.svelte +747 -0
- package/src/lib/ui/FrameStrip.svelte +854 -0
- package/src/lib/ui/LayerPanel.svelte +810 -0
- package/src/lib/ui/MenuBar.svelte +590 -0
- package/src/lib/ui/NewProjectDialog.svelte +803 -0
- package/src/lib/ui/PluginManagerPanel.svelte +475 -0
- package/src/lib/ui/PromptDialog.svelte +252 -0
- package/src/lib/ui/RecoveryDialog.svelte +295 -0
- package/src/lib/ui/ResizeDialog.svelte +416 -0
- package/src/lib/ui/StatusBar.svelte +145 -0
- package/src/lib/ui/ToolbarPanel.svelte +488 -0
- package/src/lib/ui/animation-menu-commands.ts +194 -0
- package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
- package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
- package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
- package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
- package/src/lib/ui/dialog-state.svelte.ts +70 -0
- package/src/lib/ui/edit-commands.ts +271 -0
- package/src/lib/ui/file-commands.ts +275 -0
- package/src/lib/ui/file-open.ts +99 -0
- package/src/lib/ui/help-commands.ts +93 -0
- package/src/lib/ui/image-commands.ts +181 -0
- package/src/lib/ui/layer-menu-commands.ts +420 -0
- package/src/lib/ui/menu-builder.ts +224 -0
- package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
- package/src/lib/ui/notifications/notification-plugin.ts +29 -0
- package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
- package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
- package/src/lib/ui/plugin-state.svelte.ts +62 -0
- package/src/lib/ui/select-commands.ts +75 -0
- package/src/lib/ui/theme-plugin.ts +18 -0
- package/src/lib/ui/theme.svelte.ts +45 -0
- package/src/lib/ui/theme.test.ts +51 -0
- package/src/lib/ui/toolbar-config.ts +90 -0
- package/src/lib/ui/toolbar-plugin.ts +39 -0
- package/src/lib/ui/view-commands.ts +629 -0
- package/src/lib/variants/BisectionExportDialog.svelte +500 -0
- package/src/lib/variants/VariantPanel.svelte +822 -0
- package/src/lib/variants/bisection-export.test.ts +113 -0
- package/src/lib/variants/bisection-export.ts +148 -0
- package/src/lib/variants/palette-extraction.test.ts +111 -0
- package/src/lib/variants/palette-extraction.ts +84 -0
- package/src/lib/variants/palette-interpolation.test.ts +113 -0
- package/src/lib/variants/palette-interpolation.ts +87 -0
- package/src/lib/variants/palette-swap.test.ts +101 -0
- package/src/lib/variants/palette-swap.ts +114 -0
- package/src/lib/variants/variant-commands.ts +594 -0
- package/src/lib/variants/variant-panel-plugin.ts +27 -0
- package/src/lib/variants/variant-randomizer.ts +101 -0
- package/src/lib/variants/variant-state.svelte.ts +166 -0
- package/src/lib/variants/variant-state.test.ts +138 -0
- package/src/main.ts +14 -0
- package/src/vite-env.d.ts +3 -0
- package/svelte.config.js +2 -0
- package/todo/.done/audit-design-decisions.md +812 -0
- package/todo/.done/audit-implementation-plan.md +1235 -0
- package/todo/.done/happy-path-polish.md +177 -0
- package/todo/.done/pixelweaver-full-build.md +937 -0
- package/todo/.done/server-multi-frame-design.md +405 -0
- package/todo/.done/typed-dispatcher-design.md +435 -0
- package/todo/.done/unified-toolbar-and-action-system.md +323 -0
- package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
- package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
- package/todo/comprehensive-audit.md +1085 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +20 -0
- package/uv.lock +1167 -0
- package/vite.config.ts +32 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Action Log reactive state module.
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the action log's sync, filter, toggle, delete,
|
|
5
|
+
* and select operations. Since the action log syncs from the dispatcher,
|
|
6
|
+
* we set up the dispatcher with registered commands and dispatch them
|
|
7
|
+
* to populate the semantic log and undo stack.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
11
|
+
import type { Command, CommandDefinition } from '../core/commands.js';
|
|
12
|
+
import type { CommandType, ParamsOf } from '../core/command-params.js';
|
|
13
|
+
import { commandRegistry } from '../core/registries.svelte.js';
|
|
14
|
+
import { dispatch, _resetForTesting as resetDispatcher } from '../core/dispatcher.js';
|
|
15
|
+
import { actionLog } from './action-log.svelte.js';
|
|
16
|
+
|
|
17
|
+
// --- Helpers ---
|
|
18
|
+
|
|
19
|
+
function registerTestCommand(name: string, description: string): void {
|
|
20
|
+
const def: CommandDefinition = {
|
|
21
|
+
execute() { /* no-op for test */ },
|
|
22
|
+
undo() { /* no-op for test */ },
|
|
23
|
+
describe: () => description,
|
|
24
|
+
tier: 'frame',
|
|
25
|
+
};
|
|
26
|
+
commandRegistry.set(name, def);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Typed overload: validates params when type is a known command literal
|
|
30
|
+
function makeAndDispatch<T extends CommandType>(type: T, params: ParamsOf<T>): Command;
|
|
31
|
+
// String fallback: for dynamic/unknown command types in tests
|
|
32
|
+
function makeAndDispatch(type: string, params?: Record<string, unknown>): Command;
|
|
33
|
+
function makeAndDispatch(type: string, params: Record<string, unknown> = {}): Command {
|
|
34
|
+
const cmd: Command = {
|
|
35
|
+
type,
|
|
36
|
+
plugin: 'test/plugin',
|
|
37
|
+
version: '1.0.0',
|
|
38
|
+
params,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
id: crypto.randomUUID(),
|
|
41
|
+
};
|
|
42
|
+
dispatch(cmd);
|
|
43
|
+
return cmd;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Tests ---
|
|
47
|
+
|
|
48
|
+
describe('Action Log State', () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
resetDispatcher();
|
|
51
|
+
// Re-register test commands after dispatcher reset clears callbacks
|
|
52
|
+
registerTestCommand('draw_pixel', 'Drew pixel');
|
|
53
|
+
registerTestCommand('fill_rect', 'Filled rectangle');
|
|
54
|
+
registerTestCommand('set_color', 'Set color');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should sync entries from the dispatcher semantic log', () => {
|
|
58
|
+
const cmd1 = makeAndDispatch('draw_pixel', { x: 0, y: 0 });
|
|
59
|
+
const cmd2 = makeAndDispatch('fill_rect', { x: 1, y: 1, w: 5, h: 5 });
|
|
60
|
+
|
|
61
|
+
actionLog.syncFromDispatcher();
|
|
62
|
+
|
|
63
|
+
const entries = actionLog.getEntries();
|
|
64
|
+
expect(entries).toHaveLength(2);
|
|
65
|
+
expect(entries[0]?.commandId).toBe(cmd1.id);
|
|
66
|
+
expect(entries[0]?.type).toBe('draw_pixel');
|
|
67
|
+
expect(entries[0]?.description).toBe('Drew pixel');
|
|
68
|
+
expect(entries[1]?.commandId).toBe(cmd2.id);
|
|
69
|
+
expect(entries[1]?.type).toBe('fill_rect');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should filter entries by query string matching description', () => {
|
|
73
|
+
makeAndDispatch('draw_pixel', { x: 0, y: 0 });
|
|
74
|
+
makeAndDispatch('fill_rect', { x: 1, y: 1, w: 5, h: 5 });
|
|
75
|
+
makeAndDispatch('set_color', { color: '#ff0000' });
|
|
76
|
+
|
|
77
|
+
actionLog.syncFromDispatcher();
|
|
78
|
+
actionLog.setFilterQuery('pixel');
|
|
79
|
+
|
|
80
|
+
const filtered = actionLog.getFilteredEntries();
|
|
81
|
+
expect(filtered).toHaveLength(1);
|
|
82
|
+
expect(filtered[0]?.type).toBe('draw_pixel');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should filter entries by query string matching command type', () => {
|
|
86
|
+
makeAndDispatch('draw_pixel', { x: 0, y: 0 });
|
|
87
|
+
makeAndDispatch('fill_rect', { x: 1, y: 1, w: 5, h: 5 });
|
|
88
|
+
|
|
89
|
+
actionLog.syncFromDispatcher();
|
|
90
|
+
actionLog.setFilterQuery('fill_rect');
|
|
91
|
+
|
|
92
|
+
const filtered = actionLog.getFilteredEntries();
|
|
93
|
+
expect(filtered).toHaveLength(1);
|
|
94
|
+
expect(filtered[0]?.type).toBe('fill_rect');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return all entries when filter query is empty', () => {
|
|
98
|
+
makeAndDispatch('draw_pixel');
|
|
99
|
+
makeAndDispatch('fill_rect');
|
|
100
|
+
makeAndDispatch('set_color');
|
|
101
|
+
|
|
102
|
+
actionLog.syncFromDispatcher();
|
|
103
|
+
actionLog.setFilterQuery('');
|
|
104
|
+
|
|
105
|
+
expect(actionLog.getFilteredEntries()).toHaveLength(3);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should toggle an entry between enabled and disabled', () => {
|
|
109
|
+
const cmd = makeAndDispatch('draw_pixel', { x: 5, y: 5 });
|
|
110
|
+
actionLog.syncFromDispatcher();
|
|
111
|
+
|
|
112
|
+
const entry = actionLog.getEntry(cmd.id);
|
|
113
|
+
expect(entry?.enabled).toBe(true);
|
|
114
|
+
|
|
115
|
+
actionLog.toggleEntry(cmd.id);
|
|
116
|
+
expect(actionLog.getEntry(cmd.id)?.enabled).toBe(false);
|
|
117
|
+
|
|
118
|
+
actionLog.toggleEntry(cmd.id);
|
|
119
|
+
expect(actionLog.getEntry(cmd.id)?.enabled).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should delete an entry from the log', () => {
|
|
123
|
+
const cmd1 = makeAndDispatch('draw_pixel');
|
|
124
|
+
const cmd2 = makeAndDispatch('fill_rect');
|
|
125
|
+
actionLog.syncFromDispatcher();
|
|
126
|
+
expect(actionLog.getEntries()).toHaveLength(2);
|
|
127
|
+
|
|
128
|
+
actionLog.deleteEntry(cmd1.id);
|
|
129
|
+
expect(actionLog.getEntries()).toHaveLength(1);
|
|
130
|
+
expect(actionLog.getEntry(cmd1.id)).toBeUndefined();
|
|
131
|
+
expect(actionLog.getEntry(cmd2.id)).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should clear selection when deleting the selected entry', () => {
|
|
135
|
+
const cmd = makeAndDispatch('draw_pixel');
|
|
136
|
+
actionLog.syncFromDispatcher();
|
|
137
|
+
|
|
138
|
+
actionLog.selectEntry(cmd.id);
|
|
139
|
+
expect(actionLog.getSelectedEntryId()).toBe(cmd.id);
|
|
140
|
+
|
|
141
|
+
actionLog.deleteEntry(cmd.id);
|
|
142
|
+
expect(actionLog.getSelectedEntryId()).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should select an entry by ID', () => {
|
|
146
|
+
const cmd1 = makeAndDispatch('draw_pixel');
|
|
147
|
+
const cmd2 = makeAndDispatch('fill_rect');
|
|
148
|
+
actionLog.syncFromDispatcher();
|
|
149
|
+
|
|
150
|
+
actionLog.selectEntry(cmd1.id);
|
|
151
|
+
expect(actionLog.getSelectedEntryId()).toBe(cmd1.id);
|
|
152
|
+
|
|
153
|
+
actionLog.selectEntry(cmd2.id);
|
|
154
|
+
expect(actionLog.getSelectedEntryId()).toBe(cmd2.id);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should preserve enabled states across sync', () => {
|
|
158
|
+
const cmd = makeAndDispatch('draw_pixel');
|
|
159
|
+
actionLog.syncFromDispatcher();
|
|
160
|
+
|
|
161
|
+
actionLog.toggleEntry(cmd.id);
|
|
162
|
+
expect(actionLog.getEntry(cmd.id)?.enabled).toBe(false);
|
|
163
|
+
|
|
164
|
+
// Sync again -- should preserve the disabled state
|
|
165
|
+
actionLog.syncFromDispatcher();
|
|
166
|
+
expect(actionLog.getEntry(cmd.id)?.enabled).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Commands Plugin -- registers meta-commands that manipulate the
|
|
3
|
+
* action log itself.
|
|
4
|
+
*
|
|
5
|
+
* These are 'project' tier commands that allow the user to reorder, edit,
|
|
6
|
+
* toggle, and delete entries in the semantic action log. Unlike regular
|
|
7
|
+
* commands that modify pixel data, these commands modify the history.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { PluginModule } from '../core/plugin-loader.js';
|
|
11
|
+
import type { Command } from '../core/commands.js';
|
|
12
|
+
import { actionLog } from './action-log.svelte.js';
|
|
13
|
+
import { applyReorder } from './replay-engine.js';
|
|
14
|
+
import { exportSpec, importSpec, type SpecDocument } from './spec-format.js';
|
|
15
|
+
import { macroSystem } from './macros.svelte.js';
|
|
16
|
+
import { dispatch } from '../core/dispatcher.js';
|
|
17
|
+
import { canvasState } from '../canvas/canvas-state.svelte.js';
|
|
18
|
+
import { notificationState } from '../ui/notifications/notification-state.svelte.js';
|
|
19
|
+
import { downloadFile } from '../export/download.js';
|
|
20
|
+
import DownloadIcon from '~icons/lucide/download';
|
|
21
|
+
import UploadIcon from '~icons/lucide/upload';
|
|
22
|
+
import TrashIcon from '~icons/lucide/trash-2';
|
|
23
|
+
import CircleDotIcon from '~icons/lucide/circle-dot';
|
|
24
|
+
import SquareIcon from '~icons/lucide/square';
|
|
25
|
+
|
|
26
|
+
type ReorderSnapshot = { previousOrder: string[] };
|
|
27
|
+
type EditParamsSnapshot = { oldParams: Record<string, unknown> };
|
|
28
|
+
type ToggleSnapshot = { wasEnabled: boolean };
|
|
29
|
+
type DeleteActionSnapshot = { entry: Record<string, unknown>; index: number };
|
|
30
|
+
|
|
31
|
+
export const historyCommandsPlugin: PluginModule = {
|
|
32
|
+
name: 'builtin/history',
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
description: 'Meta-commands for manipulating the action log',
|
|
35
|
+
dependencies: [],
|
|
36
|
+
|
|
37
|
+
register(api) {
|
|
38
|
+
// --- reorder_actions ---
|
|
39
|
+
// Reorder the action log entries and replay to produce a new buffer.
|
|
40
|
+
api.addCommand('reorder_actions', {
|
|
41
|
+
tier: 'project',
|
|
42
|
+
|
|
43
|
+
execute(params) {
|
|
44
|
+
const newOrder = params["newOrder"];
|
|
45
|
+
const width = params["width"];
|
|
46
|
+
const height = params["height"];
|
|
47
|
+
const layerId = params["layerId"];
|
|
48
|
+
|
|
49
|
+
// Capture previous order as snapshot for undo
|
|
50
|
+
const snapshot: ReorderSnapshot = {
|
|
51
|
+
previousOrder: actionLog.getEntries().map((e) => e.commandId),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
applyReorder(newOrder, width, height, layerId);
|
|
55
|
+
return snapshot;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
undo(params, _ctx, snapshot) {
|
|
59
|
+
const typed = snapshot as ReorderSnapshot | undefined;
|
|
60
|
+
const width = params["width"];
|
|
61
|
+
const height = params["height"];
|
|
62
|
+
const layerId = params["layerId"];
|
|
63
|
+
|
|
64
|
+
if (typed?.previousOrder) {
|
|
65
|
+
applyReorder(typed.previousOrder, width, height, layerId);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
describe() {
|
|
70
|
+
return 'Reordered action history';
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// --- edit_action_params ---
|
|
75
|
+
// Edit the params of an existing action log entry.
|
|
76
|
+
api.addCommand('edit_action_params', {
|
|
77
|
+
tier: 'project',
|
|
78
|
+
|
|
79
|
+
execute(params) {
|
|
80
|
+
const entryId = params["entryId"];
|
|
81
|
+
const newParams = params["newParams"];
|
|
82
|
+
|
|
83
|
+
const entry = actionLog.getEntry(entryId);
|
|
84
|
+
if (!entry) return;
|
|
85
|
+
|
|
86
|
+
// Capture old params as snapshot for undo
|
|
87
|
+
const snapshot: EditParamsSnapshot = { oldParams: { ...entry.params } };
|
|
88
|
+
|
|
89
|
+
// Update the entry's params
|
|
90
|
+
Object.assign(entry.params, newParams);
|
|
91
|
+
return snapshot;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
undo(params, _ctx, snapshot) {
|
|
95
|
+
const entryId = params["entryId"];
|
|
96
|
+
const typed = snapshot as EditParamsSnapshot | undefined;
|
|
97
|
+
|
|
98
|
+
const entry = actionLog.getEntry(entryId);
|
|
99
|
+
if (!entry || !typed) return;
|
|
100
|
+
|
|
101
|
+
// Restore old params (replace entirely to avoid leftover keys).
|
|
102
|
+
// Reflect.deleteProperty avoids the no-dynamic-delete rule which
|
|
103
|
+
// targets the literal `delete expr[key]` syntax only.
|
|
104
|
+
for (const key of Object.keys(entry.params)) {
|
|
105
|
+
Reflect.deleteProperty(entry.params, key);
|
|
106
|
+
}
|
|
107
|
+
Object.assign(entry.params, typed.oldParams);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
describe(params) {
|
|
111
|
+
return `Edited params of action "${String(params["entryId"])}"`;
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// --- toggle_action ---
|
|
116
|
+
// Toggle an action log entry's enabled state.
|
|
117
|
+
api.addCommand('toggle_action', {
|
|
118
|
+
tier: 'project',
|
|
119
|
+
|
|
120
|
+
execute(params) {
|
|
121
|
+
const entryId = params["entryId"];
|
|
122
|
+
const entry = actionLog.getEntry(entryId);
|
|
123
|
+
let snapshot: ToggleSnapshot | undefined;
|
|
124
|
+
if (entry) {
|
|
125
|
+
snapshot = { wasEnabled: entry.enabled };
|
|
126
|
+
}
|
|
127
|
+
actionLog.toggleEntry(entryId);
|
|
128
|
+
return snapshot;
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
undo(params, _ctx, snapshot) {
|
|
132
|
+
const entryId = params["entryId"];
|
|
133
|
+
const entry = actionLog.getEntry(entryId);
|
|
134
|
+
if (!entry) return;
|
|
135
|
+
|
|
136
|
+
const typed = snapshot as ToggleSnapshot | undefined;
|
|
137
|
+
if (!typed) return;
|
|
138
|
+
|
|
139
|
+
// Restore previous enabled state (toggle only if current differs from snapshot)
|
|
140
|
+
if (entry.enabled !== typed.wasEnabled) {
|
|
141
|
+
actionLog.toggleEntry(entryId);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
describe(params) {
|
|
146
|
+
const entry = actionLog.getEntry(params["entryId"]);
|
|
147
|
+
return entry?.enabled
|
|
148
|
+
? `Enabled action "${String(params["entryId"])}"`
|
|
149
|
+
: `Disabled action "${String(params["entryId"])}"`;
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// --- delete_action ---
|
|
154
|
+
// Remove an action log entry.
|
|
155
|
+
api.addCommand('delete_action', {
|
|
156
|
+
tier: 'project',
|
|
157
|
+
|
|
158
|
+
execute(params) {
|
|
159
|
+
const entryId = params["entryId"];
|
|
160
|
+
const entry = actionLog.getEntry(entryId);
|
|
161
|
+
let snapshot: DeleteActionSnapshot | undefined;
|
|
162
|
+
if (entry) {
|
|
163
|
+
// Capture snapshot and position for undo
|
|
164
|
+
const entries = actionLog.getEntries();
|
|
165
|
+
snapshot = {
|
|
166
|
+
entry: { ...entry },
|
|
167
|
+
index: entries.findIndex((e) => e.commandId === entryId),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
actionLog.deleteEntry(entryId);
|
|
171
|
+
return snapshot;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
undo(_params, _ctx, snapshot) {
|
|
175
|
+
const typed = snapshot as DeleteActionSnapshot | undefined;
|
|
176
|
+
if (!typed) return;
|
|
177
|
+
|
|
178
|
+
// Re-insert the entry at its original position
|
|
179
|
+
const entries = actionLog.getEntries();
|
|
180
|
+
entries.splice(typed.index, 0, typed.entry as never);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
describe(params) {
|
|
184
|
+
return `Deleted action "${String(params["entryId"])}"`;
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// --- export_spec ---
|
|
189
|
+
// Serialize the current action log to a JSON spec document and trigger
|
|
190
|
+
// a browser download. Non-undoable: this is a read-only operation.
|
|
191
|
+
api.addCommand('export_spec', {
|
|
192
|
+
tier: 'project',
|
|
193
|
+
undoable: false,
|
|
194
|
+
label: 'Export Spec',
|
|
195
|
+
category: 'History',
|
|
196
|
+
icon: DownloadIcon,
|
|
197
|
+
|
|
198
|
+
async execute() {
|
|
199
|
+
const spec = exportSpec(
|
|
200
|
+
actionLog.getEntries(),
|
|
201
|
+
canvasState.canvasWidth,
|
|
202
|
+
canvasState.canvasHeight,
|
|
203
|
+
);
|
|
204
|
+
const json = JSON.stringify(spec, null, 2);
|
|
205
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
206
|
+
const filename = `pixelweaver-spec-${String(Date.now())}.json`;
|
|
207
|
+
await downloadFile({
|
|
208
|
+
blob,
|
|
209
|
+
filename,
|
|
210
|
+
filterName: 'JSON Spec',
|
|
211
|
+
filterExtensions: ['json'],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
notificationState.push({
|
|
215
|
+
id: 'export-spec-success',
|
|
216
|
+
message: `Exported spec with ${String(spec.commands.length)} command(s)`,
|
|
217
|
+
type: 'info',
|
|
218
|
+
autoDismissMs: 3000,
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
undo() { /* non-undoable */ },
|
|
223
|
+
|
|
224
|
+
describe() {
|
|
225
|
+
return 'Exported spec';
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// --- import_spec ---
|
|
230
|
+
// Import a spec document (passed as a 'spec' param) and dispatch its
|
|
231
|
+
// commands. The file picker UI lives in ActionLogPanel; this command
|
|
232
|
+
// only accepts an already-parsed SpecDocument. Non-undoable: the
|
|
233
|
+
// dispatched child commands are individually undoable.
|
|
234
|
+
api.addCommand('import_spec', {
|
|
235
|
+
tier: 'project',
|
|
236
|
+
undoable: false,
|
|
237
|
+
label: 'Import Spec',
|
|
238
|
+
category: 'History',
|
|
239
|
+
icon: UploadIcon,
|
|
240
|
+
|
|
241
|
+
execute(params) {
|
|
242
|
+
const spec = params["spec"] as SpecDocument | undefined;
|
|
243
|
+
if (!spec) {
|
|
244
|
+
notificationState.push({
|
|
245
|
+
id: 'import-spec-missing',
|
|
246
|
+
message: 'Import spec: no spec provided',
|
|
247
|
+
type: 'warning',
|
|
248
|
+
autoDismissMs: 4000,
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let commands: { type: string; params: Record<string, unknown> }[];
|
|
254
|
+
try {
|
|
255
|
+
commands = importSpec(spec);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
notificationState.push({
|
|
258
|
+
id: 'import-spec-invalid',
|
|
259
|
+
message: `Import spec failed: ${(err as Error).message}`,
|
|
260
|
+
type: 'warning',
|
|
261
|
+
autoDismissMs: 5000,
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Dispatch each imported command through the normal dispatcher
|
|
267
|
+
// pipeline so they are individually recorded in the undo stack.
|
|
268
|
+
let dispatched = 0;
|
|
269
|
+
for (const cmd of commands) {
|
|
270
|
+
const command: Command = {
|
|
271
|
+
type: cmd.type,
|
|
272
|
+
plugin: 'builtin/history',
|
|
273
|
+
version: '1.0.0',
|
|
274
|
+
params: cmd.params,
|
|
275
|
+
timestamp: Date.now(),
|
|
276
|
+
id: crypto.randomUUID(),
|
|
277
|
+
};
|
|
278
|
+
try {
|
|
279
|
+
dispatch(command);
|
|
280
|
+
dispatched++;
|
|
281
|
+
} catch (err) {
|
|
282
|
+
// Skip commands whose types are not registered; report in notification.
|
|
283
|
+
console.warn(`import_spec: skipping command "${cmd.type}"`, err);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
notificationState.push({
|
|
288
|
+
id: 'import-spec-success',
|
|
289
|
+
message: `Imported ${String(dispatched)}/${String(commands.length)} command(s) from spec`,
|
|
290
|
+
type: 'info',
|
|
291
|
+
autoDismissMs: 3000,
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
undo() { /* non-undoable -- child commands handle their own undo */ },
|
|
296
|
+
|
|
297
|
+
describe() {
|
|
298
|
+
return 'Imported spec';
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// --- clear_action_log ---
|
|
303
|
+
// Clear the local action log view. Non-undoable: clearing history
|
|
304
|
+
// should not itself be entered into history.
|
|
305
|
+
api.addCommand('clear_action_log', {
|
|
306
|
+
tier: 'project',
|
|
307
|
+
undoable: false,
|
|
308
|
+
label: 'Clear Action Log',
|
|
309
|
+
category: 'History',
|
|
310
|
+
icon: TrashIcon,
|
|
311
|
+
|
|
312
|
+
execute() {
|
|
313
|
+
const count = actionLog.getEntries().length;
|
|
314
|
+
actionLog.clearAll();
|
|
315
|
+
notificationState.push({
|
|
316
|
+
id: 'clear-action-log',
|
|
317
|
+
message: `Cleared ${String(count)} action log entr${count === 1 ? 'y' : 'ies'}`,
|
|
318
|
+
type: 'info',
|
|
319
|
+
autoDismissMs: 3000,
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
undo() { /* non-undoable */ },
|
|
324
|
+
|
|
325
|
+
describe() {
|
|
326
|
+
return 'Cleared action log';
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// --- record_macro_start ---
|
|
331
|
+
// Begin recording dispatched commands into a new macro buffer.
|
|
332
|
+
api.addCommand('record_macro_start', {
|
|
333
|
+
tier: 'project',
|
|
334
|
+
undoable: false,
|
|
335
|
+
label: 'Start Recording Macro',
|
|
336
|
+
category: 'History',
|
|
337
|
+
icon: CircleDotIcon,
|
|
338
|
+
|
|
339
|
+
execute() {
|
|
340
|
+
if (macroSystem.recording) {
|
|
341
|
+
notificationState.push({
|
|
342
|
+
id: 'record-macro-already',
|
|
343
|
+
message: 'Macro recording already in progress',
|
|
344
|
+
type: 'info',
|
|
345
|
+
autoDismissMs: 3000,
|
|
346
|
+
});
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
macroSystem.startRecording();
|
|
350
|
+
notificationState.push({
|
|
351
|
+
id: 'record-macro-start',
|
|
352
|
+
message: 'Macro recording started',
|
|
353
|
+
type: 'info',
|
|
354
|
+
autoDismissMs: 3000,
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
undo() { /* non-undoable */ },
|
|
359
|
+
|
|
360
|
+
describe() {
|
|
361
|
+
return 'Started macro recording';
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// --- record_macro_stop ---
|
|
366
|
+
// Stop recording and finalize the macro. Accepts optional 'name' and
|
|
367
|
+
// 'coordMode' params; defaults to a timestamped name and absolute mode.
|
|
368
|
+
api.addCommand('record_macro_stop', {
|
|
369
|
+
tier: 'project',
|
|
370
|
+
undoable: false,
|
|
371
|
+
label: 'Stop Recording Macro',
|
|
372
|
+
category: 'History',
|
|
373
|
+
icon: SquareIcon,
|
|
374
|
+
|
|
375
|
+
execute(params) {
|
|
376
|
+
if (!macroSystem.recording) {
|
|
377
|
+
notificationState.push({
|
|
378
|
+
id: 'record-macro-not-recording',
|
|
379
|
+
message: 'No macro recording in progress',
|
|
380
|
+
type: 'info',
|
|
381
|
+
autoDismissMs: 3000,
|
|
382
|
+
});
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const name = params["name"] ?? `Macro ${new Date().toLocaleTimeString()}`;
|
|
386
|
+
const coordMode = params["coordMode"] ?? 'absolute';
|
|
387
|
+
const macro = macroSystem.stopRecording(name, coordMode);
|
|
388
|
+
notificationState.push({
|
|
389
|
+
id: 'record-macro-stop',
|
|
390
|
+
message: `Recorded macro "${macro.name}" with ${String(macro.commands.length)} command(s)`,
|
|
391
|
+
type: 'info',
|
|
392
|
+
autoDismissMs: 3000,
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
undo() { /* non-undoable */ },
|
|
397
|
+
|
|
398
|
+
describe() {
|
|
399
|
+
return 'Stopped macro recording';
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
},
|
|
403
|
+
};
|