@dfosco/storyboard 0.5.0-alpha.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/commandpalette.config.json +152 -0
- package/dist/storyboard-ui.css +1 -0
- package/dist/storyboard-ui.js +21328 -0
- package/dist/storyboard-ui.js.map +1 -0
- package/dist/tailwind.css +2 -0
- package/dist/tiny-canvas.css +1 -0
- package/dist/tiny-canvas.js +389 -0
- package/package.json +121 -0
- package/paste.config.json +67 -0
- package/scaffold/AGENTS.md +432 -0
- package/scaffold/agents/prompt-agent.agent.md +181 -0
- package/scaffold/agents/terminal-agent.agent.md +351 -0
- package/scaffold/codex/config.toml +246 -0
- package/scaffold/deploy.yml +103 -0
- package/scaffold/githooks/pre-push +114 -0
- package/scaffold/gitignore +64 -0
- package/scaffold/manifest.json +56 -0
- package/scaffold/preview.yml +181 -0
- package/scaffold/scripts/link.sh +26 -0
- package/scaffold/scripts/unlink.sh +10 -0
- package/scaffold/skills/agent-browser/SKILL.md +260 -0
- package/scaffold/skills/canvas/SKILL.md +364 -0
- package/scaffold/skills/create/SKILL.md +501 -0
- package/scaffold/skills/ship/SKILL.md +237 -0
- package/scaffold/skills/storyboard/SKILL.md +360 -0
- package/scaffold/skills/update-storyboard/SKILL.md +16 -0
- package/scaffold/skills/update-storyboard/update-storyboard-packages.sh +26 -0
- package/scaffold/skills/vitest/GENERATION.md +5 -0
- package/scaffold/skills/vitest/SKILL.md +52 -0
- package/scaffold/skills/vitest/references/advanced-environments.md +264 -0
- package/scaffold/skills/vitest/references/advanced-projects.md +300 -0
- package/scaffold/skills/vitest/references/advanced-type-testing.md +237 -0
- package/scaffold/skills/vitest/references/advanced-vi.md +249 -0
- package/scaffold/skills/vitest/references/core-cli.md +166 -0
- package/scaffold/skills/vitest/references/core-config.md +174 -0
- package/scaffold/skills/vitest/references/core-describe.md +193 -0
- package/scaffold/skills/vitest/references/core-expect.md +219 -0
- package/scaffold/skills/vitest/references/core-hooks.md +244 -0
- package/scaffold/skills/vitest/references/core-test-api.md +233 -0
- package/scaffold/skills/vitest/references/features-concurrency.md +250 -0
- package/scaffold/skills/vitest/references/features-context.md +238 -0
- package/scaffold/skills/vitest/references/features-coverage.md +207 -0
- package/scaffold/skills/vitest/references/features-filtering.md +211 -0
- package/scaffold/skills/vitest/references/features-mocking.md +265 -0
- package/scaffold/skills/vitest/references/features-snapshots.md +207 -0
- package/scaffold/skills/worktree/SKILL.md +93 -0
- package/scaffold/storyboard.config.json +44 -0
- package/src/canvas/Canvas.jsx +78 -0
- package/src/canvas/Draggable.jsx +235 -0
- package/src/canvas/index.d.ts +41 -0
- package/src/canvas/index.js +6 -0
- package/src/canvas/style.css +118 -0
- package/src/canvas/useResetCanvas.js +17 -0
- package/src/canvas/utils.js +136 -0
- package/src/core/assets/fonts/IoskeleyMono-Bold.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-Italic.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-Medium.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-Regular.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-SemiBold.woff2 +0 -0
- package/src/core/autosync/server.js +714 -0
- package/src/core/autosync/server.test.js +158 -0
- package/src/core/canvas/__tests__/agent-integration.test.js +596 -0
- package/src/core/canvas/__tests__/helpers/browser.js +95 -0
- package/src/core/canvas/__tests__/helpers/canvas-api.js +129 -0
- package/src/core/canvas/__tests__/helpers/perf.js +118 -0
- package/src/core/canvas/__tests__/helpers/setup.js +176 -0
- package/src/core/canvas/__tests__/helpers/tmux.js +130 -0
- package/src/core/canvas/__tests__/helpers/transcript.js +132 -0
- package/src/core/canvas/__tests__/terminal-integration.test.js +177 -0
- package/src/core/canvas/collision.js +292 -0
- package/src/core/canvas/collision.test.js +371 -0
- package/src/core/canvas/compact.js +83 -0
- package/src/core/canvas/deriveCanvasId.test.js +40 -0
- package/src/core/canvas/githubEmbeds.js +527 -0
- package/src/core/canvas/githubEmbeds.test.js +302 -0
- package/src/core/canvas/hot-pool.js +766 -0
- package/src/core/canvas/identity.js +107 -0
- package/src/core/canvas/identity.test.js +100 -0
- package/src/core/canvas/materializer.js +259 -0
- package/src/core/canvas/materializer.test.js +356 -0
- package/src/core/canvas/selectedWidgets.js +270 -0
- package/src/core/canvas/selectedWidgets.test.js +321 -0
- package/src/core/canvas/server.js +3134 -0
- package/src/core/canvas/server.test.js +379 -0
- package/src/core/canvas/terminal-config.js +330 -0
- package/src/core/canvas/terminal-registry.js +465 -0
- package/src/core/canvas/terminal-server.js +1436 -0
- package/src/core/canvas/writeGuard.js +53 -0
- package/src/core/cli/agent.js +85 -0
- package/src/core/cli/branch.js +386 -0
- package/src/core/cli/canvasAdd.js +241 -0
- package/src/core/cli/canvasBatch.js +98 -0
- package/src/core/cli/canvasBounds.js +160 -0
- package/src/core/cli/canvasRead.js +236 -0
- package/src/core/cli/canvasUpdate.js +179 -0
- package/src/core/cli/code.js +67 -0
- package/src/core/cli/compact.js +62 -0
- package/src/core/cli/create.js +674 -0
- package/src/core/cli/dev-helpers.js +53 -0
- package/src/core/cli/dev-helpers.test.js +53 -0
- package/src/core/cli/dev.js +430 -0
- package/src/core/cli/exit.js +38 -0
- package/src/core/cli/flags.js +174 -0
- package/src/core/cli/flags.test.js +155 -0
- package/src/core/cli/index.js +233 -0
- package/src/core/cli/intro.js +37 -0
- package/src/core/cli/proxy.js +319 -0
- package/src/core/cli/proxy.test.js +63 -0
- package/src/core/cli/schemas.js +223 -0
- package/src/core/cli/server.js +192 -0
- package/src/core/cli/serverUrl.js +61 -0
- package/src/core/cli/sessions.js +459 -0
- package/src/core/cli/setup.js +404 -0
- package/src/core/cli/terminal-commands.js +287 -0
- package/src/core/cli/terminal-messaging.js +231 -0
- package/src/core/cli/terminal-welcome.js +515 -0
- package/src/core/cli/updateVersion.js +124 -0
- package/src/core/comments/api.js +284 -0
- package/src/core/comments/api.test.js +282 -0
- package/src/core/comments/auth.js +151 -0
- package/src/core/comments/auth.test.js +167 -0
- package/src/core/comments/commentCache.js +109 -0
- package/src/core/comments/commentCache.test.js +48 -0
- package/src/core/comments/commentDrafts.js +68 -0
- package/src/core/comments/commentMode.js +63 -0
- package/src/core/comments/commentMode.test.js +90 -0
- package/src/core/comments/config.js +47 -0
- package/src/core/comments/config.test.js +77 -0
- package/src/core/comments/graphql.js +65 -0
- package/src/core/comments/graphql.test.js +95 -0
- package/src/core/comments/index.js +42 -0
- package/src/core/comments/metadata.js +52 -0
- package/src/core/comments/metadata.test.js +110 -0
- package/src/core/comments/queries.js +245 -0
- package/src/core/comments/ui/AuthModal.jsx +114 -0
- package/src/core/comments/ui/CommentOverlay.js +52 -0
- package/src/core/comments/ui/CommentWindow.jsx +329 -0
- package/src/core/comments/ui/CommentsDrawer.jsx +102 -0
- package/src/core/comments/ui/Composer.jsx +64 -0
- package/src/core/comments/ui/authModal.js +66 -0
- package/src/core/comments/ui/authModal.test.js +76 -0
- package/src/core/comments/ui/comment-cursor-dark.svg +1 -0
- package/src/core/comments/ui/comment-cursor.svg +1 -0
- package/src/core/comments/ui/comment-layout.css +142 -0
- package/src/core/comments/ui/commentWindow.js +121 -0
- package/src/core/comments/ui/comments.css +242 -0
- package/src/core/comments/ui/commentsDrawer.js +84 -0
- package/src/core/comments/ui/composer.js +136 -0
- package/src/core/comments/ui/index.js +14 -0
- package/src/core/comments/ui/mount.js +687 -0
- package/src/core/comments/ui/mount.test.js +336 -0
- package/src/core/data/dotPath.js +53 -0
- package/src/core/data/dotPath.test.js +114 -0
- package/src/core/data/loader.js +409 -0
- package/src/core/data/loader.test.js +599 -0
- package/src/core/data/viewfinder.js +363 -0
- package/src/core/data/viewfinder.test.js +456 -0
- package/src/core/devtools/devtools-consumer.js +28 -0
- package/src/core/devtools/devtools.js +144 -0
- package/src/core/devtools/devtools.test.js +75 -0
- package/src/core/devtools/sceneDebug.js +112 -0
- package/src/core/devtools/sceneDebug.test.js +141 -0
- package/src/core/index.js +124 -0
- package/src/core/inspector/fiberWalker.js +239 -0
- package/src/core/inspector/highlighter.js +275 -0
- package/src/core/inspector/mouseMode.js +259 -0
- package/src/core/lib/components/ui/alert/alert-action.jsx +11 -0
- package/src/core/lib/components/ui/alert/alert-description.jsx +11 -0
- package/src/core/lib/components/ui/alert/alert-title.jsx +11 -0
- package/src/core/lib/components/ui/alert/alert.jsx +25 -0
- package/src/core/lib/components/ui/alert/index.js +17 -0
- package/src/core/lib/components/ui/avatar/avatar-badge.jsx +22 -0
- package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
- package/src/core/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
- package/src/core/lib/components/ui/avatar/avatar-group.jsx +19 -0
- package/src/core/lib/components/ui/avatar/avatar-image.jsx +15 -0
- package/src/core/lib/components/ui/avatar/avatar.jsx +19 -0
- package/src/core/lib/components/ui/avatar/index.js +22 -0
- package/src/core/lib/components/ui/badge/badge.jsx +31 -0
- package/src/core/lib/components/ui/badge/index.js +2 -0
- package/src/core/lib/components/ui/button/button.jsx +100 -0
- package/src/core/lib/components/ui/button/index.js +12 -0
- package/src/core/lib/components/ui/card/card-action.jsx +11 -0
- package/src/core/lib/components/ui/card/card-content.jsx +11 -0
- package/src/core/lib/components/ui/card/card-description.jsx +11 -0
- package/src/core/lib/components/ui/card/card-footer.jsx +11 -0
- package/src/core/lib/components/ui/card/card-header.jsx +19 -0
- package/src/core/lib/components/ui/card/card-title.jsx +11 -0
- package/src/core/lib/components/ui/card/card.jsx +17 -0
- package/src/core/lib/components/ui/card/index.js +25 -0
- package/src/core/lib/components/ui/checkbox/checkbox.jsx +29 -0
- package/src/core/lib/components/ui/checkbox/index.js +6 -0
- package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
- package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
- package/src/core/lib/components/ui/collapsible/collapsible.jsx +7 -0
- package/src/core/lib/components/ui/collapsible/index.js +13 -0
- package/src/core/lib/components/ui/dialog/dialog-close.jsx +7 -0
- package/src/core/lib/components/ui/dialog/dialog-content.jsx +34 -0
- package/src/core/lib/components/ui/dialog/dialog-description.jsx +15 -0
- package/src/core/lib/components/ui/dialog/dialog-footer.jsx +23 -0
- package/src/core/lib/components/ui/dialog/dialog-header.jsx +11 -0
- package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
- package/src/core/lib/components/ui/dialog/dialog-portal.jsx +4 -0
- package/src/core/lib/components/ui/dialog/dialog-title.jsx +15 -0
- package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
- package/src/core/lib/components/ui/dialog/dialog.jsx +4 -0
- package/src/core/lib/components/ui/dialog/index.js +34 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
- package/src/core/lib/components/ui/dropdown-menu/index.js +54 -0
- package/src/core/lib/components/ui/input/index.js +7 -0
- package/src/core/lib/components/ui/input/input.jsx +19 -0
- package/src/core/lib/components/ui/label/index.js +7 -0
- package/src/core/lib/components/ui/label/label.jsx +19 -0
- package/src/core/lib/components/ui/panel/index.js +24 -0
- package/src/core/lib/components/ui/panel/panel-body.jsx +11 -0
- package/src/core/lib/components/ui/panel/panel-close.jsx +16 -0
- package/src/core/lib/components/ui/panel/panel-content.jsx +29 -0
- package/src/core/lib/components/ui/panel/panel-footer.jsx +11 -0
- package/src/core/lib/components/ui/panel/panel-header.jsx +11 -0
- package/src/core/lib/components/ui/panel/panel-title.jsx +12 -0
- package/src/core/lib/components/ui/panel/panel.jsx +4 -0
- package/src/core/lib/components/ui/popover/index.js +28 -0
- package/src/core/lib/components/ui/popover/popover-close.jsx +7 -0
- package/src/core/lib/components/ui/popover/popover-content.jsx +22 -0
- package/src/core/lib/components/ui/popover/popover-description.jsx +11 -0
- package/src/core/lib/components/ui/popover/popover-header.jsx +11 -0
- package/src/core/lib/components/ui/popover/popover-portal.jsx +4 -0
- package/src/core/lib/components/ui/popover/popover-title.jsx +11 -0
- package/src/core/lib/components/ui/popover/popover-trigger.jsx +8 -0
- package/src/core/lib/components/ui/popover/popover.jsx +4 -0
- package/src/core/lib/components/ui/searchable-list.jsx +160 -0
- package/src/core/lib/components/ui/select/index.js +37 -0
- package/src/core/lib/components/ui/select/select-content.jsx +30 -0
- package/src/core/lib/components/ui/select/select-group-heading.jsx +17 -0
- package/src/core/lib/components/ui/select/select-group.jsx +15 -0
- package/src/core/lib/components/ui/select/select-item.jsx +26 -0
- package/src/core/lib/components/ui/select/select-label.jsx +11 -0
- package/src/core/lib/components/ui/select/select-portal.jsx +4 -0
- package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
- package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
- package/src/core/lib/components/ui/select/select-separator.jsx +15 -0
- package/src/core/lib/components/ui/select/select-trigger.jsx +25 -0
- package/src/core/lib/components/ui/select/select.jsx +4 -0
- package/src/core/lib/components/ui/separator/index.js +7 -0
- package/src/core/lib/components/ui/separator/separator.jsx +22 -0
- package/src/core/lib/components/ui/sheet/index.js +34 -0
- package/src/core/lib/components/ui/sheet/sheet-close.jsx +7 -0
- package/src/core/lib/components/ui/sheet/sheet-content.jsx +35 -0
- package/src/core/lib/components/ui/sheet/sheet-description.jsx +15 -0
- package/src/core/lib/components/ui/sheet/sheet-footer.jsx +11 -0
- package/src/core/lib/components/ui/sheet/sheet-header.jsx +11 -0
- package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
- package/src/core/lib/components/ui/sheet/sheet-portal.jsx +4 -0
- package/src/core/lib/components/ui/sheet/sheet-title.jsx +15 -0
- package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
- package/src/core/lib/components/ui/sheet/sheet.jsx +4 -0
- package/src/core/lib/components/ui/textarea/index.js +7 -0
- package/src/core/lib/components/ui/textarea/textarea.jsx +18 -0
- package/src/core/lib/components/ui/toggle/index.js +8 -0
- package/src/core/lib/components/ui/toggle/toggle.jsx +36 -0
- package/src/core/lib/components/ui/toggle-group/index.js +10 -0
- package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
- package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
- package/src/core/lib/components/ui/tooltip/index.js +3 -0
- package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
- package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
- package/src/core/lib/components/ui/tooltip/tooltip.jsx +11 -0
- package/src/core/lib/components/ui/trigger-button/index.js +6 -0
- package/src/core/lib/components/ui/trigger-button/trigger-button.css +38 -0
- package/src/core/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
- package/src/core/lib/utils/index.js +6 -0
- package/src/core/logger/devLogger.js +238 -0
- package/src/core/logger/devLogger.test.js +193 -0
- package/src/core/modes/modes.css +98 -0
- package/src/core/modes/modes.js +492 -0
- package/src/core/modes/modes.test.js +562 -0
- package/src/core/mountStoryboardCore.js +478 -0
- package/src/core/rename-watcher/config.json +23 -0
- package/src/core/rename-watcher/watcher.js +531 -0
- package/src/core/scaffold.js +100 -0
- package/src/core/server/index.js +391 -0
- package/src/core/session/bodyClasses.js +128 -0
- package/src/core/session/bodyClasses.test.js +192 -0
- package/src/core/session/hashSubscribe.js +19 -0
- package/src/core/session/hashSubscribe.test.js +62 -0
- package/src/core/session/hideMode.js +424 -0
- package/src/core/session/hideMode.test.js +268 -0
- package/src/core/session/interceptHideParams.js +35 -0
- package/src/core/session/interceptHideParams.test.js +90 -0
- package/src/core/session/localStorage.js +134 -0
- package/src/core/session/localStorage.test.js +148 -0
- package/src/core/session/session.js +76 -0
- package/src/core/session/session.test.js +91 -0
- package/src/core/stores/canvasConfig.js +134 -0
- package/src/core/stores/canvasConfig.test.js +120 -0
- package/src/core/stores/commandActions.js +284 -0
- package/src/core/stores/commandPaletteConfig.js +31 -0
- package/src/core/stores/configSchema.js +232 -0
- package/src/core/stores/configSchema.test.js +72 -0
- package/src/core/stores/configStore.js +161 -0
- package/src/core/stores/customerModeConfig.js +30 -0
- package/src/core/stores/featureFlags.js +127 -0
- package/src/core/stores/paletteProviders.js +360 -0
- package/src/core/stores/paletteProviders.test.js +186 -0
- package/src/core/stores/plugins.js +40 -0
- package/src/core/stores/plugins.test.js +68 -0
- package/src/core/stores/recentArtifacts.js +68 -0
- package/src/core/stores/recentArtifacts.test.js +71 -0
- package/src/core/stores/sidePanelStore.ts +143 -0
- package/src/core/stores/themeStore.ts +291 -0
- package/src/core/stores/toolRegistry.js +227 -0
- package/src/core/stores/toolStateStore.js +183 -0
- package/src/core/stores/toolStateStore.test.js +220 -0
- package/src/core/stores/toolbarConfigStore.js +165 -0
- package/src/core/stores/uiConfig.js +64 -0
- package/src/core/stores/uiConfig.test.js +63 -0
- package/src/core/styles/tailwind.css +204 -0
- package/src/core/tools/handlers/autosync.js +12 -0
- package/src/core/tools/handlers/canvasAddWidget.js +11 -0
- package/src/core/tools/handlers/canvasAgents.js +20 -0
- package/src/core/tools/handlers/canvasToolbar.js +56 -0
- package/src/core/tools/handlers/commandPalette.js +9 -0
- package/src/core/tools/handlers/comments.js +16 -0
- package/src/core/tools/handlers/create.js +39 -0
- package/src/core/tools/handlers/devtools.js +122 -0
- package/src/core/tools/handlers/devtools.test.js +87 -0
- package/src/core/tools/handlers/featureFlags.js +21 -0
- package/src/core/tools/handlers/flows.js +68 -0
- package/src/core/tools/handlers/hideChrome.js +9 -0
- package/src/core/tools/handlers/hideToolbars.js +25 -0
- package/src/core/tools/handlers/inspector.js +19 -0
- package/src/core/tools/handlers/paletteTheme.js +35 -0
- package/src/core/tools/handlers/theme.js +9 -0
- package/src/core/tools/registry.js +26 -0
- package/src/core/tools/surfaces/canvasToolbar.js +10 -0
- package/src/core/tools/surfaces/commandList.js +10 -0
- package/src/core/tools/surfaces/mainToolbar.js +11 -0
- package/src/core/tools/surfaces/registry.js +19 -0
- package/src/core/ui/ActionMenuButton.jsx +114 -0
- package/src/core/ui/AutosyncMenuButton.css +67 -0
- package/src/core/ui/AutosyncMenuButton.jsx +242 -0
- package/src/core/ui/BranchSelect.jsx +29 -0
- package/src/core/ui/BranchSelect.module.css +30 -0
- package/src/core/ui/CanvasAgentsMenu.jsx +89 -0
- package/src/core/ui/CanvasCreateMenu.jsx +611 -0
- package/src/core/ui/CanvasSnap.css +27 -0
- package/src/core/ui/CanvasSnap.jsx +51 -0
- package/src/core/ui/CanvasUndoRedo.css +36 -0
- package/src/core/ui/CanvasUndoRedo.jsx +62 -0
- package/src/core/ui/CanvasZoomControl.css +53 -0
- package/src/core/ui/CanvasZoomControl.jsx +49 -0
- package/src/core/ui/CanvasZoomToFit.css +18 -0
- package/src/core/ui/CanvasZoomToFit.jsx +26 -0
- package/src/core/ui/CommandMenu.css +8 -0
- package/src/core/ui/CommandMenu.jsx +287 -0
- package/src/core/ui/CommandPalette.jsx +35 -0
- package/src/core/ui/CommandPaletteTrigger.jsx +25 -0
- package/src/core/ui/CommentsMenuButton.jsx +40 -0
- package/src/core/ui/CoreUIBar.css +47 -0
- package/src/core/ui/CoreUIBar.jsx +905 -0
- package/src/core/ui/CreateMenuButton.jsx +117 -0
- package/src/core/ui/HideChromeTrigger.jsx +48 -0
- package/src/core/ui/Icon.jsx +279 -0
- package/src/core/ui/InspectorPanel.css +109 -0
- package/src/core/ui/InspectorPanel.jsx +632 -0
- package/src/core/ui/PwaInstallBanner.css +42 -0
- package/src/core/ui/PwaInstallBanner.jsx +124 -0
- package/src/core/ui/SidePanel.jsx +261 -0
- package/src/core/ui/ThemeMenuButton.jsx +139 -0
- package/src/core/ui/core-ui-colors.css +129 -0
- package/src/core/ui/design-modes.ts +7 -0
- package/src/core/ui/sidepanel.css +301 -0
- package/src/core/ui/viewfinder.ts +7 -0
- package/src/core/ui-entry.js +30 -0
- package/src/core/utils/fuzzySearch.js +117 -0
- package/src/core/utils/fuzzySearch.test.js +119 -0
- package/src/core/utils/mobileViewport.js +57 -0
- package/src/core/utils/mobileViewport.test.js +68 -0
- package/src/core/utils/prodMode.js +38 -0
- package/src/core/utils/smoothCorners.js +20 -0
- package/src/core/vite/docs-handler.js +155 -0
- package/src/core/vite/server-plugin.js +797 -0
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
- package/src/core/workshop/features/createCanvas/index.js +14 -0
- package/src/core/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
- package/src/core/workshop/features/createFlow/index.js +19 -0
- package/src/core/workshop/features/createFlow/server.js +663 -0
- package/src/core/workshop/features/createPage/CreatePageForm.jsx +304 -0
- package/src/core/workshop/features/createPage/index.js +11 -0
- package/src/core/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
- package/src/core/workshop/features/createPrototype/index.js +19 -0
- package/src/core/workshop/features/createPrototype/server.js +433 -0
- package/src/core/workshop/features/createStory/CreateStoryForm.jsx +208 -0
- package/src/core/workshop/features/createStory/index.js +14 -0
- package/src/core/workshop/features/registry-server.js +22 -0
- package/src/core/workshop/features/registry.js +28 -0
- package/src/core/workshop/features/templateIndex.js +155 -0
- package/src/core/workshop/ui/WorkshopPanel.jsx +98 -0
- package/src/core/workshop/ui/mount.ts +6 -0
- package/src/core/worktree/port.js +268 -0
- package/src/core/worktree/port.test.js +222 -0
- package/src/core/worktree/serverRegistry.js +120 -0
- package/src/internals/AuthModal/AuthModal.jsx +132 -0
- package/src/internals/AuthModal/AuthModal.module.css +221 -0
- package/src/internals/BranchBar/BranchBar.jsx +87 -0
- package/src/internals/BranchBar/BranchBar.module.css +247 -0
- package/src/internals/BranchBar/useBranches.js +93 -0
- package/src/internals/BranchBar/useBranches.test.js +68 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +1361 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +219 -0
- package/src/internals/CommandPalette/command-palette.css +180 -0
- package/src/internals/FlowError.module.css +30 -0
- package/src/internals/Icon.jsx +279 -0
- package/src/internals/StoryboardContext.js +3 -0
- package/src/internals/Viewfinder.jsx +1479 -0
- package/src/internals/Viewfinder.module.css +1540 -0
- package/src/internals/Workspace.jsx +7 -0
- package/src/internals/__mocks__/virtual-storyboard-data-index.js +4 -0
- package/src/internals/canvas/CanvasControls.jsx +112 -0
- package/src/internals/canvas/CanvasControls.module.css +135 -0
- package/src/internals/canvas/CanvasPage.bridge.test.jsx +387 -0
- package/src/internals/canvas/CanvasPage.dragdrop.test.jsx +350 -0
- package/src/internals/canvas/CanvasPage.jsx +3092 -0
- package/src/internals/canvas/CanvasPage.module.css +187 -0
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +358 -0
- package/src/internals/canvas/CanvasToolbar.jsx +73 -0
- package/src/internals/canvas/CanvasToolbar.module.css +92 -0
- package/src/internals/canvas/ComponentErrorBoundary.jsx +50 -0
- package/src/internals/canvas/ConnectorLayer.jsx +208 -0
- package/src/internals/canvas/ConnectorLayer.module.css +129 -0
- package/src/internals/canvas/MarqueeOverlay.jsx +20 -0
- package/src/internals/canvas/PageSelector.jsx +587 -0
- package/src/internals/canvas/PageSelector.module.css +261 -0
- package/src/internals/canvas/PageSelector.test.jsx +113 -0
- package/src/internals/canvas/WebGLContextPool.jsx +292 -0
- package/src/internals/canvas/WebGLContextPool.test.jsx +165 -0
- package/src/internals/canvas/canvasApi.js +164 -0
- package/src/internals/canvas/canvasReloadGuard.js +37 -0
- package/src/internals/canvas/canvasReloadGuard.test.js +27 -0
- package/src/internals/canvas/canvasTheme.js +118 -0
- package/src/internals/canvas/componentIsolate.jsx +165 -0
- package/src/internals/canvas/componentSetIsolate.jsx +257 -0
- package/src/internals/canvas/computeCanvasBounds.test.js +121 -0
- package/src/internals/canvas/connectorGeometry.js +132 -0
- package/src/internals/canvas/hotPoolDevLogs.js +25 -0
- package/src/internals/canvas/textSelection.js +10 -0
- package/src/internals/canvas/textSelection.test.js +26 -0
- package/src/internals/canvas/useCanvas.js +126 -0
- package/src/internals/canvas/useCanvas.test.js +26 -0
- package/src/internals/canvas/useMarqueeSelect.js +213 -0
- package/src/internals/canvas/useMarqueeSelect.test.js +78 -0
- package/src/internals/canvas/useUndoRedo.js +86 -0
- package/src/internals/canvas/useUndoRedo.test.js +231 -0
- package/src/internals/canvas/widgets/CodePenEmbed.jsx +293 -0
- package/src/internals/canvas/widgets/CodePenEmbed.module.css +161 -0
- package/src/internals/canvas/widgets/ComponentSetWidget.jsx +2 -0
- package/src/internals/canvas/widgets/ComponentSetWidget.module.css +89 -0
- package/src/internals/canvas/widgets/ComponentWidget.jsx +14 -0
- package/src/internals/canvas/widgets/ComponentWidget.module.css +0 -0
- package/src/internals/canvas/widgets/CropOverlay.jsx +179 -0
- package/src/internals/canvas/widgets/CropOverlay.module.css +154 -0
- package/src/internals/canvas/widgets/ExpandedPane.jsx +474 -0
- package/src/internals/canvas/widgets/ExpandedPane.module.css +179 -0
- package/src/internals/canvas/widgets/ExpandedPane.test.jsx +240 -0
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
- package/src/internals/canvas/widgets/FigmaEmbed.jsx +296 -0
- package/src/internals/canvas/widgets/FigmaEmbed.module.css +222 -0
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +151 -0
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +83 -0
- package/src/internals/canvas/widgets/ImageWidget.jsx +287 -0
- package/src/internals/canvas/widgets/ImageWidget.module.css +81 -0
- package/src/internals/canvas/widgets/LinkPreview.jsx +439 -0
- package/src/internals/canvas/widgets/LinkPreview.module.css +585 -0
- package/src/internals/canvas/widgets/LinkPreview.test.jsx +193 -0
- package/src/internals/canvas/widgets/MarkdownBlock.jsx +354 -0
- package/src/internals/canvas/widgets/MarkdownBlock.module.css +377 -0
- package/src/internals/canvas/widgets/MarkdownBlock.test.jsx +92 -0
- package/src/internals/canvas/widgets/PromptWidget.jsx +428 -0
- package/src/internals/canvas/widgets/PromptWidget.module.css +273 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +463 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.module.css +579 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +10 -0
- package/src/internals/canvas/widgets/ResizeHandle.jsx +67 -0
- package/src/internals/canvas/widgets/ResizeHandle.module.css +29 -0
- package/src/internals/canvas/widgets/StickyNote.jsx +92 -0
- package/src/internals/canvas/widgets/StickyNote.module.css +70 -0
- package/src/internals/canvas/widgets/StickyNote.test.jsx +116 -0
- package/src/internals/canvas/widgets/StorySetWidget.jsx +208 -0
- package/src/internals/canvas/widgets/StorySetWidget.module.css +89 -0
- package/src/internals/canvas/widgets/StoryWidget.jsx +334 -0
- package/src/internals/canvas/widgets/StoryWidget.module.css +211 -0
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +146 -0
- package/src/internals/canvas/widgets/TerminalReadWidget.module.css +94 -0
- package/src/internals/canvas/widgets/TerminalWidget.jsx +704 -0
- package/src/internals/canvas/widgets/TerminalWidget.module.css +444 -0
- package/src/internals/canvas/widgets/TilesWidget.jsx +300 -0
- package/src/internals/canvas/widgets/TilesWidget.module.css +133 -0
- package/src/internals/canvas/widgets/WidgetChrome.jsx +580 -0
- package/src/internals/canvas/widgets/WidgetChrome.module.css +421 -0
- package/src/internals/canvas/widgets/WidgetWrapper.jsx +15 -0
- package/src/internals/canvas/widgets/WidgetWrapper.module.css +25 -0
- package/src/internals/canvas/widgets/codepenUrl.js +75 -0
- package/src/internals/canvas/widgets/codepenUrl.test.js +76 -0
- package/src/internals/canvas/widgets/embedInteraction.test.jsx +173 -0
- package/src/internals/canvas/widgets/embedOverlay.module.css +35 -0
- package/src/internals/canvas/widgets/embedTheme.js +148 -0
- package/src/internals/canvas/widgets/expandUtils.js +559 -0
- package/src/internals/canvas/widgets/expandUtils.test.js +155 -0
- package/src/internals/canvas/widgets/figmaUrl.js +118 -0
- package/src/internals/canvas/widgets/figmaUrl.test.js +139 -0
- package/src/internals/canvas/widgets/githubUrl.js +82 -0
- package/src/internals/canvas/widgets/githubUrl.test.js +74 -0
- package/src/internals/canvas/widgets/iframeDevLogs.js +49 -0
- package/src/internals/canvas/widgets/iframeDevLogs.test.jsx +81 -0
- package/src/internals/canvas/widgets/index.js +42 -0
- package/src/internals/canvas/widgets/pasteRules.js +295 -0
- package/src/internals/canvas/widgets/pasteRules.test.js +474 -0
- package/src/internals/canvas/widgets/snapshotDisplay.test.jsx +211 -0
- package/src/internals/canvas/widgets/tilePool.js +23 -0
- package/src/internals/canvas/widgets/tiles/diagonal-bl.png +0 -0
- package/src/internals/canvas/widgets/tiles/diagonal-br.png +0 -0
- package/src/internals/canvas/widgets/tiles/diagonal-tl.png +0 -0
- package/src/internals/canvas/widgets/tiles/leaf.png +0 -0
- package/src/internals/canvas/widgets/tiles/quarter-tl.png +0 -0
- package/src/internals/canvas/widgets/tiles/quarter-tr.png +0 -0
- package/src/internals/canvas/widgets/tiles/solid-a.png +0 -0
- package/src/internals/canvas/widgets/tiles/solid-b.png +0 -0
- package/src/internals/canvas/widgets/widgetConfig.js +291 -0
- package/src/internals/canvas/widgets/widgetConfig.test.js +68 -0
- package/src/internals/canvas/widgets/widgetIcons.jsx +190 -0
- package/src/internals/canvas/widgets/widgetProps.js +133 -0
- package/src/internals/context/FormContext.js +13 -0
- package/src/internals/context/FormContext.test.js +48 -0
- package/src/internals/context.jsx +481 -0
- package/src/internals/context.test.jsx +296 -0
- package/src/internals/hashPreserver.js +73 -0
- package/src/internals/hashPreserver.test.js +107 -0
- package/src/internals/hooks/useConfig.js +14 -0
- package/src/internals/hooks/useFeatureFlag.js +14 -0
- package/src/internals/hooks/useFlows.js +50 -0
- package/src/internals/hooks/useFlows.test.js +134 -0
- package/src/internals/hooks/useHideMode.js +31 -0
- package/src/internals/hooks/useHideMode.test.js +43 -0
- package/src/internals/hooks/useLocalStorage.js +57 -0
- package/src/internals/hooks/useLocalStorage.test.js +75 -0
- package/src/internals/hooks/useMode.js +43 -0
- package/src/internals/hooks/useObject.js +101 -0
- package/src/internals/hooks/useObject.test.js +74 -0
- package/src/internals/hooks/useOverride.js +84 -0
- package/src/internals/hooks/useOverride.test.js +71 -0
- package/src/internals/hooks/usePrototypeReloadGuard.js +64 -0
- package/src/internals/hooks/useRecord.js +158 -0
- package/src/internals/hooks/useRecord.test.js +221 -0
- package/src/internals/hooks/useScene.js +38 -0
- package/src/internals/hooks/useScene.test.js +66 -0
- package/src/internals/hooks/useSceneData.js +108 -0
- package/src/internals/hooks/useSceneData.test.js +136 -0
- package/src/internals/hooks/useSession.js +4 -0
- package/src/internals/hooks/useSession.test.js +8 -0
- package/src/internals/hooks/useThemeState.js +61 -0
- package/src/internals/hooks/useThemeState.test.js +66 -0
- package/src/internals/hooks/useUndoRedo.js +28 -0
- package/src/internals/hooks/useUndoRedo.test.js +64 -0
- package/src/internals/index.js +58 -0
- package/src/internals/story/ComponentSetPage.jsx +198 -0
- package/src/internals/story/ComponentSetPage.module.css +129 -0
- package/src/internals/story/StoryPage.jsx +147 -0
- package/src/internals/story/StoryPage.module.css +18 -0
- package/src/internals/test-utils.js +45 -0
- package/src/internals/vite/data-plugin.js +1508 -0
- package/src/internals/vite/data-plugin.test.js +1223 -0
- package/src/test-utils.js +44 -0
- package/toolbar.config.json +271 -0
- package/widgets.config.json +1537 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL hash-based utilities for storyboard session state.
|
|
3
|
+
*
|
|
4
|
+
* Session params are stored in the URL hash fragment (after #) to avoid
|
|
5
|
+
* triggering React Router re-renders. React Router (used by generouted)
|
|
6
|
+
* patches history.replaceState/pushState, so any search-param change
|
|
7
|
+
* causes a full route tree re-render. The hash is invisible to the router.
|
|
8
|
+
*
|
|
9
|
+
* Format: #key1=value1&key2=value2
|
|
10
|
+
* Example: /page?scene=default#user.name=Alice&settings.theme=dark
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse the current hash into a Map of key→value pairs.
|
|
15
|
+
* @returns {URLSearchParams}
|
|
16
|
+
*/
|
|
17
|
+
function parseHash() {
|
|
18
|
+
const raw = window.location.hash.replace(/^#/, '')
|
|
19
|
+
return new URLSearchParams(raw)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Write a URLSearchParams back to the hash.
|
|
24
|
+
* Uses window.location.hash (NOT history.replaceState) because
|
|
25
|
+
* generouted/React Router patches replaceState and would trigger
|
|
26
|
+
* a full route re-render. Native hash assignment only fires
|
|
27
|
+
* 'hashchange' which React Router ignores.
|
|
28
|
+
* @param {URLSearchParams} params
|
|
29
|
+
*/
|
|
30
|
+
function writeHash(params) {
|
|
31
|
+
const str = params.toString()
|
|
32
|
+
window.location.hash = str
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read a single session param value.
|
|
37
|
+
* @param {string} key
|
|
38
|
+
* @returns {string|null}
|
|
39
|
+
*/
|
|
40
|
+
export function getParam(key) {
|
|
41
|
+
return parseHash().get(key)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Write a single session param. Updates the hash in-place.
|
|
46
|
+
* @param {string} key
|
|
47
|
+
* @param {string} value
|
|
48
|
+
*/
|
|
49
|
+
export function setParam(key, value) {
|
|
50
|
+
const params = parseHash()
|
|
51
|
+
params.set(key, String(value))
|
|
52
|
+
writeHash(params)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return all session params as a plain object.
|
|
57
|
+
* @returns {Record<string, string>}
|
|
58
|
+
*/
|
|
59
|
+
export function getAllParams() {
|
|
60
|
+
const params = parseHash()
|
|
61
|
+
const result = {}
|
|
62
|
+
for (const [key, value] of params.entries()) {
|
|
63
|
+
result[key] = value
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Remove a single session param from the hash.
|
|
70
|
+
* @param {string} key
|
|
71
|
+
*/
|
|
72
|
+
export function removeParam(key) {
|
|
73
|
+
const params = parseHash()
|
|
74
|
+
params.delete(key)
|
|
75
|
+
writeHash(params)
|
|
76
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getParam, setParam, getAllParams, removeParam } from './session.js'
|
|
2
|
+
|
|
3
|
+
describe('getParam', () => {
|
|
4
|
+
it('returns null when hash is empty', () => {
|
|
5
|
+
window.location.hash = ''
|
|
6
|
+
expect(getParam('key')).toBeNull()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('returns value for existing param', () => {
|
|
10
|
+
window.location.hash = 'foo=bar'
|
|
11
|
+
expect(getParam('foo')).toBe('bar')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('returns null for missing param', () => {
|
|
15
|
+
window.location.hash = 'foo=bar'
|
|
16
|
+
expect(getParam('missing')).toBeNull()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('handles URL-encoded values', () => {
|
|
20
|
+
window.location.hash = 'name=hello%20world'
|
|
21
|
+
expect(getParam('name')).toBe('hello world')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('setParam', () => {
|
|
26
|
+
it('sets a new param in hash', () => {
|
|
27
|
+
window.location.hash = ''
|
|
28
|
+
setParam('key', 'value')
|
|
29
|
+
expect(getParam('key')).toBe('value')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('updates existing param', () => {
|
|
33
|
+
window.location.hash = 'key=old'
|
|
34
|
+
setParam('key', 'new')
|
|
35
|
+
expect(getParam('key')).toBe('new')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('preserves other params', () => {
|
|
39
|
+
window.location.hash = 'a=1&b=2'
|
|
40
|
+
setParam('c', '3')
|
|
41
|
+
expect(getParam('a')).toBe('1')
|
|
42
|
+
expect(getParam('b')).toBe('2')
|
|
43
|
+
expect(getParam('c')).toBe('3')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('converts value to string', () => {
|
|
47
|
+
window.location.hash = ''
|
|
48
|
+
setParam('num', 42)
|
|
49
|
+
expect(getParam('num')).toBe('42')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('getAllParams', () => {
|
|
54
|
+
it('returns empty object for empty hash', () => {
|
|
55
|
+
window.location.hash = ''
|
|
56
|
+
expect(getAllParams()).toEqual({})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('returns all params', () => {
|
|
60
|
+
window.location.hash = 'a=1&b=2'
|
|
61
|
+
expect(getAllParams()).toEqual({ a: '1', b: '2' })
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('handles multiple params', () => {
|
|
65
|
+
window.location.hash = 'x=hello&y=world&z=test'
|
|
66
|
+
const params = getAllParams()
|
|
67
|
+
expect(Object.keys(params)).toHaveLength(3)
|
|
68
|
+
expect(params).toEqual({ x: 'hello', y: 'world', z: 'test' })
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe('removeParam', () => {
|
|
73
|
+
it('removes existing param', () => {
|
|
74
|
+
window.location.hash = 'a=1&b=2'
|
|
75
|
+
removeParam('a')
|
|
76
|
+
expect(getParam('a')).toBeNull()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('preserves other params', () => {
|
|
80
|
+
window.location.hash = 'a=1&b=2&c=3'
|
|
81
|
+
removeParam('b')
|
|
82
|
+
expect(getParam('a')).toBe('1')
|
|
83
|
+
expect(getParam('c')).toBe('3')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('no-ops for missing param', () => {
|
|
87
|
+
window.location.hash = 'a=1'
|
|
88
|
+
removeParam('nonexistent')
|
|
89
|
+
expect(getParam('a')).toBe('1')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Config — project-level overrides for canvas behavior.
|
|
3
|
+
*
|
|
4
|
+
* Client repos use the "canvas" key in storyboard.config.json to customize
|
|
5
|
+
* canvas paste rules and other canvas-level settings.
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* "canvas": {
|
|
9
|
+
* "pasteRules": [
|
|
10
|
+
* { "pattern": "youtube\\.com/watch", "type": "link-preview", "props": { "url": "$url" } }
|
|
11
|
+
* ]
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* Framework-agnostic (zero npm dependencies).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Internal state
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
let _pasteRules = []
|
|
23
|
+
let _terminal = {}
|
|
24
|
+
let _agents = {}
|
|
25
|
+
let _zoom = { min: 10, max: 250, step: 10 }
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Configuration
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize canvas config from storyboard.config.json's "canvas" key.
|
|
33
|
+
* Called by mountStoryboardCore.
|
|
34
|
+
*
|
|
35
|
+
* @param {{ pasteRules?: object[], terminal?: object, agents?: object, zoom?: object }} [config]
|
|
36
|
+
*/
|
|
37
|
+
export function initCanvasConfig(config = {}) {
|
|
38
|
+
_pasteRules = Array.isArray(config.pasteRules) ? config.pasteRules : []
|
|
39
|
+
_terminal = config.terminal && typeof config.terminal === 'object' ? config.terminal : {}
|
|
40
|
+
_agents = config.agents && typeof config.agents === 'object' ? config.agents : {}
|
|
41
|
+
_zoom = config.zoom && typeof config.zoom === 'object'
|
|
42
|
+
? { min: config.zoom.min ?? 10, max: config.zoom.max ?? 250, step: config.zoom.step ?? 10 }
|
|
43
|
+
: { min: 10, max: 250, step: 10 }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the configured paste rules (raw config objects).
|
|
48
|
+
*
|
|
49
|
+
* @returns {object[]}
|
|
50
|
+
*/
|
|
51
|
+
export function getPasteRules() {
|
|
52
|
+
return _pasteRules
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get terminal widget configuration.
|
|
57
|
+
*
|
|
58
|
+
* @returns {{ theme?: object, fontSize?: number, fontFamily?: string, prompt?: string }}
|
|
59
|
+
*/
|
|
60
|
+
export function getTerminalConfig() {
|
|
61
|
+
return _terminal
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get agent configurations.
|
|
66
|
+
*
|
|
67
|
+
* @returns {object}
|
|
68
|
+
*/
|
|
69
|
+
export function getAgentsConfig() {
|
|
70
|
+
return _agents
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get canvas zoom configuration (min, max, step).
|
|
75
|
+
*
|
|
76
|
+
* @returns {{ min: number, max: number, step: number }}
|
|
77
|
+
*/
|
|
78
|
+
export function getCanvasZoom() {
|
|
79
|
+
return _zoom
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if a terminal/agent widget should be resizable based on config.
|
|
84
|
+
* Agent-level `resizable` overrides the base `terminal.resizable`.
|
|
85
|
+
*
|
|
86
|
+
* @param {string|null} [agentId] — agent ID to check for overrides
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
export function isTerminalResizable(agentId = null) {
|
|
90
|
+
if (agentId) {
|
|
91
|
+
const agentCfg = _agents[agentId]
|
|
92
|
+
if (agentCfg && agentCfg.resizable !== undefined) return agentCfg.resizable
|
|
93
|
+
}
|
|
94
|
+
return _terminal.resizable ?? false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get effective default dimensions for a terminal/agent widget.
|
|
99
|
+
* Cascade: agent config > terminal config > provided fallbacks.
|
|
100
|
+
*
|
|
101
|
+
* @param {string|null} [agentId] — agent ID to check for overrides
|
|
102
|
+
* @param {{ width: number, height: number }} [fallback] — schema-level fallbacks
|
|
103
|
+
* @returns {{ width: number, height: number }}
|
|
104
|
+
*/
|
|
105
|
+
export function getTerminalDimensions(agentId = null, fallback = { width: 800, height: 450 }) {
|
|
106
|
+
const base = {
|
|
107
|
+
width: _terminal.defaultWidth ?? fallback.width,
|
|
108
|
+
height: _terminal.defaultHeight ?? fallback.height,
|
|
109
|
+
}
|
|
110
|
+
if (agentId) {
|
|
111
|
+
const agentCfg = _agents[agentId]
|
|
112
|
+
if (agentCfg) {
|
|
113
|
+
return {
|
|
114
|
+
width: agentCfg.defaultWidth ?? base.width,
|
|
115
|
+
height: agentCfg.defaultHeight ?? base.height,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return base
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Test helpers
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Reset all internal state. Only for use in tests.
|
|
128
|
+
*/
|
|
129
|
+
export function _resetCanvasConfig() {
|
|
130
|
+
_pasteRules = []
|
|
131
|
+
_terminal = {}
|
|
132
|
+
_agents = {}
|
|
133
|
+
_zoom = { min: 10, max: 250, step: 10 }
|
|
134
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { initCanvasConfig, getPasteRules, isTerminalResizable, getTerminalDimensions, _resetCanvasConfig } from './canvasConfig.js'
|
|
3
|
+
|
|
4
|
+
describe('canvasConfig', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
_resetCanvasConfig()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('returns empty array by default', () => {
|
|
10
|
+
expect(getPasteRules()).toEqual([])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('stores paste rules from config', () => {
|
|
14
|
+
const rules = [
|
|
15
|
+
{ pattern: 'youtube\\.com', type: 'link-preview', props: { url: '$url' } },
|
|
16
|
+
]
|
|
17
|
+
initCanvasConfig({ pasteRules: rules })
|
|
18
|
+
expect(getPasteRules()).toEqual(rules)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('handles missing pasteRules gracefully', () => {
|
|
22
|
+
initCanvasConfig({})
|
|
23
|
+
expect(getPasteRules()).toEqual([])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('handles undefined config', () => {
|
|
27
|
+
initCanvasConfig()
|
|
28
|
+
expect(getPasteRules()).toEqual([])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('handles non-array pasteRules', () => {
|
|
32
|
+
initCanvasConfig({ pasteRules: 'not-an-array' })
|
|
33
|
+
expect(getPasteRules()).toEqual([])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('resets on _resetCanvasConfig', () => {
|
|
37
|
+
initCanvasConfig({ pasteRules: [{ pattern: '.', type: 'test' }] })
|
|
38
|
+
expect(getPasteRules()).toHaveLength(1)
|
|
39
|
+
_resetCanvasConfig()
|
|
40
|
+
expect(getPasteRules()).toEqual([])
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('isTerminalResizable', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
_resetCanvasConfig()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns false by default', () => {
|
|
50
|
+
expect(isTerminalResizable()).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('returns terminal.resizable when set', () => {
|
|
54
|
+
initCanvasConfig({ terminal: { resizable: true } })
|
|
55
|
+
expect(isTerminalResizable()).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('agent overrides terminal resizable', () => {
|
|
59
|
+
initCanvasConfig({
|
|
60
|
+
terminal: { resizable: false },
|
|
61
|
+
agents: { myAgent: { resizable: true } },
|
|
62
|
+
})
|
|
63
|
+
expect(isTerminalResizable('myAgent')).toBe(true)
|
|
64
|
+
expect(isTerminalResizable()).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('agent can disable resizable even when terminal enables it', () => {
|
|
68
|
+
initCanvasConfig({
|
|
69
|
+
terminal: { resizable: true },
|
|
70
|
+
agents: { fixedAgent: { resizable: false } },
|
|
71
|
+
})
|
|
72
|
+
expect(isTerminalResizable('fixedAgent')).toBe(false)
|
|
73
|
+
expect(isTerminalResizable()).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('falls back to terminal config for unknown agent', () => {
|
|
77
|
+
initCanvasConfig({ terminal: { resizable: true } })
|
|
78
|
+
expect(isTerminalResizable('nonexistent')).toBe(true)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('getTerminalDimensions', () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
_resetCanvasConfig()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('returns fallback defaults when no config', () => {
|
|
88
|
+
expect(getTerminalDimensions()).toEqual({ width: 800, height: 450 })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('returns terminal config dimensions', () => {
|
|
92
|
+
initCanvasConfig({ terminal: { defaultWidth: 1000, defaultHeight: 600 } })
|
|
93
|
+
expect(getTerminalDimensions()).toEqual({ width: 1000, height: 600 })
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('agent overrides terminal dimensions', () => {
|
|
97
|
+
initCanvasConfig({
|
|
98
|
+
terminal: { defaultWidth: 1000, defaultHeight: 600 },
|
|
99
|
+
agents: { bigAgent: { defaultWidth: 1400, defaultHeight: 800 } },
|
|
100
|
+
})
|
|
101
|
+
expect(getTerminalDimensions('bigAgent')).toEqual({ width: 1400, height: 800 })
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('agent partial override inherits from terminal for unset dimensions', () => {
|
|
105
|
+
initCanvasConfig({
|
|
106
|
+
terminal: { defaultWidth: 1000, defaultHeight: 600 },
|
|
107
|
+
agents: { wideAgent: { defaultWidth: 1400 } },
|
|
108
|
+
})
|
|
109
|
+
expect(getTerminalDimensions('wideAgent')).toEqual({ width: 1400, height: 600 })
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('falls back to terminal config for unknown agent', () => {
|
|
113
|
+
initCanvasConfig({ terminal: { defaultWidth: 900, defaultHeight: 500 } })
|
|
114
|
+
expect(getTerminalDimensions('nonexistent')).toEqual({ width: 900, height: 500 })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('uses custom fallback when provided', () => {
|
|
118
|
+
expect(getTerminalDimensions(null, { width: 1200, height: 450 })).toEqual({ width: 1200, height: 450 })
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Actions — config-driven registry for command menu entries.
|
|
3
|
+
*
|
|
4
|
+
* The command section of toolbar.config.json declares action metadata:
|
|
5
|
+
* id, label, type, hideFrom, separatorBefore
|
|
6
|
+
*
|
|
7
|
+
* Handler shapes by type:
|
|
8
|
+
* default: () => void
|
|
9
|
+
* toggle: { execute(), getState() → boolean }
|
|
10
|
+
* submenu: { getChildren() → Array<{ id?, label, type, active?, execute }> }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Internal state
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
let _config = { actions: {}, footer: '' }
|
|
18
|
+
|
|
19
|
+
/** @type {Map<string, any>} id → handler (function or object) */
|
|
20
|
+
const _handlers = new Map()
|
|
21
|
+
|
|
22
|
+
/** @type {Set<Function>} */
|
|
23
|
+
const _listeners = new Set()
|
|
24
|
+
|
|
25
|
+
let _snapshotVersion = 0
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Initialization
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Seed the registry from a menu config object.
|
|
33
|
+
* Called once at app startup.
|
|
34
|
+
*
|
|
35
|
+
* @param {{ actions: Array, footer?: string }} config
|
|
36
|
+
*/
|
|
37
|
+
export function initCommandActions(config) {
|
|
38
|
+
_config = { ...config }
|
|
39
|
+
_notify()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Handler registration
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register a handler for a declared action.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} id Action id (e.g. "core/viewfinder")
|
|
50
|
+
* @param {Function|object} handler
|
|
51
|
+
* - default type: () => void
|
|
52
|
+
* - toggle type: { execute(), getState() }
|
|
53
|
+
* - submenu type: { getChildren() }
|
|
54
|
+
*/
|
|
55
|
+
export function registerCommandAction(id, handler) {
|
|
56
|
+
_handlers.set(id, handler)
|
|
57
|
+
_notify()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Remove a previously registered handler.
|
|
62
|
+
* @param {string} id
|
|
63
|
+
*/
|
|
64
|
+
export function unregisterCommandAction(id) {
|
|
65
|
+
_handlers.delete(id)
|
|
66
|
+
_notify()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Dynamic actions (not in config — registered at runtime)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/** @type {Array} */
|
|
74
|
+
let _dynamicActions = []
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add dynamic actions (e.g. comments menu items).
|
|
78
|
+
* These are appended after config-declared actions.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} group Group key for bulk replacement (e.g. "comments")
|
|
81
|
+
* @param {Array<{ id: string, label: string, type?: string, separatorBefore?: boolean }>} actions
|
|
82
|
+
* @param {Record<string, Function|object>} [handlers] id → handler map
|
|
83
|
+
*/
|
|
84
|
+
export function setDynamicActions(group, actions, handlers = {}) {
|
|
85
|
+
// Remove previous entries for this group
|
|
86
|
+
_dynamicActions = _dynamicActions.filter(a => a._group !== group)
|
|
87
|
+
|
|
88
|
+
for (const action of actions) {
|
|
89
|
+
_dynamicActions.push({ ...action, type: action.type || 'default', _group: group })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Register handlers
|
|
93
|
+
for (const [id, handler] of Object.entries(handlers)) {
|
|
94
|
+
_handlers.set(id, handler)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_notify()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove all dynamic actions for a group.
|
|
102
|
+
* @param {string} group
|
|
103
|
+
*/
|
|
104
|
+
export function clearDynamicActions(group) {
|
|
105
|
+
const removed = _dynamicActions.filter(a => a._group === group)
|
|
106
|
+
_dynamicActions = _dynamicActions.filter(a => a._group !== group)
|
|
107
|
+
for (const a of removed) {
|
|
108
|
+
_handlers.delete(a.id)
|
|
109
|
+
}
|
|
110
|
+
_notify()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Resolution
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
// Base path for route matching — set by CoreUIBar or app layer
|
|
118
|
+
let _basePath = '/'
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Set the base path used by isExcludedByRoute for portable pattern matching.
|
|
122
|
+
* @param {string} basePath
|
|
123
|
+
*/
|
|
124
|
+
export function setRoutingBasePath(basePath) {
|
|
125
|
+
_basePath = basePath || '/'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if an item is excluded from the current route.
|
|
130
|
+
* Strips the base path so patterns match against app-relative paths
|
|
131
|
+
* (e.g. "/" for the root, "/Signup" for a prototype page).
|
|
132
|
+
* @param {object} item Action or menu with optional excludeRoutes array
|
|
133
|
+
* @returns {boolean} true if the item should be hidden
|
|
134
|
+
*/
|
|
135
|
+
export function isExcludedByRoute(item) {
|
|
136
|
+
const patterns = item.excludeRoutes
|
|
137
|
+
if (!patterns || !Array.isArray(patterns) || patterns.length === 0) return false
|
|
138
|
+
if (typeof window === 'undefined') return false
|
|
139
|
+
let pathname = window.location.pathname
|
|
140
|
+
const base = _basePath.replace(/\/+$/, '')
|
|
141
|
+
if (base && pathname.startsWith(base)) {
|
|
142
|
+
pathname = pathname.slice(base.length) || '/'
|
|
143
|
+
}
|
|
144
|
+
return patterns.some(pattern => new RegExp(pattern).test(pathname))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if an action is visible in a given mode.
|
|
149
|
+
* @param {object} action
|
|
150
|
+
* @param {string} mode
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
function actionVisibleInMode(action, mode) {
|
|
154
|
+
if (isExcludedByRoute(action)) return false
|
|
155
|
+
const modes = action.modes
|
|
156
|
+
if (!modes) return true
|
|
157
|
+
return modes.includes('*') || modes.includes(mode)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get resolved actions for a given mode.
|
|
162
|
+
* Filters by each action's modes array, appends dynamic actions.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} mode Current mode name
|
|
165
|
+
* @returns {Array<{ id, label, type, separatorBefore?, handler?, active? }>}
|
|
166
|
+
*/
|
|
167
|
+
export function getActionsForMode(mode) {
|
|
168
|
+
const configActions = Array.isArray(_config.actions) ? _config.actions : []
|
|
169
|
+
const filtered = configActions.filter(a => actionVisibleInMode(a, mode))
|
|
170
|
+
const dynamic = _dynamicActions.filter(a => actionVisibleInMode(a, mode))
|
|
171
|
+
|
|
172
|
+
// Insert dynamic actions before the footer (if present)
|
|
173
|
+
const footerIndex = filtered.findIndex(a => a.type === 'footer')
|
|
174
|
+
let all
|
|
175
|
+
if (footerIndex >= 0 && dynamic.length > 0) {
|
|
176
|
+
all = [
|
|
177
|
+
...filtered.slice(0, footerIndex),
|
|
178
|
+
...dynamic,
|
|
179
|
+
...filtered.slice(footerIndex),
|
|
180
|
+
]
|
|
181
|
+
} else {
|
|
182
|
+
all = [...filtered, ...dynamic]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return all.map(a => {
|
|
186
|
+
const handler = _handlers.get(a.id)
|
|
187
|
+
const isToggle = a.type === 'toggle'
|
|
188
|
+
const active = isToggle && handler?.getState ? handler.getState() : false
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
id: a.id,
|
|
192
|
+
label: a.label,
|
|
193
|
+
type: a.type || 'default',
|
|
194
|
+
url: a.url || null,
|
|
195
|
+
toolKey: a.toolKey || null,
|
|
196
|
+
localOnly: a.localOnly || false,
|
|
197
|
+
hideFromCommandPaletteSearch: a.hideFromCommandPaletteSearch || false,
|
|
198
|
+
handler,
|
|
199
|
+
active,
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Execute an action by id.
|
|
206
|
+
* @param {string} id
|
|
207
|
+
*/
|
|
208
|
+
export function executeAction(id) {
|
|
209
|
+
const handler = _handlers.get(id)
|
|
210
|
+
if (!handler) return
|
|
211
|
+
if (typeof handler === 'function') {
|
|
212
|
+
handler()
|
|
213
|
+
} else if (handler.execute) {
|
|
214
|
+
handler.execute()
|
|
215
|
+
}
|
|
216
|
+
_notify()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get submenu children for a submenu-type action.
|
|
221
|
+
* @param {string} id
|
|
222
|
+
* @returns {Array<{ id?, label, type, active?, execute? }>}
|
|
223
|
+
*/
|
|
224
|
+
export function getActionChildren(id) {
|
|
225
|
+
const handler = _handlers.get(id)
|
|
226
|
+
if (!handler?.getChildren) return []
|
|
227
|
+
return handler.getChildren()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if a handler provides dynamic children (getChildren).
|
|
232
|
+
* Used by CoreUIBar to distinguish action-menu tools (gate on children count)
|
|
233
|
+
* from custom-component menus (always visible, render their own content).
|
|
234
|
+
* @param {string} id
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
237
|
+
export function hasChildrenProvider(id) {
|
|
238
|
+
const handler = _handlers.get(id)
|
|
239
|
+
return !!handler?.getChildren
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Reactivity
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Subscribe to action changes. Compatible with useSyncExternalStore.
|
|
248
|
+
* @param {Function} callback
|
|
249
|
+
* @returns {Function} Unsubscribe
|
|
250
|
+
*/
|
|
251
|
+
export function subscribeToCommandActions(callback) {
|
|
252
|
+
_listeners.add(callback)
|
|
253
|
+
return () => _listeners.delete(callback)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Snapshot for useSyncExternalStore.
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
export function getCommandActionsSnapshot() {
|
|
261
|
+
return String(_snapshotVersion)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function _notify() {
|
|
265
|
+
_snapshotVersion++
|
|
266
|
+
for (const cb of _listeners) {
|
|
267
|
+
try { cb() } catch (err) {
|
|
268
|
+
console.error('[storyboard] Error in command action subscriber:', err)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Test helpers
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
/** Reset all state. Only for tests. */
|
|
278
|
+
export function _resetCommandActions() {
|
|
279
|
+
_config = { actions: {}, footer: '' }
|
|
280
|
+
_handlers.clear()
|
|
281
|
+
_dynamicActions = []
|
|
282
|
+
_listeners.clear()
|
|
283
|
+
_snapshotVersion = 0
|
|
284
|
+
}
|