@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,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas write guard — coordinates the canvas server with Vite's file watcher
|
|
3
|
+
* to prevent duplicate HMR events.
|
|
4
|
+
*
|
|
5
|
+
* When the canvas server writes to a .canvas.jsonl file, it immediately pushes
|
|
6
|
+
* an HMR event via pushCanvasUpdate(). Vite's file watcher also detects the
|
|
7
|
+
* change ~100-500ms later and the data plugin sends a second HMR event. This
|
|
8
|
+
* duplicate event can cause visible rollbacks when the client has made new edits
|
|
9
|
+
* in the intervening window.
|
|
10
|
+
*
|
|
11
|
+
* The guard tracks files currently being written by the canvas server. The data
|
|
12
|
+
* plugin checks this before sending watcher-triggered HMR events and skips them
|
|
13
|
+
* if the server has already pushed.
|
|
14
|
+
*
|
|
15
|
+
* Uses globalThis with a Symbol key to guarantee the same Map instance is shared
|
|
16
|
+
* across all import paths (e.g., relative imports from server.js vs package
|
|
17
|
+
* imports from data-plugin.js).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const KEY = Symbol.for('sb:canvasWriteGuard')
|
|
21
|
+
if (!globalThis[KEY]) globalThis[KEY] = new Map()
|
|
22
|
+
|
|
23
|
+
/** @type {Map<string, number>} filePath → active write count */
|
|
24
|
+
const guard = globalThis[KEY]
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Mark a file as being written by the canvas server.
|
|
28
|
+
* Call before appendEvent / writeFileSync.
|
|
29
|
+
*/
|
|
30
|
+
export function markCanvasWrite(filePath) {
|
|
31
|
+
guard.set(filePath, (guard.get(filePath) || 0) + 1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Unmark a file after the watcher has had time to fire.
|
|
36
|
+
* Call via setTimeout after the write + push completes.
|
|
37
|
+
*/
|
|
38
|
+
export function unmarkCanvasWrite(filePath) {
|
|
39
|
+
const count = (guard.get(filePath) || 1) - 1
|
|
40
|
+
if (count <= 0) guard.delete(filePath)
|
|
41
|
+
else guard.set(filePath, count)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a file is currently being written by the canvas server.
|
|
46
|
+
* The data plugin uses this to skip duplicate watcher-triggered HMR events.
|
|
47
|
+
*/
|
|
48
|
+
export function isCanvasWriteInFlight(filePath) {
|
|
49
|
+
return guard.has(filePath)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Legacy export for backward compatibility
|
|
53
|
+
export const canvasWritesInFlight = guard
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* storyboard agent signal — signal agent status to the canvas server.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx storyboard agent signal --widget <id> --canvas <canvasId> --status done|error|running [--message "..."]
|
|
7
|
+
*
|
|
8
|
+
* Environment variables (auto-set by terminal server):
|
|
9
|
+
* STORYBOARD_WIDGET_ID — widget ID (fallback if --widget not provided)
|
|
10
|
+
* STORYBOARD_CANVAS_ID — canvas ID (fallback if --canvas not provided)
|
|
11
|
+
* STORYBOARD_SERVER_URL — server base URL
|
|
12
|
+
* STORYBOARD_BRANCH — current branch
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { parseFlags } from './flags.js'
|
|
16
|
+
import { dim, bold, cyan, yellow } from './intro.js'
|
|
17
|
+
|
|
18
|
+
const subcommand = process.argv[3]
|
|
19
|
+
|
|
20
|
+
if (subcommand === 'signal') {
|
|
21
|
+
const flagSchema = {
|
|
22
|
+
widget: { type: 'string', description: 'Widget ID' },
|
|
23
|
+
canvas: { type: 'string', description: 'Canvas ID' },
|
|
24
|
+
status: { type: 'string', required: true, description: 'Status: done, error, or running' },
|
|
25
|
+
message: { type: 'string', description: 'Optional status message' },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { flags } = parseFlags(process.argv.slice(4), flagSchema)
|
|
29
|
+
|
|
30
|
+
const widgetId = flags.widget || process.env.STORYBOARD_WIDGET_ID
|
|
31
|
+
const canvasId = flags.canvas || process.env.STORYBOARD_CANVAS_ID
|
|
32
|
+
const status = flags.status
|
|
33
|
+
const message = flags.message || null
|
|
34
|
+
const serverUrl = process.env.STORYBOARD_SERVER_URL || 'http://localhost:1234'
|
|
35
|
+
const branch = process.env.STORYBOARD_BRANCH || 'unknown'
|
|
36
|
+
|
|
37
|
+
if (!widgetId || !canvasId || !status) {
|
|
38
|
+
console.error(`${bold('Usage:')} npx storyboard agent signal --status done|error|running`)
|
|
39
|
+
console.error(`${dim('Widget and canvas IDs are read from environment if not provided.')}`)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const validStatuses = ['done', 'error', 'running']
|
|
44
|
+
if (!validStatuses.includes(status)) {
|
|
45
|
+
console.error(`${bold('Error:')} Status must be one of: ${validStatuses.join(', ')}`)
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const url = `${serverUrl}/_storyboard/canvas/agent/signal`
|
|
51
|
+
const res = await fetch(url, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ widgetId, canvasId, branch, status, message }),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (res.ok) {
|
|
58
|
+
console.log(`${cyan('✓')} Agent status: ${bold(status)}${message ? ` — ${message}` : ''}`)
|
|
59
|
+
} else {
|
|
60
|
+
const data = await res.json().catch(() => ({}))
|
|
61
|
+
console.error(`${yellow('⚠')} Server returned ${res.status}: ${data.error || 'unknown error'}`)
|
|
62
|
+
// Fallback: write directly to terminal config
|
|
63
|
+
await fallbackWrite({ branch, canvasId, widgetId, status, message })
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Server not reachable — write directly to terminal config file
|
|
67
|
+
await fallbackWrite({ branch, canvasId, widgetId, status, message })
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
console.error(`${bold('Usage:')} npx storyboard agent <signal>`)
|
|
71
|
+
console.error(`${dim('Subcommands: signal')}`)
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function fallbackWrite({ branch, canvasId, widgetId, status, message }) {
|
|
76
|
+
try {
|
|
77
|
+
const { updateAgentStatus, initTerminalConfig } = await import('../canvas/terminal-config.js')
|
|
78
|
+
initTerminalConfig(process.cwd())
|
|
79
|
+
updateAgentStatus({ branch, canvasId, widgetId, status, message })
|
|
80
|
+
console.log(`${cyan('✓')} Agent status written to config file (server offline): ${bold(status)}`)
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(`Failed to write agent status: ${err.message}`)
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storyboard branch — Interactive guide to switch to a branch worktree.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic flow (no AI):
|
|
5
|
+
* 1. Ask which branch to work on (or accept --worktree flag)
|
|
6
|
+
* 2. If existing worktree:
|
|
7
|
+
* a. Stash uncommitted work in source (named stash)
|
|
8
|
+
* b. Ensure target is on the correct branch
|
|
9
|
+
* c. Apply source stash in target
|
|
10
|
+
* 3. If new worktree:
|
|
11
|
+
* a. Stash uncommitted work in source
|
|
12
|
+
* b. Create worktree (git worktree add + npm install)
|
|
13
|
+
* c. Pull --rebase from origin
|
|
14
|
+
* d. Apply source stash in target
|
|
15
|
+
* 4. Confirm to user
|
|
16
|
+
*
|
|
17
|
+
* Also available as post-setup prompt in setup.js.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* npx storyboard branch # interactive
|
|
21
|
+
* npx storyboard branch <name> # positional, skip prompt
|
|
22
|
+
* npx storyboard branch --worktree=<name> # non-interactive flag
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import * as p from '@clack/prompts'
|
|
26
|
+
import { execFileSync } from 'child_process'
|
|
27
|
+
import { existsSync } from 'fs'
|
|
28
|
+
import { resolve } from 'path'
|
|
29
|
+
import { repoRoot, worktreeDir, listWorktrees, getPort, detectWorktreeName } from '../worktree/port.js'
|
|
30
|
+
import { hasUncommittedChanges, localBranchExists } from './dev-helpers.js'
|
|
31
|
+
import { parseFlags } from './flags.js'
|
|
32
|
+
import { dim, green, bold, cyan } from './intro.js'
|
|
33
|
+
|
|
34
|
+
const flagSchema = {
|
|
35
|
+
worktree: { type: 'string', description: 'Target worktree/branch name (non-interactive)' },
|
|
36
|
+
cd: { type: 'boolean', default: false, description: 'Output shell-evaluable cd command (for eval)' },
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Check if a remote branch exists on origin. */
|
|
40
|
+
function remoteBranchExists(name, cwd) {
|
|
41
|
+
try {
|
|
42
|
+
const result = execFileSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', name], { cwd, encoding: 'utf8' })
|
|
43
|
+
return result.trim().length > 0
|
|
44
|
+
} catch {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Get the current branch name in a given directory. */
|
|
50
|
+
function currentBranch(cwd) {
|
|
51
|
+
try {
|
|
52
|
+
return execFileSync('git', ['branch', '--show-current'], { cwd, encoding: 'utf8' }).trim()
|
|
53
|
+
} catch {
|
|
54
|
+
return 'unknown'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Validate a branch name for git. */
|
|
59
|
+
function isValidBranchName(name) {
|
|
60
|
+
if (!name || name.trim().length === 0) return 'Branch name cannot be empty'
|
|
61
|
+
const n = name.trim()
|
|
62
|
+
if (/\s/.test(n)) return 'Branch name cannot contain spaces'
|
|
63
|
+
if (/\.\./.test(n)) return 'Branch name cannot contain ".."'
|
|
64
|
+
if (/[~^:\\]/.test(n)) return 'Branch name cannot contain ~, ^, :, or \\'
|
|
65
|
+
if (n.startsWith('-')) return 'Branch name cannot start with "-"'
|
|
66
|
+
if (n.endsWith('.lock')) return 'Branch name cannot end with ".lock"'
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Build a timestamped stash message. */
|
|
71
|
+
function stashMessage(from, to) {
|
|
72
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
|
73
|
+
return `from-${from}-to-${to}-${ts}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Stash uncommitted changes (including untracked files) and return the stash ref SHA.
|
|
78
|
+
* Returns null if nothing was stashed.
|
|
79
|
+
*/
|
|
80
|
+
function stashChanges(cwd, message) {
|
|
81
|
+
if (!hasUncommittedChanges(cwd)) return null
|
|
82
|
+
|
|
83
|
+
p.log.step('Stashing uncommitted work…')
|
|
84
|
+
try {
|
|
85
|
+
execFileSync('git', ['stash', 'push', '-u', '-m', message], { cwd, stdio: 'pipe' })
|
|
86
|
+
// Capture the exact stash SHA so we can apply it by ref later
|
|
87
|
+
const sha = execFileSync('git', ['stash', 'list', '--format=%H', '-1'], { cwd, encoding: 'utf8' }).trim()
|
|
88
|
+
p.log.success(`Work stashed: ${dim(message)}`)
|
|
89
|
+
return sha
|
|
90
|
+
} catch {
|
|
91
|
+
p.log.warning('Could not stash changes — proceeding anyway')
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Apply a specific stash by SHA in the given directory.
|
|
98
|
+
* Uses apply (not pop) so the backup remains in the stash list.
|
|
99
|
+
*/
|
|
100
|
+
function applyStash(cwd, stashSha) {
|
|
101
|
+
try {
|
|
102
|
+
execFileSync('git', ['stash', 'apply', stashSha], { cwd, stdio: 'pipe' })
|
|
103
|
+
p.log.success('Previous work applied to this branch')
|
|
104
|
+
return true
|
|
105
|
+
} catch {
|
|
106
|
+
p.log.warning('Stash apply had conflicts — resolve them manually')
|
|
107
|
+
p.log.info(` Your work is safe in ${dim('git stash list')}`)
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Core logic for switching to an EXISTING worktree.
|
|
114
|
+
*
|
|
115
|
+
* 1. Stash source changes (if any)
|
|
116
|
+
* 2. Ensure target worktree is on the expected branch
|
|
117
|
+
* 3. Apply source stash in target
|
|
118
|
+
*/
|
|
119
|
+
function switchToExistingWorktree(targetBranch, { sourceDir, fromBranch }) {
|
|
120
|
+
const targetDir = worktreeDir(targetBranch)
|
|
121
|
+
|
|
122
|
+
// 1. Stash source changes
|
|
123
|
+
const sourceStashSha = stashChanges(sourceDir, stashMessage(fromBranch, targetBranch))
|
|
124
|
+
|
|
125
|
+
// 2. Ensure target is on the correct branch
|
|
126
|
+
const targetCurrentBranch = currentBranch(targetDir)
|
|
127
|
+
|
|
128
|
+
if (targetCurrentBranch !== targetBranch) {
|
|
129
|
+
// Target worktree is on a different branch — check if it's clean
|
|
130
|
+
if (hasUncommittedChanges(targetDir)) {
|
|
131
|
+
p.log.error(
|
|
132
|
+
`Worktree ${bold(targetBranch)} is on branch ${bold(targetCurrentBranch)} with uncommitted changes.`
|
|
133
|
+
)
|
|
134
|
+
p.log.info(` Clean up the worktree first:`)
|
|
135
|
+
p.log.info(` ${green('cd')} ${dim(`worktrees/${targetBranch}`)}`)
|
|
136
|
+
p.log.info(` ${dim('git stash')} ${dim('or')} ${dim('git commit')}`)
|
|
137
|
+
p.log.info(` ${dim(`git checkout ${targetBranch}`)}`)
|
|
138
|
+
if (sourceStashSha) {
|
|
139
|
+
p.log.info(`\n Your source stash is safe — apply it later with:`)
|
|
140
|
+
p.log.info(` ${dim(`git stash apply ${sourceStashSha.slice(0, 8)}`)}`)
|
|
141
|
+
}
|
|
142
|
+
p.outro('')
|
|
143
|
+
process.exit(1)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Clean worktree on wrong branch — switch it
|
|
147
|
+
p.log.step(`Switching worktree from ${bold(targetCurrentBranch)} to ${bold(targetBranch)}…`)
|
|
148
|
+
try {
|
|
149
|
+
execFileSync('git', ['checkout', targetBranch], { cwd: targetDir, stdio: 'pipe' })
|
|
150
|
+
p.log.success(`Now on branch ${bold(targetBranch)}`)
|
|
151
|
+
} catch (err) {
|
|
152
|
+
p.log.error(`Could not switch branch: ${err.message || 'git checkout failed'}`)
|
|
153
|
+
if (sourceStashSha) {
|
|
154
|
+
p.log.info(` Your source stash is safe: ${dim(`git stash apply ${sourceStashSha.slice(0, 8)}`)}`)
|
|
155
|
+
}
|
|
156
|
+
p.outro('')
|
|
157
|
+
process.exit(1)
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
p.log.success(`Worktree ${bold(targetBranch)} is on the correct branch`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 3. Apply source stash in target
|
|
164
|
+
if (sourceStashSha) {
|
|
165
|
+
if (hasUncommittedChanges(targetDir)) {
|
|
166
|
+
p.log.warning(`Target worktree has uncommitted changes — skipping stash apply`)
|
|
167
|
+
p.log.info(` Apply your stash manually: ${dim(`git stash apply ${sourceStashSha.slice(0, 8)}`)}`)
|
|
168
|
+
} else {
|
|
169
|
+
applyStash(targetDir, sourceStashSha)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 4. Summary
|
|
174
|
+
const lines = [
|
|
175
|
+
` Worktree ready: ${green(`worktrees/${targetBranch}`)}`,
|
|
176
|
+
]
|
|
177
|
+
if (sourceStashSha) {
|
|
178
|
+
lines.push(` Your uncommitted work has been safely moved`)
|
|
179
|
+
}
|
|
180
|
+
lines.push('')
|
|
181
|
+
lines.push(` ${green('cd')} ${dim(`worktrees/${targetBranch}`)}`)
|
|
182
|
+
lines.push(` ${green('npx storyboard dev')} ${dim('to start developing')}`)
|
|
183
|
+
lines.push('')
|
|
184
|
+
lines.push(` ${dim('Tip: auto-cd with')} ${green('eval "$(npx sb branch --cd)"')}`)
|
|
185
|
+
|
|
186
|
+
p.note(lines.join('\n'), `Branch ${bold(targetBranch)} is ready`)
|
|
187
|
+
p.outro('')
|
|
188
|
+
return targetDir
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Core logic for creating a NEW worktree.
|
|
193
|
+
*
|
|
194
|
+
* 1. Stash source changes
|
|
195
|
+
* 2. Resolve branch (local/remote/new)
|
|
196
|
+
* 3. Create worktree + npm install
|
|
197
|
+
* 4. Pull --rebase
|
|
198
|
+
* 5. Apply source stash
|
|
199
|
+
*/
|
|
200
|
+
function createNewWorktree(targetBranch, { sourceDir, fromBranch, root }) {
|
|
201
|
+
// 1. Stash source changes
|
|
202
|
+
const sourceStashSha = stashChanges(sourceDir, stashMessage(fromBranch, targetBranch))
|
|
203
|
+
|
|
204
|
+
// 2. Resolve branch
|
|
205
|
+
const hasLocal = localBranchExists(targetBranch, root)
|
|
206
|
+
const hasRemote = !hasLocal && remoteBranchExists(targetBranch, root)
|
|
207
|
+
const isNew = !hasLocal && !hasRemote
|
|
208
|
+
|
|
209
|
+
if (isNew) {
|
|
210
|
+
p.log.step(`Creating new branch ${bold(targetBranch)} from ${bold(fromBranch)}`)
|
|
211
|
+
} else if (hasRemote) {
|
|
212
|
+
p.log.step(`Fetching ${bold(targetBranch)} from origin…`)
|
|
213
|
+
try {
|
|
214
|
+
execFileSync('git', ['fetch', 'origin', targetBranch], { cwd: root, stdio: 'pipe' })
|
|
215
|
+
try {
|
|
216
|
+
execFileSync('git', ['branch', targetBranch, `origin/${targetBranch}`], { cwd: root, stdio: 'pipe' })
|
|
217
|
+
} catch { /* may already exist after fetch */ }
|
|
218
|
+
} catch {
|
|
219
|
+
p.log.warning('Could not fetch from origin — using local state')
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
p.log.step(`Using existing branch ${bold(targetBranch)}`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 3. Create the worktree
|
|
226
|
+
const targetDir = resolve(root, 'worktrees', targetBranch)
|
|
227
|
+
const spin = p.spinner()
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// For new branches, use current branch as start-point (not main's HEAD)
|
|
231
|
+
const gitArgs = isNew
|
|
232
|
+
? ['worktree', 'add', targetDir, '-b', targetBranch, fromBranch]
|
|
233
|
+
: ['worktree', 'add', targetDir, targetBranch]
|
|
234
|
+
|
|
235
|
+
spin.start(`Creating worktree worktrees/${targetBranch}`)
|
|
236
|
+
execFileSync('git', gitArgs, { cwd: root, stdio: 'pipe' })
|
|
237
|
+
spin.stop(`Worktree created: worktrees/${targetBranch}`)
|
|
238
|
+
} catch (err) {
|
|
239
|
+
spin.stop('Failed to create worktree')
|
|
240
|
+
p.log.error(err.message || 'git worktree add failed')
|
|
241
|
+
process.exit(1)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Install dependencies
|
|
245
|
+
try {
|
|
246
|
+
spin.start('Installing dependencies…')
|
|
247
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
248
|
+
execFileSync(npmBin, ['install'], { cwd: targetDir, stdio: 'pipe' })
|
|
249
|
+
spin.stop('Dependencies installed')
|
|
250
|
+
} catch {
|
|
251
|
+
spin.stop('npm install failed — you may need to run it manually')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Assign a dev server port
|
|
255
|
+
getPort(targetBranch)
|
|
256
|
+
|
|
257
|
+
// 4. Pull --rebase from origin
|
|
258
|
+
if (!isNew) {
|
|
259
|
+
try {
|
|
260
|
+
spin.start('Pulling latest changes…')
|
|
261
|
+
execFileSync('git', ['pull', '--rebase', 'origin', targetBranch], { cwd: targetDir, stdio: 'pipe' })
|
|
262
|
+
spin.stop('Up to date with origin')
|
|
263
|
+
} catch {
|
|
264
|
+
spin.stop(dim('No remote changes (or origin not available)'))
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 5. Apply source stash
|
|
269
|
+
if (sourceStashSha) {
|
|
270
|
+
applyStash(targetDir, sourceStashSha)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 6. Summary
|
|
274
|
+
const lines = [
|
|
275
|
+
` Your branch is set up as a worktree in ${green(`worktrees/${targetBranch}`)}`,
|
|
276
|
+
]
|
|
277
|
+
if (sourceStashSha) {
|
|
278
|
+
lines.push(` Your uncommitted work has been safely moved`)
|
|
279
|
+
}
|
|
280
|
+
lines.push('')
|
|
281
|
+
lines.push(` ${green('cd')} ${dim(`worktrees/${targetBranch}`)}`)
|
|
282
|
+
lines.push(` ${green('npx storyboard dev')} ${dim('to start developing')}`)
|
|
283
|
+
lines.push('')
|
|
284
|
+
lines.push(` ${dim('Tip: auto-cd with')} ${green('eval "$(npx sb branch --cd)"')}`)
|
|
285
|
+
|
|
286
|
+
p.note(lines.join('\n'), `Branch ${bold(targetBranch)} is ready`)
|
|
287
|
+
p.outro('')
|
|
288
|
+
return targetDir
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ─── Main ───
|
|
292
|
+
|
|
293
|
+
export async function runBranchGuide(branchArg) {
|
|
294
|
+
const root = repoRoot()
|
|
295
|
+
const existing = listWorktrees()
|
|
296
|
+
const fromWorktree = detectWorktreeName()
|
|
297
|
+
const sourceDir = fromWorktree === 'main' ? root : worktreeDir(fromWorktree)
|
|
298
|
+
const fromBranch = currentBranch(sourceDir)
|
|
299
|
+
|
|
300
|
+
// 1. Get branch name — select from existing or type a new one
|
|
301
|
+
let targetBranch = branchArg?.trim()
|
|
302
|
+
|
|
303
|
+
if (!targetBranch) {
|
|
304
|
+
if (existing.length > 0) {
|
|
305
|
+
// Build select options from existing worktrees + "new branch" option
|
|
306
|
+
const NEW_BRANCH = Symbol('new')
|
|
307
|
+
const options = [
|
|
308
|
+
...existing.map(name => ({ value: name, label: name })),
|
|
309
|
+
{ value: NEW_BRANCH, label: dim('Create a new branch…') },
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
const selected = await p.select({
|
|
313
|
+
message: 'Which branch do you want to work on?',
|
|
314
|
+
options,
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
if (p.isCancel(selected)) {
|
|
318
|
+
p.cancel('Cancelled')
|
|
319
|
+
process.exit(0)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (selected === NEW_BRANCH) {
|
|
323
|
+
const newName = await p.text({
|
|
324
|
+
message: 'New branch name:',
|
|
325
|
+
placeholder: 'e.g. 4.3.0--my-feature',
|
|
326
|
+
validate: isValidBranchName,
|
|
327
|
+
})
|
|
328
|
+
if (p.isCancel(newName)) {
|
|
329
|
+
p.cancel('Cancelled')
|
|
330
|
+
process.exit(0)
|
|
331
|
+
}
|
|
332
|
+
targetBranch = newName.trim()
|
|
333
|
+
} else {
|
|
334
|
+
targetBranch = selected
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
// No existing worktrees — just ask for a name
|
|
338
|
+
const result = await p.text({
|
|
339
|
+
message: 'Branch name for new worktree:',
|
|
340
|
+
placeholder: 'e.g. 4.3.0--my-feature',
|
|
341
|
+
validate: isValidBranchName,
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
if (p.isCancel(result)) {
|
|
345
|
+
p.cancel('Cancelled')
|
|
346
|
+
process.exit(0)
|
|
347
|
+
}
|
|
348
|
+
targetBranch = result.trim()
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 2. Show equivalent non-interactive command (when user used TUI to select)
|
|
353
|
+
if (!branchArg) {
|
|
354
|
+
p.log.info(`${dim('Non-interactive:')} ${green(`npx sb branch --worktree=${targetBranch}`)}`)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 3. Route to existing or new worktree flow
|
|
358
|
+
const wtDir = worktreeDir(targetBranch)
|
|
359
|
+
if (existsSync(resolve(wtDir, '.git'))) {
|
|
360
|
+
return switchToExistingWorktree(targetBranch, { sourceDir, fromBranch })
|
|
361
|
+
} else {
|
|
362
|
+
return createNewWorktree(targetBranch, { sourceDir, fromBranch, root })
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ─── Direct invocation ───
|
|
367
|
+
|
|
368
|
+
const { flags, positional } = parseFlags(process.argv.slice(3), flagSchema)
|
|
369
|
+
const branchArg = flags.worktree || positional[0] || undefined
|
|
370
|
+
|
|
371
|
+
// When --cd is set, redirect all TUI output (Clack) to stderr so that
|
|
372
|
+
// stdout contains only the shell-evaluable `cd <path>` command.
|
|
373
|
+
// Usage: eval "$(npx sb branch --worktree=<name> --cd)"
|
|
374
|
+
const realStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
375
|
+
if (flags.cd) {
|
|
376
|
+
process.stdout.write = (chunk, encoding, callback) =>
|
|
377
|
+
process.stderr.write(chunk, encoding, callback)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
p.intro('storyboard branch')
|
|
381
|
+
runBranchGuide(branchArg).then((targetDir) => {
|
|
382
|
+
if (flags.cd && targetDir) {
|
|
383
|
+
process.stdout.write = realStdoutWrite
|
|
384
|
+
realStdoutWrite(`cd ${JSON.stringify(targetDir)}\n`)
|
|
385
|
+
}
|
|
386
|
+
})
|