@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,714 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autosync Server — automatic commit + push watcher.
|
|
3
|
+
*
|
|
4
|
+
* Dev-server middleware that provides git automation:
|
|
5
|
+
* - List branches (excluding main/master)
|
|
6
|
+
* - Enable/disable autosync per scope (canvas/prototype)
|
|
7
|
+
* - Direct commit + push on the current branch (scoped files only)
|
|
8
|
+
* - Push watcher: every 30s runs enabled scopes in relay sequence
|
|
9
|
+
* - Persists state to .storyboard/autosync.json to survive server restarts
|
|
10
|
+
* - Pauses on branch change, resumes when user returns to target branch
|
|
11
|
+
*
|
|
12
|
+
* Routes (mounted at /_storyboard/autosync/):
|
|
13
|
+
* GET /branches — list local git branches (excludes main/master)
|
|
14
|
+
* GET /status — current state (branch, enabled scopes, last sync/errors)
|
|
15
|
+
* POST /enable — enable autosync for a scope on a branch
|
|
16
|
+
* POST /disable — disable autosync for a scope (or all scopes)
|
|
17
|
+
* POST /sync — trigger a single sync cycle manually
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execFileSync } from 'node:child_process'
|
|
21
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, renameSync, unlinkSync } from 'node:fs'
|
|
22
|
+
import { join, resolve } from 'node:path'
|
|
23
|
+
|
|
24
|
+
// ── Module-level watcher state (singleton, survives page reloads) ──
|
|
25
|
+
|
|
26
|
+
let schedulerInterval = null
|
|
27
|
+
let schedulerTimeout = null
|
|
28
|
+
let targetBranch = null
|
|
29
|
+
let originalBranch = null
|
|
30
|
+
let lastSyncTime = null
|
|
31
|
+
let lastError = null
|
|
32
|
+
let syncing = false
|
|
33
|
+
let syncingScope = null
|
|
34
|
+
let pausedOnBranchChange = false
|
|
35
|
+
let previousActiveBranch = null
|
|
36
|
+
|
|
37
|
+
let enabledScopes = { canvas: false, prototype: false }
|
|
38
|
+
let lastSyncByScope = { canvas: null, prototype: null }
|
|
39
|
+
let lastErrorByScope = { canvas: null, prototype: null }
|
|
40
|
+
|
|
41
|
+
const SYNC_INTERVAL_MS = 30_000
|
|
42
|
+
const PUSH_RETRY_LIMIT = 3
|
|
43
|
+
const SCOPE_ORDER = ['canvas', 'prototype']
|
|
44
|
+
const AUTOSYNC_SCOPES = new Set(SCOPE_ORDER)
|
|
45
|
+
|
|
46
|
+
// Branch names must match git ref format — alphanumeric, hyphens, dots, slashes
|
|
47
|
+
const BRANCH_NAME_RE = /^[\w][\w.\-/]*$/
|
|
48
|
+
|
|
49
|
+
// ── Persistence (.storyboard/autosync.json) ──
|
|
50
|
+
|
|
51
|
+
const PERSIST_DIR = '.storyboard'
|
|
52
|
+
const PERSIST_FILE = 'autosync.json'
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load persisted autosync state from disk. Returns null on missing/corrupt files.
|
|
56
|
+
*/
|
|
57
|
+
export function loadPersistedState(root) {
|
|
58
|
+
const filePath = join(root, PERSIST_DIR, PERSIST_FILE)
|
|
59
|
+
try {
|
|
60
|
+
if (!existsSync(filePath)) return null
|
|
61
|
+
const raw = readFileSync(filePath, 'utf-8')
|
|
62
|
+
const data = JSON.parse(raw)
|
|
63
|
+
if (!data || typeof data !== 'object') return null
|
|
64
|
+
return validatePersistedState(data)
|
|
65
|
+
} catch {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate loaded state — reject invalid/protected branches and bad types.
|
|
72
|
+
*/
|
|
73
|
+
function validatePersistedState(data) {
|
|
74
|
+
const result = {}
|
|
75
|
+
|
|
76
|
+
if (data.targetBranch && isValidBranch(data.targetBranch) && !isProtectedBranch(data.targetBranch)) {
|
|
77
|
+
result.targetBranch = data.targetBranch
|
|
78
|
+
}
|
|
79
|
+
if (data.originalBranch && isValidBranch(data.originalBranch)) {
|
|
80
|
+
result.originalBranch = data.originalBranch
|
|
81
|
+
}
|
|
82
|
+
if (data.previousActiveBranch && isValidBranch(data.previousActiveBranch)) {
|
|
83
|
+
result.previousActiveBranch = data.previousActiveBranch
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
result.pausedOnBranchChange = data.pausedOnBranchChange === true
|
|
87
|
+
result.lastSyncTime = typeof data.lastSyncTime === 'string' ? data.lastSyncTime : null
|
|
88
|
+
result.lastSyncByScope = {
|
|
89
|
+
canvas: typeof data.lastSyncByScope?.canvas === 'string' ? data.lastSyncByScope.canvas : null,
|
|
90
|
+
prototype: typeof data.lastSyncByScope?.prototype === 'string' ? data.lastSyncByScope.prototype : null,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (data.enabledScopes && typeof data.enabledScopes === 'object') {
|
|
94
|
+
result.enabledScopes = {
|
|
95
|
+
canvas: data.enabledScopes.canvas === true,
|
|
96
|
+
prototype: data.enabledScopes.prototype === true,
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
result.enabledScopes = { canvas: false, prototype: false }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Must have a targetBranch and at least one enabled scope to be restorable
|
|
103
|
+
if (!result.targetBranch || (!result.enabledScopes.canvas && !result.enabledScopes.prototype)) {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Persist current autosync state to disk (atomic write via tmp + rename).
|
|
112
|
+
*/
|
|
113
|
+
export function persistState(root) {
|
|
114
|
+
const dirPath = join(root, PERSIST_DIR)
|
|
115
|
+
const filePath = join(dirPath, PERSIST_FILE)
|
|
116
|
+
const tmpPath = filePath + '.tmp'
|
|
117
|
+
try {
|
|
118
|
+
const currentBranch = getCurrentBranch(root)
|
|
119
|
+
const data = {
|
|
120
|
+
enabledScopes: { ...enabledScopes },
|
|
121
|
+
targetBranch,
|
|
122
|
+
originalBranch,
|
|
123
|
+
previousActiveBranch,
|
|
124
|
+
currentBranch,
|
|
125
|
+
pausedOnBranchChange,
|
|
126
|
+
lastSyncTime,
|
|
127
|
+
lastSyncByScope: { ...lastSyncByScope },
|
|
128
|
+
}
|
|
129
|
+
if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true })
|
|
130
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n', 'utf-8')
|
|
131
|
+
renameSync(tmpPath, filePath)
|
|
132
|
+
} catch {
|
|
133
|
+
// Best-effort persistence — don't break autosync if disk write fails
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Remove persisted state file (on explicit full disable).
|
|
139
|
+
*/
|
|
140
|
+
export function clearPersistedState(root) {
|
|
141
|
+
const filePath = join(root, PERSIST_DIR, PERSIST_FILE)
|
|
142
|
+
try {
|
|
143
|
+
if (existsSync(filePath)) unlinkSync(filePath)
|
|
144
|
+
} catch { /* ignore — file may already be gone */ }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Restore module-level state from a validated persisted snapshot.
|
|
149
|
+
*/
|
|
150
|
+
function applyPersistedState(data) {
|
|
151
|
+
enabledScopes = { ...data.enabledScopes }
|
|
152
|
+
targetBranch = data.targetBranch
|
|
153
|
+
originalBranch = data.originalBranch || data.targetBranch
|
|
154
|
+
previousActiveBranch = data.previousActiveBranch || null
|
|
155
|
+
pausedOnBranchChange = data.pausedOnBranchChange || false
|
|
156
|
+
lastSyncTime = data.lastSyncTime || null
|
|
157
|
+
lastSyncByScope = { ...data.lastSyncByScope }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Branch reconciliation ──
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Reconcile branch state — pause on drift, resume on return.
|
|
164
|
+
* Called at server startup and on each scheduler tick.
|
|
165
|
+
* Returns true if autosync is active (not paused), false if paused.
|
|
166
|
+
*/
|
|
167
|
+
export function reconcileBranch(root) {
|
|
168
|
+
if (!targetBranch || !hasAnyScopeEnabled()) return true
|
|
169
|
+
|
|
170
|
+
let current
|
|
171
|
+
try {
|
|
172
|
+
current = getCurrentBranch(root)
|
|
173
|
+
} catch {
|
|
174
|
+
return false // can't determine branch — don't sync
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (current === targetBranch) {
|
|
178
|
+
// Back on the target branch — resume if we were paused
|
|
179
|
+
if (pausedOnBranchChange) {
|
|
180
|
+
pausedOnBranchChange = false
|
|
181
|
+
previousActiveBranch = null
|
|
182
|
+
persistState(root)
|
|
183
|
+
}
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Branch drift — pause if not already paused
|
|
188
|
+
if (!pausedOnBranchChange) {
|
|
189
|
+
pausedOnBranchChange = true
|
|
190
|
+
previousActiveBranch = targetBranch
|
|
191
|
+
persistState(root)
|
|
192
|
+
}
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isProtectedBranch(name) {
|
|
197
|
+
const normalized = String(name || '').toLowerCase()
|
|
198
|
+
return normalized === 'main' || normalized === 'master'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Git helpers (argv-based, no shell) ──
|
|
202
|
+
|
|
203
|
+
function git(args, root) {
|
|
204
|
+
return execFileSync('git', args, { cwd: root, encoding: 'utf-8', timeout: 30_000 }).trim()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getCurrentBranch(root) {
|
|
208
|
+
return git(['rev-parse', '--abbrev-ref', 'HEAD'], root)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getUsername(root) {
|
|
212
|
+
try {
|
|
213
|
+
return git(['config', 'user.name'], root)
|
|
214
|
+
} catch {
|
|
215
|
+
return 'autosync'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getBranches(root) {
|
|
220
|
+
const raw = git(['branch', '--list', '--format=%(refname:short)'], root)
|
|
221
|
+
return raw
|
|
222
|
+
.split('\n')
|
|
223
|
+
.map((b) => b.trim())
|
|
224
|
+
.filter((b) => b && b.toLowerCase() !== 'main' && b.toLowerCase() !== 'master')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getGitDir(root) {
|
|
228
|
+
return resolve(root, git(['rev-parse', '--git-dir'], root))
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function hasScopedStagedChanges(root, files) {
|
|
232
|
+
if (!files || files.length === 0) return false
|
|
233
|
+
const changed = git(['diff', '--cached', '--name-only', '--', ...files], root)
|
|
234
|
+
return changed.length > 0
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function listChangedFiles(root) {
|
|
238
|
+
const tracked = git(['diff', '--name-only'], root)
|
|
239
|
+
const untracked = git(['ls-files', '--others', '--exclude-standard'], root)
|
|
240
|
+
return [tracked, untracked]
|
|
241
|
+
.flatMap((raw) => raw.split('\n'))
|
|
242
|
+
.map((file) => file.trim())
|
|
243
|
+
.filter(Boolean)
|
|
244
|
+
.filter((file, idx, arr) => arr.indexOf(file) === idx)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Repo-busy guards ──
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if the repo is in a state where autosync should defer.
|
|
251
|
+
* Returns { busy: true, reason } if unsafe, { busy: false } otherwise.
|
|
252
|
+
*/
|
|
253
|
+
export function isRepoBusy(root) {
|
|
254
|
+
const gitDir = getGitDir(root)
|
|
255
|
+
|
|
256
|
+
if (existsSync(join(gitDir, 'index.lock'))) {
|
|
257
|
+
return { busy: true, reason: 'index.lock exists — another git process is active' }
|
|
258
|
+
}
|
|
259
|
+
if (existsSync(join(gitDir, 'rebase-merge')) || existsSync(join(gitDir, 'rebase-apply'))) {
|
|
260
|
+
return { busy: true, reason: 'rebase in progress' }
|
|
261
|
+
}
|
|
262
|
+
if (existsSync(join(gitDir, 'MERGE_HEAD'))) {
|
|
263
|
+
return { busy: true, reason: 'merge in progress' }
|
|
264
|
+
}
|
|
265
|
+
if (existsSync(join(gitDir, 'CHERRY_PICK_HEAD'))) {
|
|
266
|
+
return { busy: true, reason: 'cherry-pick in progress' }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (targetBranch && getCurrentBranch(root) !== targetBranch) {
|
|
270
|
+
return { busy: true, reason: `branch drift: expected ${targetBranch}, on ${getCurrentBranch(root)}` }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { busy: false }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function normalizeAutosyncScope(scope) {
|
|
277
|
+
return AUTOSYNC_SCOPES.has(scope) ? scope : 'canvas'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function matchesAutosyncScope(scope, filePath) {
|
|
281
|
+
const normalizedScope = normalizeAutosyncScope(scope)
|
|
282
|
+
const file = String(filePath || '').replaceAll('\\', '/').replace(/^\.\//, '')
|
|
283
|
+
if (!file) return false
|
|
284
|
+
|
|
285
|
+
if (normalizedScope === 'prototype') {
|
|
286
|
+
return file === 'src/prototypes' || file.startsWith('src/prototypes/')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// canvas scope — includes canvas data, canvas assets, and public storyboard assets
|
|
290
|
+
return (
|
|
291
|
+
file === 'src/canvas' ||
|
|
292
|
+
file.startsWith('src/canvas/') ||
|
|
293
|
+
file.endsWith('.canvas.jsonl') ||
|
|
294
|
+
file.startsWith('assets/canvas/') ||
|
|
295
|
+
file.startsWith('assets/.storyboard-public/')
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function filterFilesForAutosyncScope(scope, files) {
|
|
300
|
+
return (files || []).filter((file) => matchesAutosyncScope(scope, file))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function listScopedChangedFiles(root, scope) {
|
|
304
|
+
return filterFilesForAutosyncScope(scope, listChangedFiles(root))
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isValidBranch(name) {
|
|
308
|
+
return typeof name === 'string' && BRANCH_NAME_RE.test(name) && name.length < 256
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function formatTime() {
|
|
312
|
+
return new Date().toLocaleString('en-US', {
|
|
313
|
+
month: 'short',
|
|
314
|
+
day: 'numeric',
|
|
315
|
+
hour: 'numeric',
|
|
316
|
+
minute: '2-digit',
|
|
317
|
+
hour12: true,
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function isRetryablePushError(message) {
|
|
322
|
+
const normalized = String(message || '').toLowerCase()
|
|
323
|
+
return (
|
|
324
|
+
normalized.includes('failed to push some refs') ||
|
|
325
|
+
normalized.includes('non-fast-forward') ||
|
|
326
|
+
normalized.includes('updates were rejected') ||
|
|
327
|
+
normalized.includes('tip of your current branch is behind') ||
|
|
328
|
+
normalized.includes('fetch first') ||
|
|
329
|
+
normalized.includes('[rejected]')
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function hasAnyScopeEnabled() {
|
|
334
|
+
return enabledScopes.canvas || enabledScopes.prototype
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getEnabledScopesInOrder() {
|
|
338
|
+
return SCOPE_ORDER.filter((scope) => enabledScopes[scope])
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function stopScheduler() {
|
|
342
|
+
if (schedulerTimeout) {
|
|
343
|
+
clearTimeout(schedulerTimeout)
|
|
344
|
+
schedulerTimeout = null
|
|
345
|
+
}
|
|
346
|
+
if (schedulerInterval) {
|
|
347
|
+
clearInterval(schedulerInterval)
|
|
348
|
+
schedulerInterval = null
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function getAlignedDelay() {
|
|
353
|
+
const remainder = Date.now() % SYNC_INTERVAL_MS
|
|
354
|
+
return remainder === 0 ? SYNC_INTERVAL_MS : SYNC_INTERVAL_MS - remainder
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function resetRuntimeState({ clearBranch = true } = {}) {
|
|
358
|
+
enabledScopes = { canvas: false, prototype: false }
|
|
359
|
+
syncing = false
|
|
360
|
+
syncingScope = null
|
|
361
|
+
pausedOnBranchChange = false
|
|
362
|
+
previousActiveBranch = null
|
|
363
|
+
if (clearBranch) {
|
|
364
|
+
targetBranch = null
|
|
365
|
+
originalBranch = null
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Undo a commit that was never pushed, leaving files staged then unstaged. */
|
|
370
|
+
function rollbackUnpushedCommit(root, scopedFiles) {
|
|
371
|
+
try {
|
|
372
|
+
git(['reset', '--soft', 'HEAD~1'], root)
|
|
373
|
+
git(['reset', '--', ...scopedFiles], root)
|
|
374
|
+
} catch {
|
|
375
|
+
// Best-effort rollback; if this fails the user's tree is still valid.
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function buildStatusPayload(root) {
|
|
380
|
+
const singleScope = enabledScopes.canvas === enabledScopes.prototype
|
|
381
|
+
? null
|
|
382
|
+
: (enabledScopes.canvas ? 'canvas' : 'prototype')
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
enabled: hasAnyScopeEnabled(),
|
|
386
|
+
enabledScopes: { ...enabledScopes },
|
|
387
|
+
scope: singleScope, // legacy field for older clients
|
|
388
|
+
branch: getCurrentBranch(root),
|
|
389
|
+
targetBranch,
|
|
390
|
+
originalBranch,
|
|
391
|
+
availableScopes: [...AUTOSYNC_SCOPES],
|
|
392
|
+
lastSyncTime,
|
|
393
|
+
lastSyncByScope: { ...lastSyncByScope },
|
|
394
|
+
lastError,
|
|
395
|
+
lastErrorByScope: { ...lastErrorByScope },
|
|
396
|
+
syncing,
|
|
397
|
+
syncingScope,
|
|
398
|
+
pausedOnBranchChange,
|
|
399
|
+
previousActiveBranch,
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function stopAutosync(root, { clearBranch = true, clearErrors = false } = {}) {
|
|
404
|
+
stopScheduler()
|
|
405
|
+
resetRuntimeState({ clearBranch })
|
|
406
|
+
if (clearErrors) {
|
|
407
|
+
lastError = null
|
|
408
|
+
lastErrorByScope = { canvas: null, prototype: null }
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── Sync cycle ──
|
|
413
|
+
|
|
414
|
+
/** Run one scoped sync — stage, commit, and push scoped files directly. */
|
|
415
|
+
function runSyncCycle(root, scope) {
|
|
416
|
+
if (syncing) return false
|
|
417
|
+
syncing = true
|
|
418
|
+
syncingScope = scope
|
|
419
|
+
let cycleSucceeded = false
|
|
420
|
+
let committed = false
|
|
421
|
+
let scopedFiles = []
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
if (!targetBranch) {
|
|
425
|
+
throw new Error('Autosync branch is not configured')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Guard: skip if repo is busy (index lock, rebase, merge, branch drift)
|
|
429
|
+
const busy = isRepoBusy(root)
|
|
430
|
+
if (busy.busy) {
|
|
431
|
+
cycleSucceeded = true // defer, not failure
|
|
432
|
+
return true
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
scopedFiles = listScopedChangedFiles(root, scope)
|
|
436
|
+
if (scopedFiles.length === 0) {
|
|
437
|
+
cycleSucceeded = true
|
|
438
|
+
return true
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Guard: skip if scoped files already have user-staged changes
|
|
442
|
+
if (hasScopedStagedChanges(root, scopedFiles)) {
|
|
443
|
+
cycleSucceeded = true // defer, not failure
|
|
444
|
+
return true
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
git(['add', '-A', '--', ...scopedFiles], root)
|
|
448
|
+
|
|
449
|
+
if (!hasScopedStagedChanges(root, scopedFiles)) {
|
|
450
|
+
cycleSucceeded = true
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const username = getUsername(root)
|
|
455
|
+
const time = formatTime()
|
|
456
|
+
git(
|
|
457
|
+
['commit', '-m', `[auto:${scope}] ${username} update at ${time}`, '--', ...scopedFiles],
|
|
458
|
+
root,
|
|
459
|
+
)
|
|
460
|
+
committed = true
|
|
461
|
+
|
|
462
|
+
for (let attempt = 1; attempt <= PUSH_RETRY_LIMIT; attempt += 1) {
|
|
463
|
+
// Re-check guards before push/rebase
|
|
464
|
+
const pushBusy = isRepoBusy(root)
|
|
465
|
+
if (pushBusy.busy) {
|
|
466
|
+
rollbackUnpushedCommit(root, scopedFiles)
|
|
467
|
+
committed = false
|
|
468
|
+
cycleSucceeded = true // defer
|
|
469
|
+
return true
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
git(['push', 'origin', `HEAD:refs/heads/${targetBranch}`], root)
|
|
474
|
+
cycleSucceeded = true
|
|
475
|
+
break
|
|
476
|
+
} catch (pushErr) {
|
|
477
|
+
if (!isRetryablePushError(pushErr?.message) || attempt === PUSH_RETRY_LIMIT) {
|
|
478
|
+
throw pushErr
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Fetch and rebase with autostash to handle non-fast-forward
|
|
482
|
+
try {
|
|
483
|
+
git(['fetch', 'origin', targetBranch], root)
|
|
484
|
+
git(['rebase', '--autostash', 'FETCH_HEAD'], root)
|
|
485
|
+
} catch {
|
|
486
|
+
// Rebase failed — abort and defer
|
|
487
|
+
try { git(['rebase', '--abort'], root) } catch { /* no rebase in progress */ }
|
|
488
|
+
rollbackUnpushedCommit(root, scopedFiles)
|
|
489
|
+
committed = false
|
|
490
|
+
cycleSucceeded = true // defer, try again next cycle
|
|
491
|
+
return true
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch (err) {
|
|
496
|
+
lastError = err.message || 'Sync failed'
|
|
497
|
+
lastErrorByScope[scope] = lastError
|
|
498
|
+
|
|
499
|
+
// Rollback the commit if we made one but never pushed
|
|
500
|
+
if (committed) {
|
|
501
|
+
rollbackUnpushedCommit(root, scopedFiles)
|
|
502
|
+
}
|
|
503
|
+
} finally {
|
|
504
|
+
if (cycleSucceeded) {
|
|
505
|
+
const nowIso = new Date().toISOString()
|
|
506
|
+
lastSyncTime = nowIso
|
|
507
|
+
lastSyncByScope[scope] = nowIso
|
|
508
|
+
lastErrorByScope[scope] = null
|
|
509
|
+
lastError = null
|
|
510
|
+
// Persist state after actual commit+push (committed is still true only if push succeeded)
|
|
511
|
+
if (committed) persistState(root)
|
|
512
|
+
}
|
|
513
|
+
syncing = false
|
|
514
|
+
syncingScope = null
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return cycleSucceeded
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function runRelayCycle(root, scopes = getEnabledScopesInOrder()) {
|
|
521
|
+
if (syncing || scopes.length === 0) return true
|
|
522
|
+
|
|
523
|
+
// Reconcile branch state — pause on drift, resume on return
|
|
524
|
+
if (!reconcileBranch(root)) return true // paused — skip sync
|
|
525
|
+
|
|
526
|
+
let ok = true
|
|
527
|
+
let firstRelaySyncTime = null
|
|
528
|
+
|
|
529
|
+
for (const scope of scopes) {
|
|
530
|
+
if (!runSyncCycle(root, scope)) {
|
|
531
|
+
ok = false
|
|
532
|
+
break
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (!firstRelaySyncTime && lastSyncByScope[scope]) {
|
|
536
|
+
firstRelaySyncTime = lastSyncByScope[scope]
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Keep a single "last sync" timestamp for relay cycles — the first synced scope.
|
|
541
|
+
if (firstRelaySyncTime) {
|
|
542
|
+
lastSyncTime = firstRelaySyncTime
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return ok
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function startScheduler(root) {
|
|
549
|
+
if (schedulerInterval || schedulerTimeout) return
|
|
550
|
+
schedulerTimeout = setTimeout(() => {
|
|
551
|
+
schedulerTimeout = null
|
|
552
|
+
runRelayCycle(root)
|
|
553
|
+
schedulerInterval = setInterval(() => runRelayCycle(root), SYNC_INTERVAL_MS)
|
|
554
|
+
}, getAlignedDelay())
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ── Route handler ──
|
|
558
|
+
|
|
559
|
+
export function createAutosyncHandler({ root, sendJson }) {
|
|
560
|
+
// ── Restore persisted state on server startup ──
|
|
561
|
+
const persisted = loadPersistedState(root)
|
|
562
|
+
if (persisted) {
|
|
563
|
+
applyPersistedState(persisted)
|
|
564
|
+
reconcileBranch(root)
|
|
565
|
+
|
|
566
|
+
// Start scheduler if any scope is enabled — reconcileBranch handles
|
|
567
|
+
// pause/resume on each tick, so the scheduler runs even when paused
|
|
568
|
+
if (hasAnyScopeEnabled()) {
|
|
569
|
+
startScheduler(root)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return async (req, res, { body, path: routePath, method }) => {
|
|
574
|
+
// GET /branches — list local branches
|
|
575
|
+
if (routePath === '/branches' && method === 'GET') {
|
|
576
|
+
try {
|
|
577
|
+
const branches = getBranches(root)
|
|
578
|
+
const current = getCurrentBranch(root)
|
|
579
|
+
sendJson(res, 200, { branches, current })
|
|
580
|
+
} catch (err) {
|
|
581
|
+
sendJson(res, 500, { error: err.message })
|
|
582
|
+
}
|
|
583
|
+
return
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// GET /status — current autosync state
|
|
587
|
+
if (routePath === '/status' && method === 'GET') {
|
|
588
|
+
try {
|
|
589
|
+
sendJson(res, 200, buildStatusPayload(root))
|
|
590
|
+
} catch (err) {
|
|
591
|
+
sendJson(res, 500, { error: err.message })
|
|
592
|
+
}
|
|
593
|
+
return
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// POST /enable — enable autosync for a scope
|
|
597
|
+
if (routePath === '/enable' && method === 'POST') {
|
|
598
|
+
try {
|
|
599
|
+
const { branch, scope } = body || {}
|
|
600
|
+
if (!branch) {
|
|
601
|
+
sendJson(res, 400, { error: 'branch is required' })
|
|
602
|
+
return
|
|
603
|
+
}
|
|
604
|
+
if (!isValidBranch(branch)) {
|
|
605
|
+
sendJson(res, 400, { error: 'Invalid branch name' })
|
|
606
|
+
return
|
|
607
|
+
}
|
|
608
|
+
if (branch.toLowerCase() === 'main' || branch.toLowerCase() === 'master') {
|
|
609
|
+
sendJson(res, 400, { error: 'Cannot autosync to main/master' })
|
|
610
|
+
return
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const currentBranch = getCurrentBranch(root)
|
|
614
|
+
if (branch !== currentBranch) {
|
|
615
|
+
sendJson(res, 400, {
|
|
616
|
+
error: `Autosync requires you to be on the target branch. Current: ${currentBranch}, requested: ${branch}`,
|
|
617
|
+
})
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const normalizedScope = normalizeAutosyncScope(scope)
|
|
622
|
+
const hadEnabledScopes = hasAnyScopeEnabled()
|
|
623
|
+
|
|
624
|
+
// Allow retargeting when paused on a different branch
|
|
625
|
+
if (hadEnabledScopes && targetBranch !== branch) {
|
|
626
|
+
if (pausedOnBranchChange) {
|
|
627
|
+
// Clear pause and retarget to the new branch
|
|
628
|
+
pausedOnBranchChange = false
|
|
629
|
+
previousActiveBranch = null
|
|
630
|
+
targetBranch = branch
|
|
631
|
+
originalBranch = currentBranch
|
|
632
|
+
} else {
|
|
633
|
+
sendJson(res, 409, { error: `Autosync is active on ${targetBranch}. Disable all scopes before switching branch.` })
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (!hadEnabledScopes) {
|
|
639
|
+
originalBranch = currentBranch
|
|
640
|
+
targetBranch = branch
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
enabledScopes[normalizedScope] = true
|
|
644
|
+
lastErrorByScope[normalizedScope] = null
|
|
645
|
+
pausedOnBranchChange = false
|
|
646
|
+
previousActiveBranch = null
|
|
647
|
+
persistState(root)
|
|
648
|
+
startScheduler(root)
|
|
649
|
+
|
|
650
|
+
// Immediate first sync for the enabled scope.
|
|
651
|
+
runSyncCycle(root, normalizedScope)
|
|
652
|
+
sendJson(res, 200, buildStatusPayload(root))
|
|
653
|
+
} catch (err) {
|
|
654
|
+
sendJson(res, 500, { error: err.message })
|
|
655
|
+
}
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// POST /disable — disable one scope or all scopes
|
|
660
|
+
if (routePath === '/disable' && method === 'POST') {
|
|
661
|
+
try {
|
|
662
|
+
const requestedScope = body?.scope
|
|
663
|
+
if (requestedScope) {
|
|
664
|
+
const normalizedScope = normalizeAutosyncScope(requestedScope)
|
|
665
|
+
enabledScopes[normalizedScope] = false
|
|
666
|
+
} else {
|
|
667
|
+
enabledScopes = { canvas: false, prototype: false }
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (!hasAnyScopeEnabled()) {
|
|
671
|
+
// Explicit full disable — clear persisted state entirely
|
|
672
|
+
stopAutosync(root, { clearBranch: true, clearErrors: true })
|
|
673
|
+
clearPersistedState(root)
|
|
674
|
+
} else {
|
|
675
|
+
// Partial disable — persist the remaining state
|
|
676
|
+
persistState(root)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
sendJson(res, 200, buildStatusPayload(root))
|
|
680
|
+
} catch (err) {
|
|
681
|
+
sendJson(res, 500, { error: err.message })
|
|
682
|
+
}
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// POST /sync — manual single relay cycle
|
|
687
|
+
if (routePath === '/sync' && method === 'POST') {
|
|
688
|
+
try {
|
|
689
|
+
if (syncing) {
|
|
690
|
+
sendJson(res, 409, { error: 'Autosync is already running' })
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
let ok = true
|
|
695
|
+
if (body?.scope) {
|
|
696
|
+
const scope = normalizeAutosyncScope(body.scope)
|
|
697
|
+
ok = runSyncCycle(root, scope)
|
|
698
|
+
} else {
|
|
699
|
+
ok = runRelayCycle(root)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
sendJson(res, ok ? 200 : 500, {
|
|
703
|
+
ok,
|
|
704
|
+
...buildStatusPayload(root),
|
|
705
|
+
})
|
|
706
|
+
} catch (err) {
|
|
707
|
+
sendJson(res, 500, { error: err.message })
|
|
708
|
+
}
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
sendJson(res, 404, { error: `Unknown autosync route: ${method} ${routePath}` })
|
|
713
|
+
}
|
|
714
|
+
}
|