@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,797 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storyboard Server Plugin — core dev-server infrastructure.
|
|
3
|
+
*
|
|
4
|
+
* Always-on Vite plugin that mounts a middleware backbone at `/_storyboard/`.
|
|
5
|
+
* Reads `storyboard.config.json` for workshop features and plugin config.
|
|
6
|
+
* Workshop API routes are wired directly; plugins register via the registry.
|
|
7
|
+
*
|
|
8
|
+
* Usage in vite.config.js:
|
|
9
|
+
* import storyboardServer from '@dfosco/storyboard/vite/server'
|
|
10
|
+
* storyboardServer() // reads storyboard.config.json, no args needed
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'node:fs'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import { parse as parseJsonc } from 'jsonc-parser'
|
|
16
|
+
import { getConfig } from '../stores/configSchema.js'
|
|
17
|
+
import { createDevLogger, setDevLogger } from '../logger/devLogger.js'
|
|
18
|
+
import { serverFeatures as workshopFeatures } from '../workshop/features/registry-server.js'
|
|
19
|
+
import { docsHandler, collectFiles } from './docs-handler.js'
|
|
20
|
+
import { createCanvasHandler } from '../canvas/server.js'
|
|
21
|
+
import { setupSelectedWidgets } from '../canvas/selectedWidgets.js'
|
|
22
|
+
import { HotPoolManager } from '../canvas/hot-pool.js'
|
|
23
|
+
import { createAutosyncHandler } from '../autosync/server.js'
|
|
24
|
+
import { setupTerminalServer } from '../canvas/terminal-server.js'
|
|
25
|
+
import { listSessions, detachSession, killSession, orphanSession, bulkCleanup, getSessionStats } from '../canvas/terminal-registry.js'
|
|
26
|
+
import { execSync as cpExecSync } from 'node:child_process'
|
|
27
|
+
import { list as listRunningServers } from '../worktree/serverRegistry.js'
|
|
28
|
+
|
|
29
|
+
const API_PREFIX = '/_storyboard/'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse JSON request body from an IncomingMessage.
|
|
33
|
+
*/
|
|
34
|
+
function parseJsonBody(req) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
let body = ''
|
|
37
|
+
req.on('data', (chunk) => { body += chunk })
|
|
38
|
+
req.on('end', () => {
|
|
39
|
+
if (!body) return resolve({})
|
|
40
|
+
try { resolve(JSON.parse(body)) }
|
|
41
|
+
catch { reject(new Error('Invalid JSON body')) }
|
|
42
|
+
})
|
|
43
|
+
req.on('error', reject)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Send a JSON response.
|
|
49
|
+
*/
|
|
50
|
+
function sendJson(res, status, data) {
|
|
51
|
+
res.writeHead(status, { 'Content-Type': 'application/json' })
|
|
52
|
+
res.end(JSON.stringify(data))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create a logging wrapper around sendJson.
|
|
57
|
+
* Reads per-request route context from res.__sbLogCtx (set by middleware).
|
|
58
|
+
*/
|
|
59
|
+
function createLoggedSendJson(logger) {
|
|
60
|
+
return function sendJsonLogged(res, status, data) {
|
|
61
|
+
sendJson(res, status, data)
|
|
62
|
+
if (status >= 400 && logger) {
|
|
63
|
+
const ctx = res.__sbLogCtx || {}
|
|
64
|
+
logger.logResponse({
|
|
65
|
+
status,
|
|
66
|
+
method: ctx.method || 'UNKNOWN',
|
|
67
|
+
url: ctx.url || '',
|
|
68
|
+
route: ctx.route || null,
|
|
69
|
+
subRoute: ctx.subRoute || null,
|
|
70
|
+
error: data?.error || null,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read storyboard.config.json from the project root and apply defaults.
|
|
78
|
+
*/
|
|
79
|
+
function readConfig(root) {
|
|
80
|
+
const configPath = path.join(root, 'storyboard.config.json')
|
|
81
|
+
if (!fs.existsSync(configPath)) return getConfig({})
|
|
82
|
+
try {
|
|
83
|
+
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
84
|
+
return getConfig(parseJsonc(raw) || {})
|
|
85
|
+
} catch {
|
|
86
|
+
return getConfig({})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Core storyboard server Vite plugin.
|
|
92
|
+
*/
|
|
93
|
+
export default function storyboardServer() {
|
|
94
|
+
let root = ''
|
|
95
|
+
let base = '/'
|
|
96
|
+
let config = {}
|
|
97
|
+
let isDev = false
|
|
98
|
+
|
|
99
|
+
// Route handler registry — plugins register here during setup
|
|
100
|
+
const routeHandlers = new Map()
|
|
101
|
+
const clientScripts = []
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
name: 'storyboard-server',
|
|
105
|
+
|
|
106
|
+
config() {
|
|
107
|
+
return {
|
|
108
|
+
optimizeDeps: {
|
|
109
|
+
include: [
|
|
110
|
+
'highlight.js/lib/core',
|
|
111
|
+
'highlight.js/lib/languages/javascript',
|
|
112
|
+
'highlight.js/lib/languages/typescript',
|
|
113
|
+
'highlight.js/lib/languages/xml',
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
configResolved(viteConfig) {
|
|
120
|
+
root = viteConfig.root
|
|
121
|
+
base = viteConfig.base || '/'
|
|
122
|
+
config = readConfig(root)
|
|
123
|
+
isDev = viteConfig.command === 'serve'
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
configureServer(server) {
|
|
127
|
+
// --- Reload guard ----------------------------------------------------------
|
|
128
|
+
// Suppress full-reloads and HMR updates for guarded clients.
|
|
129
|
+
//
|
|
130
|
+
// Two guard channels:
|
|
131
|
+
// 1. Canvas guard — canvas pages send heartbeats via storyboard:canvas-hmr-guard.
|
|
132
|
+
// Controlled by the "canvas-auto-reload" feature flag (default: false = guard ON).
|
|
133
|
+
// 2. Prototype guard — all pages send heartbeats via storyboard:prototype-reload-guard.
|
|
134
|
+
// Controlled by the "prototype-auto-reload" feature flag (default: true = guard OFF).
|
|
135
|
+
//
|
|
136
|
+
// Both guards auto-expire 5s after the last heartbeat so closed tabs never
|
|
137
|
+
// leave them stuck. Custom storyboard events always pass through.
|
|
138
|
+
{
|
|
139
|
+
let recentCanvasMutationAt = 0
|
|
140
|
+
const CANVAS_WINDOW_MS = 1500
|
|
141
|
+
const GUARD_TTL_MS = 5000
|
|
142
|
+
const isCanvasFile = (file = '') => /\.canvas\.jsonl$/i.test(file.replace(/\\/g, '/'))
|
|
143
|
+
|
|
144
|
+
const markCanvasMutation = (file = '') => {
|
|
145
|
+
if (isCanvasFile(file)) recentCanvasMutationAt = Date.now()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
server.watcher.on('change', markCanvasMutation)
|
|
149
|
+
server.watcher.on('add', markCanvasMutation)
|
|
150
|
+
server.watcher.on('unlink', markCanvasMutation)
|
|
151
|
+
|
|
152
|
+
const canvasGuardedClients = new Map()
|
|
153
|
+
const prototypeGuardedClients = new Map()
|
|
154
|
+
|
|
155
|
+
server.hot.on('storyboard:canvas-hmr-guard', (data, client) => {
|
|
156
|
+
if (data.active) {
|
|
157
|
+
canvasGuardedClients.set(client, Date.now() + GUARD_TTL_MS)
|
|
158
|
+
} else {
|
|
159
|
+
canvasGuardedClients.delete(client)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
server.hot.on('storyboard:prototype-reload-guard', (data, client) => {
|
|
164
|
+
if (data.active) {
|
|
165
|
+
prototypeGuardedClients.set(client, Date.now() + GUARD_TTL_MS)
|
|
166
|
+
} else {
|
|
167
|
+
prototypeGuardedClients.delete(client)
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const cleanup = setInterval(() => {
|
|
172
|
+
const now = Date.now()
|
|
173
|
+
for (const [client, until] of canvasGuardedClients) {
|
|
174
|
+
if (now > until || !server.ws.clients.has(client)) {
|
|
175
|
+
canvasGuardedClients.delete(client)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const [client, until] of prototypeGuardedClients) {
|
|
179
|
+
if (now > until || !server.ws.clients.has(client)) {
|
|
180
|
+
prototypeGuardedClients.delete(client)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}, 10000)
|
|
184
|
+
server.httpServer?.on('close', () => clearInterval(cleanup))
|
|
185
|
+
|
|
186
|
+
function isClientGuarded(client) {
|
|
187
|
+
const cu = canvasGuardedClients.get(client)
|
|
188
|
+
if (cu != null && Date.now() < cu) return true
|
|
189
|
+
const pu = prototypeGuardedClients.get(client)
|
|
190
|
+
if (pu != null && Date.now() < pu) return true
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const originalSend = server.ws.send.bind(server.ws)
|
|
195
|
+
server.ws.send = (payload, ...rest) => {
|
|
196
|
+
// Suppress broadcast reloads within the canvas mutation window
|
|
197
|
+
if (
|
|
198
|
+
payload &&
|
|
199
|
+
payload.type === 'full-reload' &&
|
|
200
|
+
Date.now() - recentCanvasMutationAt < CANVAS_WINDOW_MS
|
|
201
|
+
) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// No guarded clients → broadcast normally
|
|
206
|
+
if (canvasGuardedClients.size === 0 && prototypeGuardedClients.size === 0) {
|
|
207
|
+
return originalSend(payload, ...rest)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// For reload/update payloads, send only to unguarded clients
|
|
211
|
+
if (payload && (payload.type === 'full-reload' || payload.type === 'update')) {
|
|
212
|
+
for (const client of server.ws.clients) {
|
|
213
|
+
if (!isClientGuarded(client)) {
|
|
214
|
+
client.send(payload)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Everything else (custom events, errors) broadcasts normally
|
|
221
|
+
return originalSend(payload, ...rest)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// --- End reload guard ------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
// Initialize dev logger for structured o11y logging
|
|
227
|
+
const devDomain = config.devDomain || null
|
|
228
|
+
let currentBranch = null
|
|
229
|
+
try { currentBranch = cpExecSync('git branch --show-current', { encoding: 'utf8', cwd: root }).trim() } catch { /* empty */ }
|
|
230
|
+
const logVerbose = config.featureFlags?.['dev-logs'] || false
|
|
231
|
+
const devLogger = createDevLogger({ root, devDomain, branch: currentBranch, verbose: logVerbose })
|
|
232
|
+
setDevLogger(devLogger) // make available to all server-side modules via devLog()
|
|
233
|
+
const sendJsonLogged = createLoggedSendJson(devLogger)
|
|
234
|
+
|
|
235
|
+
// Listen for browser-side console errors forwarded via HMR
|
|
236
|
+
server.hot.on('storyboard:client-error', (data) => {
|
|
237
|
+
devLogger.logEvent(data.level || 'error', data.message || 'Unknown browser error', {
|
|
238
|
+
source: 'browser',
|
|
239
|
+
url: data.url || null,
|
|
240
|
+
line: data.line || null,
|
|
241
|
+
col: data.col || null,
|
|
242
|
+
stack: data.stack || null,
|
|
243
|
+
route: data.route || null,
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const workshopConfig = config.workshop || {}
|
|
248
|
+
const enabledFeatures = workshopConfig.features || {}
|
|
249
|
+
|
|
250
|
+
// Wire workshop API routes — compose handlers from all enabled features
|
|
251
|
+
const workshopHandlers = []
|
|
252
|
+
for (const [featureName, featureModule] of Object.entries(workshopFeatures)) {
|
|
253
|
+
if (enabledFeatures[featureName] === false) continue
|
|
254
|
+
if (featureModule.serverSetup) {
|
|
255
|
+
workshopHandlers.push(featureModule.serverSetup({ root, sendJson: sendJsonLogged, workshopConfig }))
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (workshopHandlers.length > 0) {
|
|
259
|
+
routeHandlers.set('workshop', async (req, res, ctx) => {
|
|
260
|
+
for (const handler of workshopHandlers) {
|
|
261
|
+
await handler(req, res, ctx)
|
|
262
|
+
if (res.writableEnded) return
|
|
263
|
+
}
|
|
264
|
+
sendJsonLogged(res, 404, { error: `Unknown workshop route: ${ctx.method} ${ctx.path}` })
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Wire docs API routes (always enabled — serves README + source files)
|
|
269
|
+
routeHandlers.set('docs', docsHandler({ root, sendJson: sendJsonLogged }))
|
|
270
|
+
|
|
271
|
+
// Create shared hot pool manager (per-type pre-warmed sessions)
|
|
272
|
+
const hotPoolConfig = config.hotPool || {}
|
|
273
|
+
const agentsConfig = config.canvas?.agents || {}
|
|
274
|
+
const wsSend = server.ws.send.bind(server.ws)
|
|
275
|
+
const hotPool = new HotPoolManager({ root, config: hotPoolConfig, agentsConfig, wsSend })
|
|
276
|
+
hotPool.start().catch((err) => {
|
|
277
|
+
devLogger.logEvent('error', 'Hot pool failed to start', { error: err.message })
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Wire canvas API routes (always enabled — CRUD for .canvas.jsonl files)
|
|
281
|
+
routeHandlers.set('canvas', createCanvasHandler({ root, sendJson: sendJsonLogged, hotPool }))
|
|
282
|
+
|
|
283
|
+
// Selected widgets bridge — writes .selectedwidgets.json for Copilot context
|
|
284
|
+
setupSelectedWidgets(server, root)
|
|
285
|
+
|
|
286
|
+
// Terminal WebSocket server — PTY backend for terminal canvas widgets
|
|
287
|
+
if (server.httpServer) {
|
|
288
|
+
let branch = 'unknown'
|
|
289
|
+
try {
|
|
290
|
+
branch = cpExecSync('git branch --show-current', { encoding: 'utf8', cwd: root }).trim()
|
|
291
|
+
} catch { /* empty */ }
|
|
292
|
+
setupTerminalServer(server.httpServer, base, branch, hotPool)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Ignore assets/canvas/ so image/snapshot writes don't trigger reloads
|
|
296
|
+
server.watcher.unwatch(path.join(root, 'assets', 'canvas', 'images'))
|
|
297
|
+
server.watcher.unwatch(path.join(root, 'assets', 'canvas', 'snapshots'))
|
|
298
|
+
server.watcher.unwatch(path.join(root, 'assets', '.storyboard-public', 'terminal-snapshots'))
|
|
299
|
+
|
|
300
|
+
// Wire autosync API routes (always enabled — git automation for dev)
|
|
301
|
+
routeHandlers.set('autosync', createAutosyncHandler({ root, sendJson: sendJsonLogged }))
|
|
302
|
+
|
|
303
|
+
// Terminal sessions API — list, detach, kill sessions
|
|
304
|
+
routeHandlers.set('terminal', async (req, res, ctx) => {
|
|
305
|
+
// Strip query string and leading slash from path
|
|
306
|
+
const rawPath = (ctx.path || '/').replace(/^\//, '')
|
|
307
|
+
const subpath = rawPath.split('?')[0]
|
|
308
|
+
|
|
309
|
+
// GET /sessions — list all sessions (optional ?branch= filter)
|
|
310
|
+
if (ctx.method === 'GET' && (subpath === 'sessions' || subpath === 'sessions/')) {
|
|
311
|
+
const url = new URL(req.url, 'http://localhost')
|
|
312
|
+
const filterBranch = url.searchParams.get('branch') || null
|
|
313
|
+
sendJsonLogged(res, 200, { sessions: listSessions(filterBranch) })
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// GET /sessions/stats — quick session counts by status
|
|
318
|
+
if (ctx.method === 'GET' && subpath === 'sessions/stats') {
|
|
319
|
+
sendJsonLogged(res, 200, getSessionStats())
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// POST /sessions/cleanup — bulk remove sessions by status
|
|
324
|
+
if (ctx.method === 'POST' && subpath === 'sessions/cleanup') {
|
|
325
|
+
let body = ''
|
|
326
|
+
for await (const chunk of req) body += chunk
|
|
327
|
+
try {
|
|
328
|
+
const { statuses } = JSON.parse(body)
|
|
329
|
+
const allowed = new Set(['archived', 'background'])
|
|
330
|
+
if (!Array.isArray(statuses) || statuses.length === 0) {
|
|
331
|
+
sendJsonLogged(res, 400, { error: 'statuses must be a non-empty array' })
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
const invalid = statuses.filter(s => !allowed.has(s))
|
|
335
|
+
if (invalid.length > 0) {
|
|
336
|
+
sendJsonLogged(res, 400, { error: `Invalid statuses: ${invalid.join(', ')}. Allowed: archived, background` })
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
const result = bulkCleanup({ statuses })
|
|
340
|
+
sendJsonLogged(res, 200, { success: true, ...result })
|
|
341
|
+
} catch {
|
|
342
|
+
sendJsonLogged(res, 400, { error: 'Invalid JSON body' })
|
|
343
|
+
}
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// POST /sessions/:name/detach — detach a session
|
|
348
|
+
const detachMatch = subpath.match(/^sessions\/(.+)\/detach$/)
|
|
349
|
+
if (ctx.method === 'POST' && detachMatch) {
|
|
350
|
+
const tmuxName = decodeURIComponent(detachMatch[1])
|
|
351
|
+
const entry = detachSession(tmuxName)
|
|
352
|
+
if (!entry) {
|
|
353
|
+
sendJsonLogged(res, 404, { error: 'Session not found' })
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
sendJsonLogged(res, 200, { success: true, session: entry })
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// POST /sessions/:name/orphan — archive a session with grace timer
|
|
361
|
+
const orphanMatch = subpath.match(/^sessions\/(.+)\/orphan$/)
|
|
362
|
+
if (ctx.method === 'POST' && orphanMatch) {
|
|
363
|
+
const tmuxName = decodeURIComponent(orphanMatch[1])
|
|
364
|
+
orphanSession(tmuxName)
|
|
365
|
+
sendJsonLogged(res, 200, { success: true })
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// DELETE /sessions/:name — kill a session immediately
|
|
370
|
+
const deleteMatch = subpath.match(/^sessions\/(.+)$/)
|
|
371
|
+
if (ctx.method === 'DELETE' && deleteMatch) {
|
|
372
|
+
const tmuxName = decodeURIComponent(deleteMatch[1])
|
|
373
|
+
killSession(tmuxName)
|
|
374
|
+
sendJsonLogged(res, 200, { success: true })
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Hot Pool routes (/terminal/hot-pool/*) ──────────────
|
|
379
|
+
|
|
380
|
+
// GET /hot-pool — pool status
|
|
381
|
+
if (ctx.method === 'GET' && subpath === 'hot-pool') {
|
|
382
|
+
sendJsonLogged(res, 200, hotPool.status())
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// PUT /hot-pool — reconfigure pool
|
|
387
|
+
if (ctx.method === 'PUT' && subpath === 'hot-pool') {
|
|
388
|
+
hotPool.reconfigure(ctx.body || {})
|
|
389
|
+
sendJsonLogged(res, 200, hotPool.status())
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// POST /hot-pool/acquire — acquire a warm session from a specific pool
|
|
394
|
+
if (ctx.method === 'POST' && subpath === 'hot-pool/acquire') {
|
|
395
|
+
const poolId = ctx.body?.poolId || 'terminal'
|
|
396
|
+
const session = hotPool.acquire(poolId)
|
|
397
|
+
if (!session) {
|
|
398
|
+
sendJsonLogged(res, 200, { acquired: false, poolId, session: null })
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
sendJsonLogged(res, 200, { acquired: true, poolId, session: { id: session.id, tmuxName: session.tmuxName, poolId: session.poolId } })
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
sendJsonLogged(res, 404, { error: 'Not found' })
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// Worktrees API — lists running worktrees/branches from server registry
|
|
409
|
+
routeHandlers.set('worktrees', async (req, res) => {
|
|
410
|
+
try {
|
|
411
|
+
const servers = listRunningServers()
|
|
412
|
+
const branches = servers.map(srv => ({
|
|
413
|
+
branch: srv.worktree,
|
|
414
|
+
folder: srv.worktree === 'main' ? '' : `branch--${srv.worktree}/`,
|
|
415
|
+
}))
|
|
416
|
+
// Always include main
|
|
417
|
+
if (!branches.some(b => b.branch === 'main')) {
|
|
418
|
+
branches.unshift({ branch: 'main', folder: '' })
|
|
419
|
+
}
|
|
420
|
+
sendJsonLogged(res, 200, branches)
|
|
421
|
+
} catch { sendJsonLogged(res, 200, []) }
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// Git user — return git config user name and GitHub login (via gh CLI)
|
|
425
|
+
routeHandlers.set('git-user', async (req, res) => {
|
|
426
|
+
try {
|
|
427
|
+
const { execSync } = await import('node:child_process')
|
|
428
|
+
const name = execSync('git config user.name', { cwd: root, encoding: 'utf8' }).trim()
|
|
429
|
+
let login = null
|
|
430
|
+
try {
|
|
431
|
+
const status = execSync('gh auth status 2>&1', { cwd: root, encoding: 'utf8' })
|
|
432
|
+
const m = status.match(/Logged in to github\.com account (\S+)/) || status.match(/Logged in to github\.com as (\S+)/)
|
|
433
|
+
if (m) login = m[1]
|
|
434
|
+
} catch { /* gh not installed or not logged in */ }
|
|
435
|
+
sendJsonLogged(res, 200, { name, login })
|
|
436
|
+
} catch { sendJsonLogged(res, 200, { name: null, login: null }) }
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
// Switch branch — proxy to storyboard server which manages worktree
|
|
440
|
+
// dev servers. The server port is derived from the devDomain.
|
|
441
|
+
routeHandlers.set('switch-branch', async (req, res, ctx) => {
|
|
442
|
+
if (ctx.method !== 'POST') {
|
|
443
|
+
sendJsonLogged(res, 405, { error: 'POST required' })
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
// Derive storyboard server port (same algorithm as server/index.js)
|
|
448
|
+
// readDevDomain() returns "{devDomain}.localhost"
|
|
449
|
+
const domain = `${config.devDomain || 'storyboard'}.localhost`
|
|
450
|
+
let h = 0
|
|
451
|
+
for (let i = 0; i < domain.length; i++) {
|
|
452
|
+
h = ((h << 5) - h + domain.charCodeAt(i)) | 0
|
|
453
|
+
}
|
|
454
|
+
const serverPort = 4100 + (Math.abs(h) % 100)
|
|
455
|
+
|
|
456
|
+
const proxyRes = await fetch(`http://localhost:${serverPort}/_storyboard/switch-branch`, {
|
|
457
|
+
method: 'POST',
|
|
458
|
+
headers: { 'Content-Type': 'application/json' },
|
|
459
|
+
body: JSON.stringify(ctx.body),
|
|
460
|
+
})
|
|
461
|
+
const data = await proxyRes.json()
|
|
462
|
+
sendJsonLogged(res, proxyRes.status, data)
|
|
463
|
+
} catch {
|
|
464
|
+
sendJsonLogged(res, 502, {
|
|
465
|
+
error: 'Storyboard server not running. Start it with: npx storyboard server',
|
|
466
|
+
})
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
// Watch toolbar.config.json for changes — trigger full reload so
|
|
471
|
+
// CoreUIBar.jsx picks up menu/mode config changes during dev
|
|
472
|
+
const toolbarConfigPath = path.resolve(
|
|
473
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
474
|
+
'../../toolbar.config.json'
|
|
475
|
+
)
|
|
476
|
+
server.watcher.add(toolbarConfigPath)
|
|
477
|
+
server.watcher.on('change', (filePath) => {
|
|
478
|
+
if (path.resolve(filePath) === toolbarConfigPath) {
|
|
479
|
+
// Invalidate the cached JSON module so Vite re-reads from disk
|
|
480
|
+
const mods = server.moduleGraph.getModulesByFile(toolbarConfigPath)
|
|
481
|
+
if (mods) {
|
|
482
|
+
for (const mod of mods) {
|
|
483
|
+
server.moduleGraph.invalidateModule(mod)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
server.ws.send({ type: 'full-reload' })
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
// Workshop client UI is now mounted by mountStoryboardCore() via the
|
|
491
|
+
// compiled UI bundle. No script injection needed.
|
|
492
|
+
|
|
493
|
+
// Plugin registry for external plugins (future use).
|
|
494
|
+
// Plugins call registerRoutes/registerClientScript in their setup().
|
|
495
|
+
// const pluginCtx = { server, root, config, registerRoutes, registerClientScript }
|
|
496
|
+
// Future: auto-discover and initialize plugins from pluginsConfig here
|
|
497
|
+
|
|
498
|
+
// Mount the /_storyboard/ middleware router
|
|
499
|
+
// Vite's dev server strips the base path from req.url for middleware,
|
|
500
|
+
// but the base-redirect plugin may redirect bare URLs first.
|
|
501
|
+
// We check both with and without base prefix.
|
|
502
|
+
server.middlewares.use(async (req, res, next) => {
|
|
503
|
+
if (!req.url) return next()
|
|
504
|
+
|
|
505
|
+
// Strip base path if present to normalize the URL
|
|
506
|
+
let url = req.url
|
|
507
|
+
const baseNoTrail = base.replace(/\/$/, '')
|
|
508
|
+
if (baseNoTrail && url.startsWith(baseNoTrail)) {
|
|
509
|
+
url = url.slice(baseNoTrail.length) || '/'
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!url.startsWith(API_PREFIX)) return next()
|
|
513
|
+
|
|
514
|
+
// Parse: /_storyboard/{prefix}/{rest}
|
|
515
|
+
const pathAfterPrefix = url.slice(API_PREFIX.length)
|
|
516
|
+
const slashIndex = pathAfterPrefix.indexOf('/')
|
|
517
|
+
const prefix = slashIndex === -1 ? pathAfterPrefix : pathAfterPrefix.slice(0, slashIndex)
|
|
518
|
+
const restPath = slashIndex === -1 ? '/' : pathAfterPrefix.slice(slashIndex)
|
|
519
|
+
|
|
520
|
+
// Attach route context for the logging sendJson wrapper
|
|
521
|
+
res.__sbLogCtx = { method: req.method, url, route: prefix, subRoute: restPath }
|
|
522
|
+
|
|
523
|
+
const handler = routeHandlers.get(prefix)
|
|
524
|
+
if (!handler) {
|
|
525
|
+
// Proxy to standalone storyboard server for unhandled prefixes
|
|
526
|
+
try {
|
|
527
|
+
const proxyReq = await import('node:http')
|
|
528
|
+
const proxyUrl = `http://localhost:4100${url}`
|
|
529
|
+
const proxy = proxyReq.default.request(proxyUrl, { method: req.method, headers: req.headers }, (proxyRes) => {
|
|
530
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers)
|
|
531
|
+
proxyRes.pipe(res)
|
|
532
|
+
})
|
|
533
|
+
proxy.on('error', () => {
|
|
534
|
+
sendJsonLogged(res, 502, { error: `Storyboard server not running. Start it with: npx storyboard server` })
|
|
535
|
+
})
|
|
536
|
+
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
|
537
|
+
req.pipe(proxy)
|
|
538
|
+
} else {
|
|
539
|
+
proxy.end()
|
|
540
|
+
}
|
|
541
|
+
} catch {
|
|
542
|
+
sendJsonLogged(res, 502, { error: 'Storyboard server not running' })
|
|
543
|
+
}
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
let body = {}
|
|
549
|
+
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH' || req.method === 'DELETE') {
|
|
550
|
+
body = await parseJsonBody(req)
|
|
551
|
+
}
|
|
552
|
+
await handler(req, res, { body, path: restPath, method: req.method, __viteWs: server.ws })
|
|
553
|
+
} catch (err) {
|
|
554
|
+
sendJsonLogged(res, 500, { error: err.message || 'Internal server error' })
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
transformIndexHtml() {
|
|
560
|
+
const tags = []
|
|
561
|
+
|
|
562
|
+
// Inject local dev flag only during dev server (not production builds)
|
|
563
|
+
if (isDev) {
|
|
564
|
+
tags.push({
|
|
565
|
+
tag: 'script',
|
|
566
|
+
children: 'window.__SB_LOCAL_DEV__=true',
|
|
567
|
+
injectTo: 'head',
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
// Inject dev domain name for branch bar display
|
|
571
|
+
if (config.devDomain) {
|
|
572
|
+
tags.push({
|
|
573
|
+
tag: 'script',
|
|
574
|
+
children: `window.__SB_DEV_DOMAIN__=${JSON.stringify(config.devDomain)}`,
|
|
575
|
+
injectTo: 'head',
|
|
576
|
+
})
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Inject per-domain branch bar color (configurable via devDomainColor)
|
|
580
|
+
if (config.devDomainColor) {
|
|
581
|
+
tags.push({
|
|
582
|
+
tag: 'script',
|
|
583
|
+
children: `window.__SB_DEV_DOMAIN_COLOR__=${JSON.stringify(config.devDomainColor)}`,
|
|
584
|
+
injectTo: 'head',
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Browser error bridge — forwards console.error/warn and uncaught
|
|
589
|
+
// exceptions to the dev server via HMR for structured o11y logging
|
|
590
|
+
tags.push({
|
|
591
|
+
tag: 'script',
|
|
592
|
+
attrs: { type: 'module' },
|
|
593
|
+
children: `
|
|
594
|
+
(function() {
|
|
595
|
+
if (!import.meta.hot) return;
|
|
596
|
+
var MAX_LEN = 2000;
|
|
597
|
+
function trunc(s) { return typeof s === 'string' && s.length > MAX_LEN ? s.slice(0, MAX_LEN) + '…' : s; }
|
|
598
|
+
function route() { return location.pathname + location.hash; }
|
|
599
|
+
function send(level, msg, extra) {
|
|
600
|
+
try { import.meta.hot.send('storyboard:client-error', Object.assign({ level: level, message: trunc(msg), route: route() }, extra || {})); } catch {}
|
|
601
|
+
}
|
|
602
|
+
// Patch console.error and console.warn
|
|
603
|
+
['error', 'warn'].forEach(function(level) {
|
|
604
|
+
var orig = console[level];
|
|
605
|
+
console[level] = function() {
|
|
606
|
+
orig.apply(console, arguments);
|
|
607
|
+
var parts = [];
|
|
608
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
609
|
+
try { parts.push(typeof arguments[i] === 'string' ? arguments[i] : JSON.stringify(arguments[i])); } catch { parts.push(String(arguments[i])); }
|
|
610
|
+
}
|
|
611
|
+
send(level, parts.join(' '));
|
|
612
|
+
};
|
|
613
|
+
});
|
|
614
|
+
// Uncaught errors
|
|
615
|
+
window.addEventListener('error', function(e) {
|
|
616
|
+
send('error', e.message || 'Uncaught error', { url: e.filename, line: e.lineno, col: e.colno, stack: trunc(e.error && e.error.stack) });
|
|
617
|
+
});
|
|
618
|
+
// Unhandled promise rejections
|
|
619
|
+
window.addEventListener('unhandledrejection', function(e) {
|
|
620
|
+
var msg = e.reason ? (e.reason.message || String(e.reason)) : 'Unhandled rejection';
|
|
621
|
+
send('error', msg, { stack: trunc(e.reason && e.reason.stack) });
|
|
622
|
+
});
|
|
623
|
+
})();
|
|
624
|
+
`.trim(),
|
|
625
|
+
injectTo: 'head',
|
|
626
|
+
})
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Inject base path so the inspector UI can resolve static assets
|
|
630
|
+
// (e.g. inspector.json) when deployed under a subpath
|
|
631
|
+
tags.push({
|
|
632
|
+
tag: 'script',
|
|
633
|
+
children: `window.__STORYBOARD_BASE_PATH__=${JSON.stringify(base)}`,
|
|
634
|
+
injectTo: 'head',
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
for (const src of clientScripts) {
|
|
638
|
+
tags.push({
|
|
639
|
+
tag: 'script',
|
|
640
|
+
attrs: { type: 'module', src: base + src.replace(/^\//, '') },
|
|
641
|
+
injectTo: 'body',
|
|
642
|
+
})
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return tags
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
// Build-time: emit a static JSON with source files so the inspector
|
|
649
|
+
// works in deployed environments without the dev middleware.
|
|
650
|
+
async generateBundle() {
|
|
651
|
+
const srcDir = path.join(root, 'src')
|
|
652
|
+
const prototypesDir = path.join(root, 'src', 'prototypes')
|
|
653
|
+
|
|
654
|
+
// Collect file lists (prototypes for the files index, all src/ for sources)
|
|
655
|
+
const [prototypeFiles, allSrcFiles] = await Promise.all([
|
|
656
|
+
collectFiles(prototypesDir, root),
|
|
657
|
+
collectFiles(srcDir, root),
|
|
658
|
+
])
|
|
659
|
+
|
|
660
|
+
// Read all source file contents
|
|
661
|
+
const sources = {}
|
|
662
|
+
await Promise.all(
|
|
663
|
+
allSrcFiles.map(async (relPath) => {
|
|
664
|
+
try {
|
|
665
|
+
sources[relPath] = await fs.promises.readFile(
|
|
666
|
+
path.join(root, relPath),
|
|
667
|
+
'utf-8'
|
|
668
|
+
)
|
|
669
|
+
} catch { /* skip unreadable files */ }
|
|
670
|
+
})
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
// Resolve repo info (same logic as docs-handler)
|
|
674
|
+
let repo = null
|
|
675
|
+
try {
|
|
676
|
+
const { execSync } = await import('node:child_process')
|
|
677
|
+
const remote = execSync('git remote get-url origin', {
|
|
678
|
+
cwd: root,
|
|
679
|
+
encoding: 'utf-8',
|
|
680
|
+
}).trim()
|
|
681
|
+
const match = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/)
|
|
682
|
+
if (match) repo = { owner: match[1], name: match[2] }
|
|
683
|
+
} catch { /* no git or no remote */ }
|
|
684
|
+
|
|
685
|
+
if (!repo) {
|
|
686
|
+
const configPath = path.join(root, 'storyboard.config.json')
|
|
687
|
+
try {
|
|
688
|
+
const raw = await fs.promises.readFile(configPath, 'utf-8')
|
|
689
|
+
const cfg = JSON.parse(raw)
|
|
690
|
+
if (cfg.repository?.owner && cfg.repository?.name) {
|
|
691
|
+
repo = { owner: cfg.repository.owner, name: cfg.repository.name }
|
|
692
|
+
}
|
|
693
|
+
} catch { /* config not available */ }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
this.emitFile({
|
|
697
|
+
type: 'asset',
|
|
698
|
+
fileName: '_storyboard/inspector.json',
|
|
699
|
+
source: JSON.stringify({
|
|
700
|
+
files: prototypeFiles.sort(),
|
|
701
|
+
sources,
|
|
702
|
+
repo,
|
|
703
|
+
}),
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
// Emit README as static JSON so the docs panel works in deployed builds.
|
|
707
|
+
// Dev server serves this dynamically; production needs the static file.
|
|
708
|
+
let readmeContent = null
|
|
709
|
+
for (const candidate of ['README.md', 'readme.md', 'Readme.md']) {
|
|
710
|
+
try {
|
|
711
|
+
readmeContent = await fs.promises.readFile(path.join(root, candidate), 'utf-8')
|
|
712
|
+
break
|
|
713
|
+
} catch { /* try next */ }
|
|
714
|
+
}
|
|
715
|
+
if (readmeContent) {
|
|
716
|
+
this.emitFile({
|
|
717
|
+
type: 'asset',
|
|
718
|
+
fileName: '_storyboard/docs/readme',
|
|
719
|
+
source: JSON.stringify({ content: readmeContent, path: 'README.md' }),
|
|
720
|
+
})
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Emit repo info so the docs panel GitHub link works in deployed builds.
|
|
724
|
+
if (repo) {
|
|
725
|
+
this.emitFile({
|
|
726
|
+
type: 'asset',
|
|
727
|
+
fileName: '_storyboard/docs/repo',
|
|
728
|
+
source: JSON.stringify(repo),
|
|
729
|
+
})
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Emit story sources JSON so the "show code" widget action works in
|
|
733
|
+
// deployed builds. In dev, StoryWidget uses Vite's ?raw import; in prod
|
|
734
|
+
// it fetches this static JSON instead.
|
|
735
|
+
const storySources = {}
|
|
736
|
+
const storyExts = ['.story.jsx', '.story.tsx', '.story.js', '.story.ts']
|
|
737
|
+
for (const relPath of allSrcFiles) {
|
|
738
|
+
if (storyExts.some(ext => relPath.endsWith(ext))) {
|
|
739
|
+
storySources[relPath] = sources[relPath] || ''
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (Object.keys(storySources).length > 0) {
|
|
743
|
+
this.emitFile({
|
|
744
|
+
type: 'asset',
|
|
745
|
+
fileName: '_storyboard/stories/sources.json',
|
|
746
|
+
source: JSON.stringify(storySources),
|
|
747
|
+
})
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Emit canvas images so they're available in deployed (static) builds.
|
|
751
|
+
// Dev server serves these dynamically; production needs the static files.
|
|
752
|
+
// Private images (prefixed with ~) are excluded from the build.
|
|
753
|
+
for (const dir of [
|
|
754
|
+
path.join(root, 'assets', 'canvas', 'images'),
|
|
755
|
+
path.join(root, 'assets', 'canvas', 'snapshots'),
|
|
756
|
+
]) {
|
|
757
|
+
try {
|
|
758
|
+
const imageFiles = await fs.promises.readdir(dir)
|
|
759
|
+
const subdir = dir.endsWith('snapshots') ? 'snapshots' : 'images'
|
|
760
|
+
for (const file of imageFiles) {
|
|
761
|
+
if (file.startsWith('~') || file.startsWith('.')) continue
|
|
762
|
+
try {
|
|
763
|
+
const data = await fs.promises.readFile(path.join(dir, file))
|
|
764
|
+
this.emitFile({
|
|
765
|
+
type: 'asset',
|
|
766
|
+
fileName: `_storyboard/canvas/${subdir}/${file}`,
|
|
767
|
+
source: data,
|
|
768
|
+
})
|
|
769
|
+
} catch { /* skip unreadable files */ }
|
|
770
|
+
}
|
|
771
|
+
} catch { /* directory doesn't exist */ }
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// GitHub Pages uses Jekyll which ignores _-prefixed directories.
|
|
775
|
+
// Emit .nojekyll to ensure _storyboard/ is served.
|
|
776
|
+
this.emitFile({
|
|
777
|
+
type: 'asset',
|
|
778
|
+
fileName: '.nojekyll',
|
|
779
|
+
source: '',
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
// Emit CNAME for GitHub Pages custom domain if configured.
|
|
783
|
+
// Without this, deploy scripts that clean the gh-pages root will
|
|
784
|
+
// delete the CNAME on every push, causing intermittent 404s.
|
|
785
|
+
const customDomain = (config.customDomain || '').trim()
|
|
786
|
+
if (customDomain && !customDomain.includes('/') && !customDomain.includes(':') && !customDomain.includes(' ')) {
|
|
787
|
+
this.emitFile({
|
|
788
|
+
type: 'asset',
|
|
789
|
+
fileName: 'CNAME',
|
|
790
|
+
source: customDomain + '\n',
|
|
791
|
+
})
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
export { sendJson }
|