@dfosco/storyboard-core 4.2.0-beta.2 → 4.2.0-beta.21
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 +109 -24
- package/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +17379 -28568
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +5 -2
- 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/manifest.json +5 -0
- package/scaffold/skills/canvas/SKILL.md +5 -4
- package/scaffold/skills/ship/SKILL.md +1 -1
- package/scaffold/storyboard.config.json +14 -1
- package/scaffold/toolbar.config.json +1 -1
- package/src/ActionMenuButton.jsx +100 -0
- package/src/AutosyncMenuButton.css +67 -0
- package/src/AutosyncMenuButton.jsx +241 -0
- package/src/BranchSelect.jsx +29 -0
- package/src/BranchSelect.module.css +30 -0
- package/src/CanvasAgentsMenu.jsx +87 -0
- package/src/CanvasCreateMenu.jsx +609 -0
- package/src/CanvasSnap.css +27 -0
- package/src/CanvasSnap.jsx +51 -0
- package/src/CanvasUndoRedo.css +36 -0
- package/src/CanvasUndoRedo.jsx +62 -0
- package/src/CanvasZoomControl.css +53 -0
- package/src/CanvasZoomControl.jsx +49 -0
- package/src/CanvasZoomToFit.css +18 -0
- package/src/CanvasZoomToFit.jsx +26 -0
- package/src/CommandMenu.css +8 -0
- package/src/CommandMenu.jsx +286 -0
- package/src/CommandPalette.jsx +35 -0
- package/src/CommandPaletteTrigger.jsx +25 -0
- package/src/CommentsMenuButton.jsx +38 -0
- package/src/CoreUIBar.css +47 -0
- package/src/CoreUIBar.jsx +855 -0
- package/src/CreateMenuButton.jsx +116 -0
- package/src/HideChromeTrigger.jsx +40 -0
- package/src/InspectorPanel.css +109 -0
- package/src/InspectorPanel.jsx +629 -0
- package/src/PwaInstallBanner.css +42 -0
- package/src/PwaInstallBanner.jsx +124 -0
- package/src/SidePanel.jsx +260 -0
- package/src/ThemeMenuButton.jsx +136 -0
- package/src/autosync/server.js +202 -5
- package/src/autosync/server.test.js +112 -0
- package/src/canvas/__tests__/agent-integration.test.js +593 -0
- package/src/canvas/__tests__/helpers/browser.js +95 -0
- package/src/canvas/__tests__/helpers/canvas-api.js +129 -0
- package/src/canvas/__tests__/helpers/perf.js +118 -0
- package/src/canvas/__tests__/helpers/setup.js +176 -0
- package/src/canvas/__tests__/helpers/tmux.js +130 -0
- package/src/canvas/__tests__/helpers/transcript.js +129 -0
- package/src/canvas/__tests__/terminal-integration.test.js +175 -0
- package/src/canvas/hot-pool.js +757 -0
- package/src/canvas/materializer.js +31 -0
- package/src/canvas/materializer.test.js +56 -0
- package/src/canvas/selectedWidgets.js +65 -7
- package/src/canvas/server.js +1801 -22
- package/src/canvas/server.test.js +239 -0
- package/src/canvas/terminal-config.js +331 -0
- package/src/canvas/terminal-registry.js +38 -0
- package/src/canvas/terminal-server.js +1037 -29
- package/src/canvas/writeGuard.js +51 -3
- package/src/canvasConfig.js +67 -1
- package/src/canvasConfig.test.js +79 -1
- package/src/cli/agent.js +85 -0
- package/src/cli/branch.js +232 -0
- package/src/cli/canvasAdd.js +59 -12
- package/src/cli/canvasBatch.js +98 -0
- package/src/cli/canvasBounds.js +1 -1
- package/src/cli/canvasRead.js +1 -1
- package/src/cli/canvasUpdate.js +179 -0
- package/src/cli/create.js +38 -14
- package/src/cli/dev.js +157 -83
- package/src/cli/exit.js +23 -24
- package/src/cli/index.js +55 -2
- package/src/cli/proxy.js +96 -37
- package/src/cli/schemas.js +22 -4
- package/src/cli/server.js +148 -25
- package/src/cli/serverUrl.js +8 -3
- package/src/cli/sessions.js +131 -5
- package/src/cli/setup.js +109 -11
- package/src/cli/terminal-commands.js +16 -8
- package/src/cli/terminal-messaging.js +231 -0
- package/src/cli/terminal-welcome.js +365 -33
- package/src/commandActions.js +1 -0
- package/src/commandPaletteConfig.js +9 -0
- package/src/comments/auth.js +2 -1
- package/src/comments/ui/AuthModal.jsx +114 -0
- package/src/comments/ui/CommentWindow.jsx +329 -0
- package/src/comments/ui/CommentsDrawer.jsx +102 -0
- package/src/comments/ui/Composer.jsx +64 -0
- package/src/comments/ui/authModal.test.js +1 -1
- package/src/comments/ui/commentWindow.js +16 -17
- package/src/comments/ui/commentsDrawer.js +25 -26
- package/src/comments/ui/composer.js +23 -24
- package/src/comments/ui/index.js +2 -3
- package/src/configSchema.js +59 -1
- package/src/configStore.js +161 -0
- package/src/core-ui-colors.css +12 -0
- package/src/devtools.js +17 -19
- package/src/devtools.test.js +18 -9
- package/src/featureFlags.js +12 -5
- package/src/fuzzySearch.test.js +10 -0
- package/src/index.js +14 -2
- package/src/lib/components/ui/alert/alert-action.jsx +11 -0
- package/src/lib/components/ui/alert/alert-description.jsx +11 -0
- package/src/lib/components/ui/alert/alert-title.jsx +11 -0
- package/src/lib/components/ui/alert/alert.jsx +25 -0
- package/src/lib/components/ui/alert/index.js +15 -15
- package/src/lib/components/ui/avatar/avatar-badge.jsx +22 -0
- package/src/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
- package/src/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
- package/src/lib/components/ui/avatar/avatar-group.jsx +19 -0
- package/src/lib/components/ui/avatar/avatar-image.jsx +15 -0
- package/src/lib/components/ui/avatar/avatar.jsx +19 -0
- package/src/lib/components/ui/avatar/index.js +20 -20
- package/src/lib/components/ui/badge/badge.jsx +31 -0
- package/src/lib/components/ui/badge/index.js +2 -2
- package/src/lib/components/ui/button/button.jsx +100 -0
- package/src/lib/components/ui/button/index.js +9 -9
- package/src/lib/components/ui/card/card-action.jsx +11 -0
- package/src/lib/components/ui/card/card-content.jsx +11 -0
- package/src/lib/components/ui/card/card-description.jsx +11 -0
- package/src/lib/components/ui/card/card-footer.jsx +11 -0
- package/src/lib/components/ui/card/card-header.jsx +19 -0
- package/src/lib/components/ui/card/card-title.jsx +11 -0
- package/src/lib/components/ui/card/card.jsx +17 -0
- package/src/lib/components/ui/card/index.js +23 -23
- package/src/lib/components/ui/checkbox/checkbox.jsx +29 -0
- package/src/lib/components/ui/checkbox/index.js +5 -5
- package/src/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
- package/src/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
- package/src/lib/components/ui/collapsible/collapsible.jsx +7 -0
- package/src/lib/components/ui/collapsible/index.js +11 -11
- package/src/lib/components/ui/dialog/dialog-close.jsx +7 -0
- package/src/lib/components/ui/dialog/dialog-content.jsx +34 -0
- package/src/lib/components/ui/dialog/dialog-description.jsx +15 -0
- package/src/lib/components/ui/dialog/dialog-footer.jsx +23 -0
- package/src/lib/components/ui/dialog/dialog-header.jsx +11 -0
- package/src/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
- package/src/lib/components/ui/dialog/dialog-portal.jsx +4 -0
- package/src/lib/components/ui/dialog/dialog-title.jsx +15 -0
- package/src/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
- package/src/lib/components/ui/dialog/dialog.jsx +4 -0
- package/src/lib/components/ui/dialog/index.js +32 -32
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
- package/src/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
- package/src/lib/components/ui/dropdown-menu/index.js +52 -52
- package/src/lib/components/ui/input/index.js +5 -5
- package/src/lib/components/ui/input/input.jsx +19 -0
- package/src/lib/components/ui/label/index.js +5 -5
- package/src/lib/components/ui/label/label.jsx +19 -0
- package/src/lib/components/ui/panel/index.js +21 -21
- package/src/lib/components/ui/panel/panel-body.jsx +11 -0
- package/src/lib/components/ui/panel/panel-close.jsx +16 -0
- package/src/lib/components/ui/panel/panel-content.jsx +29 -0
- package/src/lib/components/ui/panel/panel-footer.jsx +11 -0
- package/src/lib/components/ui/panel/panel-header.jsx +11 -0
- package/src/lib/components/ui/panel/panel-title.jsx +12 -0
- package/src/lib/components/ui/panel/panel.jsx +4 -0
- package/src/lib/components/ui/popover/index.js +26 -26
- package/src/lib/components/ui/popover/popover-close.jsx +7 -0
- package/src/lib/components/ui/popover/popover-content.jsx +22 -0
- package/src/lib/components/ui/popover/popover-description.jsx +11 -0
- package/src/lib/components/ui/popover/popover-header.jsx +11 -0
- package/src/lib/components/ui/popover/popover-portal.jsx +4 -0
- package/src/lib/components/ui/popover/popover-title.jsx +11 -0
- package/src/lib/components/ui/popover/popover-trigger.jsx +8 -0
- package/src/lib/components/ui/popover/popover.jsx +4 -0
- package/src/lib/components/ui/searchable-list.jsx +159 -0
- package/src/lib/components/ui/select/index.js +35 -35
- package/src/lib/components/ui/select/select-content.jsx +30 -0
- package/src/lib/components/ui/select/select-group-heading.jsx +17 -0
- package/src/lib/components/ui/select/select-group.jsx +15 -0
- package/src/lib/components/ui/select/select-item.jsx +26 -0
- package/src/lib/components/ui/select/select-label.jsx +11 -0
- package/src/lib/components/ui/select/select-portal.jsx +4 -0
- package/src/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
- package/src/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
- package/src/lib/components/ui/select/select-separator.jsx +15 -0
- package/src/lib/components/ui/select/select-trigger.jsx +25 -0
- package/src/lib/components/ui/select/select.jsx +4 -0
- package/src/lib/components/ui/separator/index.js +5 -5
- package/src/lib/components/ui/separator/separator.jsx +22 -0
- package/src/lib/components/ui/sheet/index.js +32 -32
- package/src/lib/components/ui/sheet/sheet-close.jsx +7 -0
- package/src/lib/components/ui/sheet/sheet-content.jsx +35 -0
- package/src/lib/components/ui/sheet/sheet-description.jsx +15 -0
- package/src/lib/components/ui/sheet/sheet-footer.jsx +11 -0
- package/src/lib/components/ui/sheet/sheet-header.jsx +11 -0
- package/src/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
- package/src/lib/components/ui/sheet/sheet-portal.jsx +4 -0
- package/src/lib/components/ui/sheet/sheet-title.jsx +15 -0
- package/src/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
- package/src/lib/components/ui/sheet/sheet.jsx +4 -0
- package/src/lib/components/ui/textarea/index.js +5 -5
- package/src/lib/components/ui/textarea/textarea.jsx +18 -0
- package/src/lib/components/ui/toggle/index.js +6 -9
- package/src/lib/components/ui/toggle/toggle.jsx +36 -0
- package/src/lib/components/ui/toggle-group/index.js +8 -8
- package/src/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
- package/src/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
- package/src/lib/components/ui/tooltip/index.js +3 -3
- package/src/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
- package/src/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
- package/src/lib/components/ui/tooltip/tooltip.jsx +11 -0
- package/src/lib/components/ui/trigger-button/index.js +3 -3
- package/src/lib/components/ui/trigger-button/trigger-button.css +38 -0
- package/src/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
- package/src/logger/devLogger.js +238 -0
- package/src/logger/devLogger.test.js +193 -0
- package/src/modes.test.js +4 -4
- package/src/mountStoryboardCore.js +123 -27
- package/src/paletteProviders.js +3 -0
- package/src/paletteProviders.test.js +2 -2
- package/src/server/index.js +98 -36
- package/src/sidepanel.css +214 -0
- package/src/styles/tailwind.css +1 -1
- package/src/svelte-plugin-ui/__tests__/ModeSwitch.test.ts +8 -8
- package/src/svelte-plugin-ui/__tests__/ToolbarShell.test.ts +11 -10
- package/src/svelte-plugin-ui/components/Icon.css +11 -0
- package/src/svelte-plugin-ui/components/Icon.jsx +281 -0
- package/src/svelte-plugin-ui/components/ModeSwitch.css +90 -0
- package/src/svelte-plugin-ui/components/ModeSwitch.jsx +47 -0
- package/src/svelte-plugin-ui/components/ToolbarShell.css +80 -0
- package/src/svelte-plugin-ui/components/ToolbarShell.jsx +84 -0
- package/src/svelte-plugin-ui/components/Viewfinder.css +412 -0
- package/src/svelte-plugin-ui/components/Viewfinder.jsx +512 -0
- package/src/svelte-plugin-ui/mount.ts +12 -16
- package/src/toolRegistry.js +4 -4
- package/src/toolbarConfigStore.js +30 -0
- package/src/tools/handlers/autosync.js +1 -1
- package/src/tools/handlers/canvasAddWidget.js +1 -1
- package/src/tools/handlers/canvasAgents.js +19 -0
- package/src/tools/handlers/canvasToolbar.js +8 -8
- package/src/tools/handlers/commandPalette.js +9 -0
- package/src/tools/handlers/comments.js +1 -1
- package/src/tools/handlers/create.js +1 -1
- package/src/tools/handlers/devtools.js +16 -0
- package/src/tools/handlers/devtools.test.js +38 -0
- package/src/tools/handlers/flows.js +1 -1
- package/src/tools/handlers/hideChrome.js +9 -0
- package/src/tools/handlers/paletteTheme.js +35 -0
- package/src/tools/handlers/theme.js +1 -1
- package/src/tools/registry.js +4 -1
- package/src/tools/surfaces/commandList.js +3 -3
- package/src/tools/surfaces/mainToolbar.js +3 -3
- package/src/tools/surfaces/registry.js +4 -4
- package/src/ui/design-modes.ts +2 -2
- package/src/ui/viewfinder.ts +1 -1
- package/src/vite/server-plugin.js +242 -60
- package/src/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
- package/src/workshop/features/createCanvas/index.js +1 -1
- package/src/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
- package/src/workshop/features/createFlow/index.js +1 -1
- package/src/workshop/features/createPage/CreatePageForm.jsx +304 -0
- package/src/workshop/features/createPage/index.js +1 -1
- package/src/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
- package/src/workshop/features/createPrototype/index.js +1 -1
- package/src/workshop/features/createPrototype/server.js +98 -0
- package/src/workshop/features/createStory/CreateStoryForm.jsx +208 -0
- package/src/workshop/features/createStory/index.js +1 -1
- package/src/workshop/ui/WorkshopPanel.jsx +98 -0
- package/src/workshop/ui/mount.ts +1 -1
- package/src/worktree/port.js +48 -0
- package/src/worktree/serverRegistry.js +120 -0
- package/toolbar.config.json +93 -42
- package/widgets.config.json +580 -12
- package/src/ActionMenuButton.svelte +0 -119
- package/src/AutosyncMenuButton.svelte +0 -397
- package/src/CanvasCreateMenu.svelte +0 -295
- package/src/CanvasSnap.svelte +0 -87
- package/src/CanvasUndoRedo.svelte +0 -108
- package/src/CanvasZoomControl.svelte +0 -111
- package/src/CanvasZoomToFit.svelte +0 -52
- package/src/CommandMenu.svelte +0 -249
- package/src/CommandPalette.svelte +0 -33
- package/src/CommentsMenuButton.svelte +0 -53
- package/src/CoreUIBar.svelte +0 -847
- package/src/CreateMenuButton.svelte +0 -133
- package/src/DocPanel.svelte +0 -299
- package/src/InspectorPanel.svelte +0 -745
- package/src/PwaInstallBanner.svelte +0 -124
- package/src/SidePanel.svelte +0 -480
- package/src/ThemeMenuButton.svelte +0 -132
- package/src/comments/ui/AuthModal.svelte +0 -108
- package/src/comments/ui/CommentWindow.svelte +0 -333
- package/src/comments/ui/CommentsDrawer.svelte +0 -96
- package/src/comments/ui/Composer.svelte +0 -65
- package/src/lib/components/ui/alert/alert-action.svelte +0 -19
- package/src/lib/components/ui/alert/alert-description.svelte +0 -22
- package/src/lib/components/ui/alert/alert-title.svelte +0 -22
- package/src/lib/components/ui/alert/alert.svelte +0 -38
- package/src/lib/components/ui/avatar/avatar-badge.svelte +0 -25
- package/src/lib/components/ui/avatar/avatar-fallback.svelte +0 -20
- package/src/lib/components/ui/avatar/avatar-group-count.svelte +0 -22
- package/src/lib/components/ui/avatar/avatar-group.svelte +0 -22
- package/src/lib/components/ui/avatar/avatar-image.svelte +0 -17
- package/src/lib/components/ui/avatar/avatar.svelte +0 -24
- package/src/lib/components/ui/badge/badge.svelte +0 -44
- package/src/lib/components/ui/button/button.svelte +0 -108
- package/src/lib/components/ui/card/card-action.svelte +0 -21
- package/src/lib/components/ui/card/card-content.svelte +0 -19
- package/src/lib/components/ui/card/card-description.svelte +0 -19
- package/src/lib/components/ui/card/card-footer.svelte +0 -18
- package/src/lib/components/ui/card/card-header.svelte +0 -21
- package/src/lib/components/ui/card/card-title.svelte +0 -14
- package/src/lib/components/ui/card/card.svelte +0 -21
- package/src/lib/components/ui/checkbox/checkbox.svelte +0 -39
- package/src/lib/components/ui/collapsible/collapsible-content.svelte +0 -7
- package/src/lib/components/ui/collapsible/collapsible-trigger.svelte +0 -7
- package/src/lib/components/ui/collapsible/collapsible.svelte +0 -11
- package/src/lib/components/ui/dialog/dialog-close.svelte +0 -11
- package/src/lib/components/ui/dialog/dialog-content.svelte +0 -42
- package/src/lib/components/ui/dialog/dialog-description.svelte +0 -17
- package/src/lib/components/ui/dialog/dialog-footer.svelte +0 -29
- package/src/lib/components/ui/dialog/dialog-header.svelte +0 -19
- package/src/lib/components/ui/dialog/dialog-overlay.svelte +0 -17
- package/src/lib/components/ui/dialog/dialog-portal.svelte +0 -7
- package/src/lib/components/ui/dialog/dialog-title.svelte +0 -17
- package/src/lib/components/ui/dialog/dialog-trigger.svelte +0 -11
- package/src/lib/components/ui/dialog/dialog.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +0 -16
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -40
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -27
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +0 -18
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -24
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +0 -20
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +0 -16
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +0 -34
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -17
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +0 -19
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +0 -17
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +0 -27
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -7
- package/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +0 -7
- package/src/lib/components/ui/input/input.svelte +0 -40
- package/src/lib/components/ui/label/label.svelte +0 -20
- package/src/lib/components/ui/panel/panel-body.svelte +0 -13
- package/src/lib/components/ui/panel/panel-close.svelte +0 -16
- package/src/lib/components/ui/panel/panel-content.svelte +0 -33
- package/src/lib/components/ui/panel/panel-footer.svelte +0 -13
- package/src/lib/components/ui/panel/panel-header.svelte +0 -16
- package/src/lib/components/ui/panel/panel-title.svelte +0 -14
- package/src/lib/components/ui/panel/panel.svelte +0 -15
- package/src/lib/components/ui/popover/popover-close.svelte +0 -7
- package/src/lib/components/ui/popover/popover-content.svelte +0 -27
- package/src/lib/components/ui/popover/popover-description.svelte +0 -19
- package/src/lib/components/ui/popover/popover-header.svelte +0 -19
- package/src/lib/components/ui/popover/popover-portal.svelte +0 -7
- package/src/lib/components/ui/popover/popover-title.svelte +0 -19
- package/src/lib/components/ui/popover/popover-trigger.svelte +0 -17
- package/src/lib/components/ui/popover/popover.svelte +0 -7
- package/src/lib/components/ui/select/select-content.svelte +0 -40
- package/src/lib/components/ui/select/select-group-heading.svelte +0 -19
- package/src/lib/components/ui/select/select-group.svelte +0 -17
- package/src/lib/components/ui/select/select-item.svelte +0 -38
- package/src/lib/components/ui/select/select-label.svelte +0 -18
- package/src/lib/components/ui/select/select-portal.svelte +0 -7
- package/src/lib/components/ui/select/select-scroll-down-button.svelte +0 -20
- package/src/lib/components/ui/select/select-scroll-up-button.svelte +0 -20
- package/src/lib/components/ui/select/select-separator.svelte +0 -17
- package/src/lib/components/ui/select/select-trigger.svelte +0 -27
- package/src/lib/components/ui/select/select.svelte +0 -11
- package/src/lib/components/ui/separator/separator.svelte +0 -23
- package/src/lib/components/ui/sheet/sheet-close.svelte +0 -7
- package/src/lib/components/ui/sheet/sheet-content.svelte +0 -43
- package/src/lib/components/ui/sheet/sheet-description.svelte +0 -17
- package/src/lib/components/ui/sheet/sheet-footer.svelte +0 -18
- package/src/lib/components/ui/sheet/sheet-header.svelte +0 -19
- package/src/lib/components/ui/sheet/sheet-overlay.svelte +0 -17
- package/src/lib/components/ui/sheet/sheet-portal.svelte +0 -7
- package/src/lib/components/ui/sheet/sheet-title.svelte +0 -17
- package/src/lib/components/ui/sheet/sheet-trigger.svelte +0 -7
- package/src/lib/components/ui/sheet/sheet.svelte +0 -7
- package/src/lib/components/ui/textarea/textarea.svelte +0 -21
- package/src/lib/components/ui/toggle/toggle.svelte +0 -45
- package/src/lib/components/ui/toggle-group/toggle-group-item.svelte +0 -35
- package/src/lib/components/ui/toggle-group/toggle-group.svelte +0 -63
- package/src/lib/components/ui/tooltip/tooltip-content.svelte +0 -24
- package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +0 -27
- package/src/lib/components/ui/tooltip/tooltip.svelte +0 -9
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +0 -106
- package/src/svelte-plugin-ui/components/Icon.svelte +0 -181
- package/src/svelte-plugin-ui/components/ModeSwitch.svelte +0 -121
- package/src/svelte-plugin-ui/components/ToolbarShell.svelte +0 -150
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +0 -1001
- package/src/tools/handlers/docs.js +0 -11
- package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +0 -139
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +0 -314
- package/src/workshop/features/createPage/CreatePageForm.svelte +0 -249
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +0 -287
- package/src/workshop/features/createStory/CreateStoryForm.svelte +0 -161
- package/src/workshop/ui/WorkshopPanel.svelte +0 -97
|
@@ -138,3 +138,242 @@ describe('POST /create with convertFrom', () => {
|
|
|
138
138
|
expect(lastResponse.body.error).toContain('collides')
|
|
139
139
|
})
|
|
140
140
|
})
|
|
141
|
+
|
|
142
|
+
// ──────────────────────────────────────────────────
|
|
143
|
+
// POST /batch
|
|
144
|
+
// ──────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
describe('POST /batch', () => {
|
|
147
|
+
let root, canvasDir, invoke, lastResponse
|
|
148
|
+
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
({ invoke, root, canvasDir, lastResponse } = setup())
|
|
151
|
+
writeCanvas(canvasDir, 'test-canvas')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
afterEach(() => {
|
|
155
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('creates multiple widgets in one batch', async () => {
|
|
159
|
+
await invoke('/batch', 'POST', {
|
|
160
|
+
name: 'test-canvas',
|
|
161
|
+
operations: [
|
|
162
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'A' } },
|
|
163
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'B' } },
|
|
164
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'C' } },
|
|
165
|
+
],
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
expect(lastResponse.status).toBe(200)
|
|
169
|
+
expect(lastResponse.body.success).toBe(true)
|
|
170
|
+
expect(lastResponse.body.results).toHaveLength(3)
|
|
171
|
+
expect(lastResponse.body.results[0].widget.props.text).toBe('A')
|
|
172
|
+
expect(lastResponse.body.results[1].widget.props.text).toBe('B')
|
|
173
|
+
expect(lastResponse.body.results[2].widget.props.text).toBe('C')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('auto-assigns index refs ($0, $1, ...)', async () => {
|
|
177
|
+
await invoke('/batch', 'POST', {
|
|
178
|
+
name: 'test-canvas',
|
|
179
|
+
operations: [
|
|
180
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'first' } },
|
|
181
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'second' } },
|
|
182
|
+
],
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
expect(lastResponse.body.success).toBe(true)
|
|
186
|
+
const { refs } = lastResponse.body
|
|
187
|
+
expect(refs['0']).toBe(lastResponse.body.results[0].widgetId)
|
|
188
|
+
expect(refs['1']).toBe(lastResponse.body.results[1].widgetId)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('supports named refs alongside index refs', async () => {
|
|
192
|
+
await invoke('/batch', 'POST', {
|
|
193
|
+
name: 'test-canvas',
|
|
194
|
+
operations: [
|
|
195
|
+
{ op: 'create-widget', type: 'sticky-note', ref: 'header', props: { text: 'H' } },
|
|
196
|
+
],
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
expect(lastResponse.body.success).toBe(true)
|
|
200
|
+
const { refs } = lastResponse.body
|
|
201
|
+
const widgetId = lastResponse.body.results[0].widgetId
|
|
202
|
+
expect(refs['0']).toBe(widgetId)
|
|
203
|
+
expect(refs['header']).toBe(widgetId)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('resolves $index refs in create-connector', async () => {
|
|
207
|
+
// First create a widget to act as the "existing" terminal widget
|
|
208
|
+
await invoke('/widget', 'POST', { name: 'test-canvas', type: 'terminal', props: {} })
|
|
209
|
+
const terminalId = lastResponse.body.widget.id
|
|
210
|
+
|
|
211
|
+
await invoke('/batch', 'POST', {
|
|
212
|
+
name: 'test-canvas',
|
|
213
|
+
operations: [
|
|
214
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'target' } },
|
|
215
|
+
{ op: 'create-connector', startWidgetId: terminalId, endWidgetId: '$0', startAnchor: 'right', endAnchor: 'left' },
|
|
216
|
+
],
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
expect(lastResponse.body.success).toBe(true)
|
|
220
|
+
expect(lastResponse.body.results).toHaveLength(2)
|
|
221
|
+
expect(lastResponse.body.results[1].op).toBe('create-connector')
|
|
222
|
+
expect(lastResponse.body.results[1].connectorId).toBeTruthy()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('resolves $named refs in update-widget', async () => {
|
|
226
|
+
await invoke('/batch', 'POST', {
|
|
227
|
+
name: 'test-canvas',
|
|
228
|
+
operations: [
|
|
229
|
+
{ op: 'create-widget', type: 'sticky-note', ref: 'note', props: { text: 'before' } },
|
|
230
|
+
{ op: 'update-widget', widgetId: '$note', props: { text: 'after' } },
|
|
231
|
+
],
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
expect(lastResponse.body.success).toBe(true)
|
|
235
|
+
expect(lastResponse.body.results[1].op).toBe('update-widget')
|
|
236
|
+
expect(lastResponse.body.results[1].success).toBe(true)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('supports move-widget with ref resolution', async () => {
|
|
240
|
+
await invoke('/batch', 'POST', {
|
|
241
|
+
name: 'test-canvas',
|
|
242
|
+
operations: [
|
|
243
|
+
{ op: 'create-widget', type: 'sticky-note', position: { x: 0, y: 0 }, props: { text: 'X' } },
|
|
244
|
+
{ op: 'move-widget', widgetId: '$0', position: { x: 500, y: 300 } },
|
|
245
|
+
],
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
expect(lastResponse.body.success).toBe(true)
|
|
249
|
+
expect(lastResponse.body.results[1].op).toBe('move-widget')
|
|
250
|
+
expect(lastResponse.body.results[1].success).toBe(true)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('supports delete-widget with ref resolution', async () => {
|
|
254
|
+
await invoke('/batch', 'POST', {
|
|
255
|
+
name: 'test-canvas',
|
|
256
|
+
operations: [
|
|
257
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'temp' } },
|
|
258
|
+
{ op: 'delete-widget', widgetId: '$0' },
|
|
259
|
+
],
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
expect(lastResponse.body.success).toBe(true)
|
|
263
|
+
expect(lastResponse.body.results[1].op).toBe('delete-widget')
|
|
264
|
+
expect(lastResponse.body.results[1].success).toBe(true)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('fails fast on unknown ref', async () => {
|
|
268
|
+
await invoke('/batch', 'POST', {
|
|
269
|
+
name: 'test-canvas',
|
|
270
|
+
operations: [
|
|
271
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'ok' } },
|
|
272
|
+
{ op: 'update-widget', widgetId: '$nonexistent', props: { text: 'fail' } },
|
|
273
|
+
],
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
expect(lastResponse.status).toBe(400)
|
|
277
|
+
expect(lastResponse.body.success).toBe(false)
|
|
278
|
+
expect(lastResponse.body.failedAt).toBe(1)
|
|
279
|
+
expect(lastResponse.body.error).toContain('Unknown ref')
|
|
280
|
+
// First operation's result should still be returned
|
|
281
|
+
expect(lastResponse.body.results).toHaveLength(1)
|
|
282
|
+
expect(lastResponse.body.results[0].op).toBe('create-widget')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('fails fast on unknown operation type', async () => {
|
|
286
|
+
await invoke('/batch', 'POST', {
|
|
287
|
+
name: 'test-canvas',
|
|
288
|
+
operations: [
|
|
289
|
+
{ op: 'explode-widget' },
|
|
290
|
+
],
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
expect(lastResponse.status).toBe(400)
|
|
294
|
+
expect(lastResponse.body.success).toBe(false)
|
|
295
|
+
expect(lastResponse.body.failedAt).toBe(0)
|
|
296
|
+
expect(lastResponse.body.error).toContain('Unknown operation')
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('rejects empty operations array', async () => {
|
|
300
|
+
await invoke('/batch', 'POST', {
|
|
301
|
+
name: 'test-canvas',
|
|
302
|
+
operations: [],
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
expect(lastResponse.status).toBe(400)
|
|
306
|
+
expect(lastResponse.body.error).toContain('non-empty')
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('rejects missing canvas name', async () => {
|
|
310
|
+
await invoke('/batch', 'POST', {
|
|
311
|
+
operations: [{ op: 'create-widget', type: 'sticky-note' }],
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
expect(lastResponse.status).toBe(400)
|
|
315
|
+
expect(lastResponse.body.error).toContain('Canvas name')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('rejects batch exceeding 200 operations', async () => {
|
|
319
|
+
const ops = Array.from({ length: 201 }, (_, i) => ({
|
|
320
|
+
op: 'create-widget', type: 'sticky-note', props: { text: `#${i}` },
|
|
321
|
+
}))
|
|
322
|
+
|
|
323
|
+
await invoke('/batch', 'POST', { name: 'test-canvas', operations: ops })
|
|
324
|
+
|
|
325
|
+
expect(lastResponse.status).toBe(400)
|
|
326
|
+
expect(lastResponse.body.error).toContain('200')
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('returns 404 for unknown canvas', async () => {
|
|
330
|
+
await invoke('/batch', 'POST', {
|
|
331
|
+
name: 'nonexistent',
|
|
332
|
+
operations: [{ op: 'create-widget', type: 'sticky-note' }],
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
expect(lastResponse.status).toBe(404)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('supports full create-update-move-connect workflow', async () => {
|
|
339
|
+
// Create a pre-existing terminal widget for connectors
|
|
340
|
+
await invoke('/widget', 'POST', { name: 'test-canvas', type: 'terminal', props: {} })
|
|
341
|
+
const termId = lastResponse.body.widget.id
|
|
342
|
+
|
|
343
|
+
await invoke('/batch', 'POST', {
|
|
344
|
+
name: 'test-canvas',
|
|
345
|
+
operations: [
|
|
346
|
+
{ op: 'create-widget', type: 'sticky-note', position: { x: 100, y: 100 }, props: { text: 'Draft' } },
|
|
347
|
+
{ op: 'update-widget', widgetId: '$0', props: { text: 'Final', color: 'blue' } },
|
|
348
|
+
{ op: 'move-widget', widgetId: '$0', position: { x: 500, y: 300 } },
|
|
349
|
+
{ op: 'create-connector', startWidgetId: termId, endWidgetId: '$0', startAnchor: 'right', endAnchor: 'left' },
|
|
350
|
+
],
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
expect(lastResponse.body.success).toBe(true)
|
|
354
|
+
expect(lastResponse.body.results).toHaveLength(4)
|
|
355
|
+
expect(lastResponse.body.results[0].op).toBe('create-widget')
|
|
356
|
+
expect(lastResponse.body.results[1].op).toBe('update-widget')
|
|
357
|
+
expect(lastResponse.body.results[2].op).toBe('move-widget')
|
|
358
|
+
expect(lastResponse.body.results[3].op).toBe('create-connector')
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('connector refs also get index refs', async () => {
|
|
362
|
+
await invoke('/widget', 'POST', { name: 'test-canvas', type: 'terminal', props: {} })
|
|
363
|
+
const termId = lastResponse.body.widget.id
|
|
364
|
+
|
|
365
|
+
await invoke('/batch', 'POST', {
|
|
366
|
+
name: 'test-canvas',
|
|
367
|
+
operations: [
|
|
368
|
+
{ op: 'create-widget', type: 'sticky-note', props: { text: 'A' } },
|
|
369
|
+
{ op: 'create-connector', startWidgetId: termId, endWidgetId: '$0', startAnchor: 'right', endAnchor: 'left' },
|
|
370
|
+
],
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
expect(lastResponse.body.success).toBe(true)
|
|
374
|
+
const { refs } = lastResponse.body
|
|
375
|
+
// Op 0 = widget, Op 1 = connector — both get index refs
|
|
376
|
+
expect(refs['0']).toMatch(/^sticky-note-/)
|
|
377
|
+
expect(refs['1']).toMatch(/^connector-/)
|
|
378
|
+
})
|
|
379
|
+
})
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Config — per-terminal context files for agent awareness.
|
|
3
|
+
*
|
|
4
|
+
* Each terminal widget gets a config at `.storyboard/terminals/{hash}.json`
|
|
5
|
+
* that agents read on startup to understand their canvas context.
|
|
6
|
+
*
|
|
7
|
+
* Files are keyed by a stable hash (same as tmuxName) so renames don't break them.
|
|
8
|
+
* The canvasId/widgetId are stored inside the JSON payload.
|
|
9
|
+
*
|
|
10
|
+
* Connected widgets are stored as IDs only — full widget data is resolved
|
|
11
|
+
* from the materialized canvas state at read time to stay fresh.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync, symlinkSync, unlinkSync, lstatSync } from 'node:fs'
|
|
15
|
+
import { join, dirname } from 'node:path'
|
|
16
|
+
import { createHash } from 'node:crypto'
|
|
17
|
+
import { execSync } from 'node:child_process'
|
|
18
|
+
import { findByWorktree } from '../worktree/serverRegistry.js'
|
|
19
|
+
import { detectWorktreeName } from '../worktree/port.js'
|
|
20
|
+
import { readCurrentViewport } from './selectedWidgets.js'
|
|
21
|
+
|
|
22
|
+
const TERMINALS_DIR = '.storyboard/terminals'
|
|
23
|
+
|
|
24
|
+
let rootDir = process.cwd()
|
|
25
|
+
|
|
26
|
+
/** Initialize with the project root directory */
|
|
27
|
+
export function initTerminalConfig(root) {
|
|
28
|
+
rootDir = root
|
|
29
|
+
const dir = join(rootDir, TERMINALS_DIR)
|
|
30
|
+
if (!existsSync(dir)) {
|
|
31
|
+
mkdirSync(dir, { recursive: true })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Read storyboard.config.json for devDomain */
|
|
36
|
+
function readDevDomain() {
|
|
37
|
+
try {
|
|
38
|
+
const raw = readFileSync(join(rootDir, 'storyboard.config.json'), 'utf8')
|
|
39
|
+
return JSON.parse(raw).devDomain || 'storyboard'
|
|
40
|
+
} catch { return 'storyboard' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Detect worktree name */
|
|
44
|
+
function getWorktreeName() {
|
|
45
|
+
try {
|
|
46
|
+
// Check if we're in a .worktrees/ directory
|
|
47
|
+
const cwd = rootDir
|
|
48
|
+
const match = cwd.match(/\.worktrees\/([^/]+)/)
|
|
49
|
+
return match ? match[1] : 'main'
|
|
50
|
+
} catch { return 'main' }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Generate a stable filename from branch + canvasId + widgetId */
|
|
54
|
+
function configKey(branch, canvasId, widgetId) {
|
|
55
|
+
const input = `${branch}::${canvasId}::${widgetId}`
|
|
56
|
+
return createHash('sha256').update(input).digest('hex').slice(0, 16)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Get the config file path */
|
|
60
|
+
function configPath(branch, canvasId, widgetId) {
|
|
61
|
+
return join(rootDir, TERMINALS_DIR, `${configKey(branch, canvasId, widgetId)}.json`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Atomic write — write to temp then rename */
|
|
65
|
+
function atomicWrite(filePath, data) {
|
|
66
|
+
const tmp = filePath + '.tmp'
|
|
67
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2))
|
|
68
|
+
renameSync(tmp, filePath)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pre-reserve terminal identity at widget creation time.
|
|
73
|
+
* Called from POST /widget when a terminal/agent widget is added to the canvas,
|
|
74
|
+
* BEFORE the widget renders or the WebSocket connects.
|
|
75
|
+
*
|
|
76
|
+
* Writes a minimal config file at `.storyboard/terminals/{widgetId}.json` so
|
|
77
|
+
* agents (especially hot-pool sessions) can find their identity immediately.
|
|
78
|
+
* The `reserved` flag marks this as a pre-reserve — writeTerminalConfig() will
|
|
79
|
+
* later overwrite it with the full config.
|
|
80
|
+
*/
|
|
81
|
+
export function preReserveTerminalIdentity({ widgetId, preDisplayName, canvasId, branch, serverUrl }) {
|
|
82
|
+
const dir = join(rootDir, TERMINALS_DIR)
|
|
83
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
84
|
+
|
|
85
|
+
const fp = join(dir, `${widgetId}.json`)
|
|
86
|
+
const data = {
|
|
87
|
+
widgetId,
|
|
88
|
+
preDisplayName,
|
|
89
|
+
displayName: preDisplayName,
|
|
90
|
+
canvasId,
|
|
91
|
+
branch,
|
|
92
|
+
serverUrl: serverUrl || null,
|
|
93
|
+
reserved: true,
|
|
94
|
+
connectedWidgets: [],
|
|
95
|
+
messaging: null,
|
|
96
|
+
agentStatus: null,
|
|
97
|
+
viewport: readCurrentViewport(rootDir) || null,
|
|
98
|
+
updatedAt: new Date().toISOString(),
|
|
99
|
+
}
|
|
100
|
+
atomicWrite(fp, data)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Write or update a terminal config file.
|
|
105
|
+
* Called when a terminal widget is created or reconnected.
|
|
106
|
+
*/
|
|
107
|
+
export function writeTerminalConfig({ branch, canvasId, widgetId, canvasFile = null, serverUrl = null, tmuxName = null, widgetProps = null, displayName = null }) {
|
|
108
|
+
const fp = configPath(branch, canvasId, widgetId)
|
|
109
|
+
const dir = dirname(fp)
|
|
110
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
111
|
+
|
|
112
|
+
let existing = {}
|
|
113
|
+
try {
|
|
114
|
+
existing = JSON.parse(readFileSync(fp, 'utf8'))
|
|
115
|
+
} catch { /* new file */ }
|
|
116
|
+
|
|
117
|
+
const worktree = getWorktreeName()
|
|
118
|
+
const devDomain = readDevDomain()
|
|
119
|
+
|
|
120
|
+
// Resolve server URL: use passed value, or query server registry, or default
|
|
121
|
+
if (!serverUrl) {
|
|
122
|
+
try {
|
|
123
|
+
const name = detectWorktreeName()
|
|
124
|
+
const servers = findByWorktree(name)
|
|
125
|
+
if (servers.length > 0) serverUrl = `http://localhost:${servers[0].port}`
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
if (!serverUrl) serverUrl = 'http://localhost:1234'
|
|
129
|
+
|
|
130
|
+
const config = {
|
|
131
|
+
...existing,
|
|
132
|
+
widgetId,
|
|
133
|
+
displayName: displayName || existing.displayName || widgetProps?.prettyName || existing.widgetProps?.prettyName || null,
|
|
134
|
+
canvasId,
|
|
135
|
+
canvasFile: canvasFile || existing.canvasFile || null,
|
|
136
|
+
branch,
|
|
137
|
+
worktree,
|
|
138
|
+
devDomain,
|
|
139
|
+
serverUrl,
|
|
140
|
+
workingDirectory: rootDir,
|
|
141
|
+
deleted: false,
|
|
142
|
+
widgetProps: widgetProps || existing.widgetProps || null,
|
|
143
|
+
connectedWidgets: existing.connectedWidgets || [],
|
|
144
|
+
agentStatus: existing.agentStatus || null,
|
|
145
|
+
viewport: readCurrentViewport(rootDir) || existing.viewport || null,
|
|
146
|
+
updatedAt: new Date().toISOString(),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
atomicWrite(fp, config)
|
|
150
|
+
|
|
151
|
+
// Create a widgetId-named symlink so agents can find their config directly
|
|
152
|
+
const hashName = `${configKey(branch, canvasId, widgetId)}.json`
|
|
153
|
+
const symPath = join(dir, `${widgetId}.json`)
|
|
154
|
+
try {
|
|
155
|
+
if (existsSync(symPath)) unlinkSync(symPath)
|
|
156
|
+
symlinkSync(hashName, symPath)
|
|
157
|
+
} catch { /* symlink creation is best-effort */ }
|
|
158
|
+
|
|
159
|
+
// Create a tmuxName-named symlink so agents can resolve identity via tmux session name
|
|
160
|
+
// (tmux session name is always available and never goes stale)
|
|
161
|
+
if (tmuxName) {
|
|
162
|
+
const tmuxSymPath = join(dir, `${tmuxName}.json`)
|
|
163
|
+
try {
|
|
164
|
+
if (existsSync(tmuxSymPath)) unlinkSync(tmuxSymPath)
|
|
165
|
+
symlinkSync(hashName, tmuxSymPath)
|
|
166
|
+
} catch { /* best-effort */ }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return config
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Update connected widgets for a terminal.
|
|
174
|
+
* Called when connectors are added/removed.
|
|
175
|
+
* Stores full widget objects (id, type, props, position) so agents
|
|
176
|
+
* can read context directly without additional API calls.
|
|
177
|
+
*/
|
|
178
|
+
export function updateTerminalConnections({ branch, canvasId, widgetId, connectedWidgets, widgetProps = null, messaging = null }) {
|
|
179
|
+
const fp = configPath(branch, canvasId, widgetId)
|
|
180
|
+
let config = {}
|
|
181
|
+
try {
|
|
182
|
+
config = JSON.parse(readFileSync(fp, 'utf8'))
|
|
183
|
+
} catch { /* file may not exist yet */ }
|
|
184
|
+
|
|
185
|
+
if (widgetProps) {
|
|
186
|
+
config.widgetProps = widgetProps
|
|
187
|
+
// Promote displayName from prettyName
|
|
188
|
+
if (widgetProps.prettyName) config.displayName = widgetProps.prettyName
|
|
189
|
+
}
|
|
190
|
+
config.connectedWidgets = connectedWidgets || []
|
|
191
|
+
config.messaging = messaging || null
|
|
192
|
+
config.viewport = readCurrentViewport(rootDir) || config.viewport || null
|
|
193
|
+
config.updatedAt = new Date().toISOString()
|
|
194
|
+
|
|
195
|
+
atomicWrite(fp, config)
|
|
196
|
+
return config
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Mark a terminal config as deleted (tombstone).
|
|
201
|
+
* Called when a terminal widget is deleted.
|
|
202
|
+
*/
|
|
203
|
+
export function markTerminalDeleted({ branch, canvasId, widgetId }) {
|
|
204
|
+
const fp = configPath(branch, canvasId, widgetId)
|
|
205
|
+
try {
|
|
206
|
+
const config = JSON.parse(readFileSync(fp, 'utf8'))
|
|
207
|
+
config.deleted = true
|
|
208
|
+
config.updatedAt = new Date().toISOString()
|
|
209
|
+
atomicWrite(fp, config)
|
|
210
|
+
} catch { /* file may not exist */ }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Unmark a terminal config as deleted (undo).
|
|
215
|
+
* Called when a deleted terminal widget is restored.
|
|
216
|
+
*/
|
|
217
|
+
export function unmarkTerminalDeleted({ branch, canvasId, widgetId }) {
|
|
218
|
+
const fp = configPath(branch, canvasId, widgetId)
|
|
219
|
+
try {
|
|
220
|
+
const config = JSON.parse(readFileSync(fp, 'utf8'))
|
|
221
|
+
config.deleted = false
|
|
222
|
+
config.updatedAt = new Date().toISOString()
|
|
223
|
+
atomicWrite(fp, config)
|
|
224
|
+
} catch { /* file may not exist */ }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Read a terminal config. Connected widgets are already inline —
|
|
229
|
+
* no additional resolution needed.
|
|
230
|
+
*/
|
|
231
|
+
export function readTerminalConfig({ branch, canvasId, widgetId }) {
|
|
232
|
+
const fp = configPath(branch, canvasId, widgetId)
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(readFileSync(fp, 'utf8'))
|
|
235
|
+
} catch {
|
|
236
|
+
return null
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Update agent status in the terminal config.
|
|
242
|
+
* Called by the signal endpoint.
|
|
243
|
+
*/
|
|
244
|
+
export function updateAgentStatus({ branch, canvasId, widgetId, status, message = null, data = null }) {
|
|
245
|
+
const fp = configPath(branch, canvasId, widgetId)
|
|
246
|
+
let config = {}
|
|
247
|
+
try {
|
|
248
|
+
config = JSON.parse(readFileSync(fp, 'utf8'))
|
|
249
|
+
} catch { /* may not exist */ }
|
|
250
|
+
|
|
251
|
+
config.agentStatus = {
|
|
252
|
+
status,
|
|
253
|
+
message,
|
|
254
|
+
data,
|
|
255
|
+
updatedAt: new Date().toISOString(),
|
|
256
|
+
}
|
|
257
|
+
config.updatedAt = new Date().toISOString()
|
|
258
|
+
|
|
259
|
+
atomicWrite(fp, config)
|
|
260
|
+
return config
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Read a terminal config by widget ID (searches by symlink).
|
|
265
|
+
* @param {string} widgetId
|
|
266
|
+
* @returns {Object|null}
|
|
267
|
+
*/
|
|
268
|
+
export function readTerminalConfigById(widgetId) {
|
|
269
|
+
const dir = join(rootDir, TERMINALS_DIR)
|
|
270
|
+
const symPath = join(dir, `${widgetId}.json`)
|
|
271
|
+
try {
|
|
272
|
+
return JSON.parse(readFileSync(symPath, 'utf8'))
|
|
273
|
+
} catch {
|
|
274
|
+
return null
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Append a pending message to a terminal's config.
|
|
280
|
+
* Messages are delivered when the agent starts or reconnects.
|
|
281
|
+
* @param {string} widgetId
|
|
282
|
+
* @param {{ from: string|null, fromName: string, message: string, createdAt: string }} msg
|
|
283
|
+
*/
|
|
284
|
+
export function updatePendingMessages(widgetId, msg) {
|
|
285
|
+
const config = readTerminalConfigById(widgetId)
|
|
286
|
+
if (!config) return
|
|
287
|
+
|
|
288
|
+
if (!Array.isArray(config.pendingMessages)) config.pendingMessages = []
|
|
289
|
+
config.pendingMessages.push(msg)
|
|
290
|
+
config.updatedAt = new Date().toISOString()
|
|
291
|
+
|
|
292
|
+
// Write back using the config's known path
|
|
293
|
+
const fp = configPath(config.branch || 'unknown', config.canvasId || 'unknown', widgetId)
|
|
294
|
+
atomicWrite(fp, config)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Atomically read and clear pending messages from a terminal config.
|
|
299
|
+
* Returns the messages array (empty if none). Safe to call from any process.
|
|
300
|
+
* @param {string} widgetId
|
|
301
|
+
* @returns {Array<{ from: string|null, fromName: string, message: string, createdAt: string }>}
|
|
302
|
+
*/
|
|
303
|
+
export function takePendingMessages(widgetId) {
|
|
304
|
+
const config = readTerminalConfigById(widgetId)
|
|
305
|
+
if (!config?.pendingMessages?.length) return []
|
|
306
|
+
|
|
307
|
+
const messages = config.pendingMessages
|
|
308
|
+
config.pendingMessages = []
|
|
309
|
+
config.updatedAt = new Date().toISOString()
|
|
310
|
+
|
|
311
|
+
// Write back via hash path (not symlink) to preserve symlink integrity
|
|
312
|
+
const fp = configPath(config.branch || 'unknown', config.canvasId || 'unknown', widgetId)
|
|
313
|
+
atomicWrite(fp, config)
|
|
314
|
+
return messages
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Save the latest output from an agent for peers to read.
|
|
319
|
+
* @param {string} widgetId
|
|
320
|
+
* @param {{ content: string, summary: string, updatedAt: string }} output
|
|
321
|
+
*/
|
|
322
|
+
export function updateLatestOutput(widgetId, output) {
|
|
323
|
+
const config = readTerminalConfigById(widgetId)
|
|
324
|
+
if (!config) return
|
|
325
|
+
|
|
326
|
+
config.latestOutput = output
|
|
327
|
+
config.updatedAt = new Date().toISOString()
|
|
328
|
+
|
|
329
|
+
const fp = configPath(config.branch || 'unknown', config.canvasId || 'unknown', widgetId)
|
|
330
|
+
atomicWrite(fp, config)
|
|
331
|
+
}
|
|
@@ -305,6 +305,44 @@ export function killSession(tmuxName) {
|
|
|
305
305
|
persist()
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Bulk cleanup — kill all sessions matching the given statuses.
|
|
310
|
+
* Returns { removed, remaining: { live, background, archived, total } }.
|
|
311
|
+
*/
|
|
312
|
+
export function bulkCleanup({ statuses }) {
|
|
313
|
+
const statusSet = new Set(statuses)
|
|
314
|
+
const toKill = []
|
|
315
|
+
|
|
316
|
+
for (const [name, entry] of sessions) {
|
|
317
|
+
if (statusSet.has(entry.status)) {
|
|
318
|
+
toKill.push(name)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
for (const name of toKill) {
|
|
323
|
+
cancelOrphanTimer(name)
|
|
324
|
+
killTmuxSession(name)
|
|
325
|
+
sessions.delete(name)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (toKill.length > 0) persist()
|
|
329
|
+
|
|
330
|
+
return { removed: toKill.length, remaining: getSessionStats() }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get session counts by status.
|
|
335
|
+
*/
|
|
336
|
+
export function getSessionStats() {
|
|
337
|
+
let live = 0, background = 0, archived = 0
|
|
338
|
+
for (const entry of sessions.values()) {
|
|
339
|
+
if (entry.status === 'live') live++
|
|
340
|
+
else if (entry.status === 'background') background++
|
|
341
|
+
else if (entry.status === 'archived') archived++
|
|
342
|
+
}
|
|
343
|
+
return { live, background, archived, total: live + background + archived }
|
|
344
|
+
}
|
|
345
|
+
|
|
308
346
|
/**
|
|
309
347
|
* Get a session entry by tmux name.
|
|
310
348
|
*/
|