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