@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
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Widget Integration Tests (Groups 2–4)
|
|
3
|
+
*
|
|
4
|
+
* For each configured agent (Copilot, Claude, etc.), runs:
|
|
5
|
+
* - Group 2: Startup & basic interaction
|
|
6
|
+
* - Group 3: Context awareness
|
|
7
|
+
* - Group 4: Connected widget CRUD
|
|
8
|
+
*
|
|
9
|
+
* Agent chains run in parallel — each agent gets its own widget, tmux session,
|
|
10
|
+
* and browser session. This mirrors real-world canvas usage where multiple
|
|
11
|
+
* agents run simultaneously.
|
|
12
|
+
*
|
|
13
|
+
* Prerequisites: dev server running, tmux installed, agent-browser installed.
|
|
14
|
+
* Run with: npm run test:integration
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
18
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
19
|
+
import { join } from 'node:path'
|
|
20
|
+
import * as canvasApi from './helpers/canvas-api.js'
|
|
21
|
+
import * as tmux from './helpers/tmux.js'
|
|
22
|
+
import * as browser from './helpers/browser.js'
|
|
23
|
+
import * as transcript from './helpers/transcript.js'
|
|
24
|
+
import * as perf from './helpers/perf.js'
|
|
25
|
+
import {
|
|
26
|
+
preflight,
|
|
27
|
+
createTestCanvas,
|
|
28
|
+
deleteTestCanvas,
|
|
29
|
+
resolveServerUrl,
|
|
30
|
+
loadConfiguredAgents,
|
|
31
|
+
checkAgentAvailability,
|
|
32
|
+
writeResults,
|
|
33
|
+
} from './helpers/setup.js'
|
|
34
|
+
|
|
35
|
+
const CANVAS_NAME = '__test__-agent'
|
|
36
|
+
const ROOT = process.cwd()
|
|
37
|
+
|
|
38
|
+
let serverUrl
|
|
39
|
+
let agents = []
|
|
40
|
+
|
|
41
|
+
// Track created resources for cleanup
|
|
42
|
+
const createdWidgetIds = []
|
|
43
|
+
const tmuxSessions = []
|
|
44
|
+
const browserSessions = []
|
|
45
|
+
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
const { url } = await preflight()
|
|
48
|
+
serverUrl = url
|
|
49
|
+
createTestCanvas(CANVAS_NAME)
|
|
50
|
+
|
|
51
|
+
// Load agents and filter to available ones
|
|
52
|
+
const configured = loadConfiguredAgents()
|
|
53
|
+
agents = configured.filter((a) => {
|
|
54
|
+
const available = checkAgentAvailability(a.id)
|
|
55
|
+
if (!available) {
|
|
56
|
+
console.log(`[skip] Agent "${a.label}" (${a.id}) — binary not found, skipping`)
|
|
57
|
+
}
|
|
58
|
+
return available
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
if (agents.length === 0) {
|
|
62
|
+
console.warn('[warn] No agents available — agent tests will be skipped')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Give the dev server a moment to pick up the new canvas file
|
|
66
|
+
await new Promise((r) => setTimeout(r, 2000))
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
afterAll(() => {
|
|
70
|
+
// Flush all transcripts
|
|
71
|
+
const paths = transcript.flushAll()
|
|
72
|
+
|
|
73
|
+
// Write results
|
|
74
|
+
writeResults(perf.toJSON(), paths)
|
|
75
|
+
perf.report()
|
|
76
|
+
|
|
77
|
+
// Cleanup: kill tmux sessions
|
|
78
|
+
for (const s of tmuxSessions) {
|
|
79
|
+
tmux.killSession(s)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Cleanup: close browser sessions
|
|
83
|
+
for (const s of browserSessions) {
|
|
84
|
+
browser.close(s)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Cleanup: delete test canvas
|
|
88
|
+
deleteTestCanvas(CANVAS_NAME)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read a terminal config for a widget from .storyboard/terminals/.
|
|
93
|
+
* Tries both the widgetId symlink and falls back to scanning files.
|
|
94
|
+
*/
|
|
95
|
+
function readTerminalConfig(widgetId) {
|
|
96
|
+
const terminalsDir = join(ROOT, '.storyboard', 'terminals')
|
|
97
|
+
try {
|
|
98
|
+
// Try widgetId symlink first
|
|
99
|
+
const symPath = join(terminalsDir, `${widgetId}.json`)
|
|
100
|
+
return JSON.parse(readFileSync(symPath, 'utf8'))
|
|
101
|
+
} catch { /* fallback */ }
|
|
102
|
+
|
|
103
|
+
// Scan for config containing this widgetId
|
|
104
|
+
try {
|
|
105
|
+
const files = readdirSync(terminalsDir).filter((f) => f.endsWith('.json'))
|
|
106
|
+
for (const f of files) {
|
|
107
|
+
try {
|
|
108
|
+
const config = JSON.parse(readFileSync(join(terminalsDir, f), 'utf8'))
|
|
109
|
+
if (config.widgetId === widgetId) return config
|
|
110
|
+
} catch { /* skip */ }
|
|
111
|
+
}
|
|
112
|
+
} catch { /* dir may not exist */ }
|
|
113
|
+
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Review helper — logs full context on soft assertion failure.
|
|
119
|
+
*/
|
|
120
|
+
function reviewLog(testId, expected, actual, extra = {}) {
|
|
121
|
+
const lines = [
|
|
122
|
+
`\n[REVIEW] ${testId}`,
|
|
123
|
+
` Expected pattern: ${expected}`,
|
|
124
|
+
` Actual output (last 30 lines):`,
|
|
125
|
+
...String(actual).split('\n').slice(-30).map((l) => ` ${l}`),
|
|
126
|
+
]
|
|
127
|
+
if (extra.widgetId) lines.push(` Widget ID: ${extra.widgetId}`)
|
|
128
|
+
if (extra.elapsed) lines.push(` Elapsed: ${extra.elapsed}`)
|
|
129
|
+
console.log(lines.join('\n'))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Generate test suites for each agent — they run in parallel
|
|
133
|
+
for (const agent of loadConfiguredAgents()) {
|
|
134
|
+
const agentDescribe = checkAgentAvailability(agent.id) ? describe : describe.skip
|
|
135
|
+
|
|
136
|
+
agentDescribe(`Agent: ${agent.label} (${agent.id})`, () => {
|
|
137
|
+
let agentWidgetId
|
|
138
|
+
let agentTmuxSession
|
|
139
|
+
const browserSession = `test-${agent.id}`
|
|
140
|
+
let stickyNoteId
|
|
141
|
+
let connectorId
|
|
142
|
+
let markdownId
|
|
143
|
+
let markdownConnectorId
|
|
144
|
+
|
|
145
|
+
browserSessions.push(browserSession)
|
|
146
|
+
|
|
147
|
+
// ─── Group 2: Startup & Basic Interaction ────────────────────────
|
|
148
|
+
|
|
149
|
+
describe('Group 2: Startup & Interaction', () => {
|
|
150
|
+
it(`T2.1 — Create ${agent.label} agent widget`, async () => {
|
|
151
|
+
const result = await canvasApi.createWidget(
|
|
152
|
+
CANVAS_NAME,
|
|
153
|
+
'agent',
|
|
154
|
+
{ startupCommand: agent.startupCommand },
|
|
155
|
+
{ x: 100, y: 100 + agents.indexOf(agent) * 500 },
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
expect(result.success).toBe(true)
|
|
159
|
+
expect(result.widget).toBeDefined()
|
|
160
|
+
expect(result.widget.type).toBe('agent')
|
|
161
|
+
agentWidgetId = result.widget.id
|
|
162
|
+
createdWidgetIds.push(agentWidgetId)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it(`T2.2 — ${agent.label} starts and loads startup sequence`, async () => {
|
|
166
|
+
if (!agentWidgetId) return
|
|
167
|
+
|
|
168
|
+
// Open canvas in browser and click the agent widget
|
|
169
|
+
const canvasUrl = `${serverUrl}/storyboard/canvas/${CANVAS_NAME}`
|
|
170
|
+
browser.open(browserSession, canvasUrl)
|
|
171
|
+
browser.waitForLoad(browserSession, 'networkidle')
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
browser.click(browserSession, `[data-widget-id="${agentWidgetId}"]`)
|
|
175
|
+
} catch { /* try alternative interaction */ }
|
|
176
|
+
|
|
177
|
+
// Wait for tmux session
|
|
178
|
+
const chainTimer = perf.start(`agent.${agent.id}.full_chain`)
|
|
179
|
+
const startupTimer = perf.start(`agent.${agent.id}.startup`)
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
agentTmuxSession = await tmux.waitForSession(/^sb-/, 30000)
|
|
183
|
+
} catch {
|
|
184
|
+
agentTmuxSession = await tmux.waitForSession(/^sb-/, 60000)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
tmuxSessions.push(agentTmuxSession)
|
|
188
|
+
transcript.createTranscript(agentTmuxSession, {
|
|
189
|
+
widgetId: agentWidgetId,
|
|
190
|
+
canvasName: CANVAS_NAME,
|
|
191
|
+
agentId: agent.id,
|
|
192
|
+
})
|
|
193
|
+
transcript.logSection(agentTmuxSession, 'T2.2')
|
|
194
|
+
|
|
195
|
+
expect(agentTmuxSession).toBeTruthy()
|
|
196
|
+
|
|
197
|
+
// Wait for agent to be ready — look for readiness indicators
|
|
198
|
+
try {
|
|
199
|
+
await tmux.waitForOutput(
|
|
200
|
+
agentTmuxSession,
|
|
201
|
+
/ready|copilot|claude|>|\$/i,
|
|
202
|
+
90000,
|
|
203
|
+
2000,
|
|
204
|
+
)
|
|
205
|
+
} catch (err) {
|
|
206
|
+
// Log but don't fail — startup might be slow
|
|
207
|
+
console.warn(`[warn] Agent ${agent.id} startup may be slow: ${err.message}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
startupTimer.end()
|
|
211
|
+
|
|
212
|
+
// Validate startup output is clean — check for error indicators
|
|
213
|
+
const startupOutput = tmux.capturePane(agentTmuxSession)
|
|
214
|
+
const errorPatterns = /error:|stack trace|ENOENT|EACCES|segfault|panic|unhandled/i
|
|
215
|
+
if (errorPatterns.test(startupOutput)) {
|
|
216
|
+
console.warn(`[REVIEW] T2.2 — ${agent.label} startup contains error-like output:\n${startupOutput}`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Store chainTimer for later
|
|
220
|
+
agentDescribe.__chainTimer = chainTimer
|
|
221
|
+
}, 120_000)
|
|
222
|
+
|
|
223
|
+
it(`T2.3 — ${agent.label} terminal output flows correctly`, async () => {
|
|
224
|
+
if (!agentTmuxSession) return
|
|
225
|
+
transcript.logSection(agentTmuxSession, 'T2.3')
|
|
226
|
+
|
|
227
|
+
// Verify the pane has content (not empty/blank)
|
|
228
|
+
const output = tmux.capturePane(agentTmuxSession)
|
|
229
|
+
expect(output.trim().length).toBeGreaterThan(0)
|
|
230
|
+
|
|
231
|
+
// Check that there are no broken escape sequences (raw \x1b[ without completion)
|
|
232
|
+
const brokenEscapes = output.match(/\x1b\[[^a-zA-Z]*$/m)
|
|
233
|
+
expect.soft(brokenEscapes).toBeNull()
|
|
234
|
+
|
|
235
|
+
if (brokenEscapes) {
|
|
236
|
+
reviewLog(`T2.3 — ${agent.label} broken escapes`, 'no broken escape sequences', output, { widgetId: agentWidgetId })
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it(`T2.5 — ${agent.label} answers a simple question`, async () => {
|
|
241
|
+
if (!agentTmuxSession) return
|
|
242
|
+
transcript.logSection(agentTmuxSession, 'T2.5')
|
|
243
|
+
|
|
244
|
+
const questionTimer = perf.start(`agent.${agent.id}.response`)
|
|
245
|
+
|
|
246
|
+
// Send the question
|
|
247
|
+
tmux.sendText(agentTmuxSession, 'What color is the sky during the day? Answer in one word.')
|
|
248
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
249
|
+
|
|
250
|
+
// Wait for a response — generous timeout for agent processing
|
|
251
|
+
let responseOutput = ''
|
|
252
|
+
try {
|
|
253
|
+
responseOutput = await tmux.waitForOutput(
|
|
254
|
+
agentTmuxSession,
|
|
255
|
+
/blue|azure|cerulean|cyan|sky\s*blue/i,
|
|
256
|
+
90000,
|
|
257
|
+
3000,
|
|
258
|
+
)
|
|
259
|
+
questionTimer.end({ matched: true })
|
|
260
|
+
} catch (err) {
|
|
261
|
+
questionTimer.end({ matched: false })
|
|
262
|
+
responseOutput = err.lastCapture || tmux.capturePane(agentTmuxSession)
|
|
263
|
+
|
|
264
|
+
// Soft assertion — don't block subsequent tests
|
|
265
|
+
reviewLog(`T2.5 — ${agent.label} answer`, '/blue|azure|cerulean/i', responseOutput, {
|
|
266
|
+
widgetId: agentWidgetId,
|
|
267
|
+
elapsed: `${(questionTimer.end?.duration || 0 / 1000).toFixed(1)}s`,
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Soft assert — record failure but continue
|
|
272
|
+
expect.soft(responseOutput).toMatch(/blue|azure|cerulean|cyan|sky\s*blue/i)
|
|
273
|
+
|
|
274
|
+
// Verify in browser
|
|
275
|
+
try {
|
|
276
|
+
const snap = browser.snapshot(browserSession)
|
|
277
|
+
// Best-effort: terminal content may not be in accessibility tree
|
|
278
|
+
} catch { /* browser check is best-effort */ }
|
|
279
|
+
}, 120_000)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// ─── Group 3: Agent Context Awareness ────────────────────────────
|
|
283
|
+
|
|
284
|
+
describe('Group 3: Context Awareness', () => {
|
|
285
|
+
it(`T3.1 — ${agent.label} terminal config exists and is valid`, async () => {
|
|
286
|
+
if (!agentWidgetId) return
|
|
287
|
+
|
|
288
|
+
// Wait a moment for config to be written
|
|
289
|
+
await new Promise((r) => setTimeout(r, 2000))
|
|
290
|
+
|
|
291
|
+
const config = readTerminalConfig(agentWidgetId)
|
|
292
|
+
|
|
293
|
+
expect(config).toBeDefined()
|
|
294
|
+
if (config) {
|
|
295
|
+
expect(config.widgetId).toBe(agentWidgetId)
|
|
296
|
+
expect(config.canvasId).toBeTruthy()
|
|
297
|
+
expect(config.branch).toBeTruthy()
|
|
298
|
+
expect(config.serverUrl).toBeTruthy()
|
|
299
|
+
// displayName may be set from prettyName
|
|
300
|
+
expect(config.displayName || config.widgetProps?.prettyName).toBeTruthy()
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it(`T3.2 — ${agent.label} can identify itself`, async () => {
|
|
305
|
+
if (!agentTmuxSession || !agentWidgetId) return
|
|
306
|
+
transcript.logSection(agentTmuxSession, 'T3.2')
|
|
307
|
+
|
|
308
|
+
const config = readTerminalConfig(agentWidgetId)
|
|
309
|
+
if (!config) return
|
|
310
|
+
|
|
311
|
+
// Ask the agent to read its config
|
|
312
|
+
tmux.sendText(
|
|
313
|
+
agentTmuxSession,
|
|
314
|
+
'Read your terminal config and tell me your widget ID, canvas ID, and display name. Reply with just those three values.',
|
|
315
|
+
)
|
|
316
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
317
|
+
|
|
318
|
+
// Wait for response containing the widget ID
|
|
319
|
+
let responseOutput = ''
|
|
320
|
+
try {
|
|
321
|
+
responseOutput = await tmux.waitForOutput(agentTmuxSession, new RegExp(agentWidgetId.slice(0, 12)), 90000, 3000)
|
|
322
|
+
} catch (err) {
|
|
323
|
+
responseOutput = err.lastCapture || tmux.capturePane(agentTmuxSession)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Soft assertions — agent response is non-deterministic
|
|
327
|
+
expect.soft(responseOutput).toMatch(new RegExp(agentWidgetId.slice(0, 12), 'i'))
|
|
328
|
+
|
|
329
|
+
if (!new RegExp(agentWidgetId.slice(0, 12), 'i').test(responseOutput)) {
|
|
330
|
+
reviewLog(`T3.2 — ${agent.label} self-identification`, agentWidgetId, responseOutput, { widgetId: agentWidgetId })
|
|
331
|
+
}
|
|
332
|
+
}, 120_000)
|
|
333
|
+
|
|
334
|
+
it(`T3.3 — ${agent.label} environment variables are injected`, async () => {
|
|
335
|
+
if (!agentTmuxSession) return
|
|
336
|
+
transcript.logSection(agentTmuxSession, 'T3.3')
|
|
337
|
+
|
|
338
|
+
// Check STORYBOARD_WIDGET_ID
|
|
339
|
+
tmux.sendText(agentTmuxSession, 'echo WIDGET_ID_CHECK:$STORYBOARD_WIDGET_ID')
|
|
340
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const output = await tmux.waitForOutput(agentTmuxSession, /WIDGET_ID_CHECK:/, 15000)
|
|
344
|
+
// The env var should contain the widget ID
|
|
345
|
+
if (agentWidgetId) {
|
|
346
|
+
expect.soft(output).toContain(agentWidgetId)
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
// Agent may not support direct shell commands — that's OK
|
|
350
|
+
console.log(`[info] ${agent.label} may not support direct echo — skipping env var check`)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check STORYBOARD_CANVAS_ID
|
|
354
|
+
tmux.sendText(agentTmuxSession, 'echo CANVAS_ID_CHECK:$STORYBOARD_CANVAS_ID')
|
|
355
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const output = await tmux.waitForOutput(agentTmuxSession, /CANVAS_ID_CHECK:/, 15000)
|
|
359
|
+
expect.soft(output).toMatch(/CANVAS_ID_CHECK:.+/)
|
|
360
|
+
} catch { /* best-effort */ }
|
|
361
|
+
}, 60_000)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
// ─── Group 4: Connected Widget CRUD ──────────────────────────────
|
|
365
|
+
|
|
366
|
+
describe('Group 4: Connected Widget CRUD', () => {
|
|
367
|
+
it(`T4.1 — Create sticky note and connect to ${agent.label}`, async () => {
|
|
368
|
+
if (!agentWidgetId) return
|
|
369
|
+
|
|
370
|
+
// Create a sticky note
|
|
371
|
+
const stickyResult = await canvasApi.createWidget(
|
|
372
|
+
CANVAS_NAME,
|
|
373
|
+
'sticky-note',
|
|
374
|
+
{ text: 'banana', color: 'yellow' },
|
|
375
|
+
{ x: 400, y: 100 + agents.indexOf(agent) * 500 },
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
expect(stickyResult.success).toBe(true)
|
|
379
|
+
stickyNoteId = stickyResult.widget.id
|
|
380
|
+
createdWidgetIds.push(stickyNoteId)
|
|
381
|
+
|
|
382
|
+
// Connect sticky note to agent widget
|
|
383
|
+
const connResult = await canvasApi.addConnector(
|
|
384
|
+
CANVAS_NAME,
|
|
385
|
+
stickyNoteId,
|
|
386
|
+
'right',
|
|
387
|
+
agentWidgetId,
|
|
388
|
+
'left',
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
expect(connResult.success).toBe(true)
|
|
392
|
+
connectorId = connResult.connector?.id
|
|
393
|
+
|
|
394
|
+
// Verify connector exists in canvas
|
|
395
|
+
const canvas = await canvasApi.readCanvas(CANVAS_NAME)
|
|
396
|
+
const conn = canvas.connectors?.find((c) => c.id === connectorId)
|
|
397
|
+
expect(conn).toBeDefined()
|
|
398
|
+
|
|
399
|
+
// Wait for terminal config to update with connected widgets
|
|
400
|
+
await new Promise((r) => setTimeout(r, 3000))
|
|
401
|
+
|
|
402
|
+
const config = readTerminalConfig(agentWidgetId)
|
|
403
|
+
if (config) {
|
|
404
|
+
// connectedWidgets should include the sticky note ID
|
|
405
|
+
const connected = config.connectedWidgets || config.connectedWidgetIds || []
|
|
406
|
+
const hasSticky = Array.isArray(connected)
|
|
407
|
+
? connected.some((w) => (typeof w === 'string' ? w === stickyNoteId : w.id === stickyNoteId))
|
|
408
|
+
: false
|
|
409
|
+
expect.soft(hasSticky).toBe(true)
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it(`T4.2 — ${agent.label} sees connected widget`, async () => {
|
|
414
|
+
if (!agentTmuxSession || !stickyNoteId) return
|
|
415
|
+
transcript.logSection(agentTmuxSession, 'T4.2')
|
|
416
|
+
|
|
417
|
+
tmux.sendText(
|
|
418
|
+
agentTmuxSession,
|
|
419
|
+
'What widgets are connected to you? Tell me the widget type and its text content.',
|
|
420
|
+
)
|
|
421
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
422
|
+
|
|
423
|
+
let responseOutput = ''
|
|
424
|
+
try {
|
|
425
|
+
responseOutput = await tmux.waitForOutput(agentTmuxSession, /banana|sticky/i, 90000, 3000)
|
|
426
|
+
} catch (err) {
|
|
427
|
+
responseOutput = err.lastCapture || tmux.capturePane(agentTmuxSession)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
expect.soft(responseOutput).toMatch(/banana/i)
|
|
431
|
+
|
|
432
|
+
if (!/banana/i.test(responseOutput)) {
|
|
433
|
+
reviewLog(`T4.2 — ${agent.label} connected widget`, '/banana/i', responseOutput, { widgetId: agentWidgetId })
|
|
434
|
+
}
|
|
435
|
+
}, 120_000)
|
|
436
|
+
|
|
437
|
+
it(`T4.3 — Edit connected widget text (banana → apple)`, async () => {
|
|
438
|
+
if (!stickyNoteId) return
|
|
439
|
+
|
|
440
|
+
// Update sticky note text
|
|
441
|
+
await canvasApi.updateWidget(CANVAS_NAME, stickyNoteId, {
|
|
442
|
+
props: { text: 'apple', color: 'yellow' },
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
// Verify update in canvas
|
|
446
|
+
const canvas = await canvasApi.readCanvas(CANVAS_NAME)
|
|
447
|
+
const sticky = canvas.widgets?.find((w) => w.id === stickyNoteId)
|
|
448
|
+
expect(sticky?.props?.text).toBe('apple')
|
|
449
|
+
|
|
450
|
+
// Ask agent about the update
|
|
451
|
+
if (!agentTmuxSession) return
|
|
452
|
+
transcript.logSection(agentTmuxSession, 'T4.3')
|
|
453
|
+
|
|
454
|
+
tmux.sendText(agentTmuxSession, 'What does your connected sticky note say now? Just tell me the text.')
|
|
455
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
456
|
+
|
|
457
|
+
let responseOutput = ''
|
|
458
|
+
try {
|
|
459
|
+
responseOutput = await tmux.waitForOutput(agentTmuxSession, /apple/i, 90000, 3000)
|
|
460
|
+
} catch (err) {
|
|
461
|
+
responseOutput = err.lastCapture || tmux.capturePane(agentTmuxSession)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
expect.soft(responseOutput).toMatch(/apple/i)
|
|
465
|
+
|
|
466
|
+
if (!/apple/i.test(responseOutput)) {
|
|
467
|
+
reviewLog(`T4.3 — ${agent.label} edited widget`, '/apple/i', responseOutput, { widgetId: agentWidgetId })
|
|
468
|
+
}
|
|
469
|
+
}, 120_000)
|
|
470
|
+
|
|
471
|
+
it(`T4.4 — Edit connected widget color (yellow → red)`, async () => {
|
|
472
|
+
if (!stickyNoteId) return
|
|
473
|
+
|
|
474
|
+
await canvasApi.updateWidget(CANVAS_NAME, stickyNoteId, {
|
|
475
|
+
props: { text: 'apple', color: 'red' },
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
// Verify in canvas
|
|
479
|
+
const canvas = await canvasApi.readCanvas(CANVAS_NAME)
|
|
480
|
+
const sticky = canvas.widgets?.find((w) => w.id === stickyNoteId)
|
|
481
|
+
expect(sticky?.props?.color).toBe('red')
|
|
482
|
+
|
|
483
|
+
// Verify in browser
|
|
484
|
+
try {
|
|
485
|
+
browser.open(browserSession, `${serverUrl}/storyboard/canvas/${CANVAS_NAME}`)
|
|
486
|
+
browser.waitForLoad(browserSession, 'networkidle')
|
|
487
|
+
// Best-effort visual check
|
|
488
|
+
const snap = browser.snapshot(browserSession)
|
|
489
|
+
} catch { /* best-effort */ }
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
it(`T4.5 — Delete connected sticky note`, async () => {
|
|
493
|
+
if (!stickyNoteId) return
|
|
494
|
+
|
|
495
|
+
await canvasApi.deleteWidget(CANVAS_NAME, stickyNoteId)
|
|
496
|
+
|
|
497
|
+
// Verify widget is gone
|
|
498
|
+
const canvas = await canvasApi.readCanvas(CANVAS_NAME)
|
|
499
|
+
const sticky = canvas.widgets?.find((w) => w.id === stickyNoteId)
|
|
500
|
+
expect(sticky).toBeUndefined()
|
|
501
|
+
|
|
502
|
+
// Connector should be orphaned (widget it points to is gone)
|
|
503
|
+
// The connector may still exist in the data but one end is invalid
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
it(`T4.6 — Connected markdown block (variant)`, async () => {
|
|
507
|
+
if (!agentWidgetId) return
|
|
508
|
+
|
|
509
|
+
// Create markdown widget
|
|
510
|
+
const mdResult = await canvasApi.createWidget(
|
|
511
|
+
CANVAS_NAME,
|
|
512
|
+
'markdown',
|
|
513
|
+
{ content: '# Hello World' },
|
|
514
|
+
{ x: 400, y: 250 + agents.indexOf(agent) * 500 },
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
expect(mdResult.success).toBe(true)
|
|
518
|
+
markdownId = mdResult.widget.id
|
|
519
|
+
createdWidgetIds.push(markdownId)
|
|
520
|
+
|
|
521
|
+
// Connect to agent
|
|
522
|
+
const connResult = await canvasApi.addConnector(
|
|
523
|
+
CANVAS_NAME,
|
|
524
|
+
markdownId,
|
|
525
|
+
'right',
|
|
526
|
+
agentWidgetId,
|
|
527
|
+
'left',
|
|
528
|
+
)
|
|
529
|
+
expect(connResult.success).toBe(true)
|
|
530
|
+
markdownConnectorId = connResult.connector?.id
|
|
531
|
+
|
|
532
|
+
// Wait for config update
|
|
533
|
+
await new Promise((r) => setTimeout(r, 3000))
|
|
534
|
+
|
|
535
|
+
// Ask agent about connected widget
|
|
536
|
+
if (agentTmuxSession) {
|
|
537
|
+
transcript.logSection(agentTmuxSession, 'T4.6')
|
|
538
|
+
|
|
539
|
+
tmux.sendText(agentTmuxSession, 'What markdown content is connected to you? Just tell me the heading text.')
|
|
540
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
541
|
+
|
|
542
|
+
let responseOutput = ''
|
|
543
|
+
try {
|
|
544
|
+
responseOutput = await tmux.waitForOutput(agentTmuxSession, /hello\s*world/i, 90000, 3000)
|
|
545
|
+
} catch (err) {
|
|
546
|
+
responseOutput = err.lastCapture || tmux.capturePane(agentTmuxSession)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
expect.soft(responseOutput).toMatch(/hello\s*world/i)
|
|
550
|
+
|
|
551
|
+
if (!/hello\s*world/i.test(responseOutput)) {
|
|
552
|
+
reviewLog(`T4.6 — ${agent.label} markdown widget`, '/hello world/i', responseOutput, { widgetId: agentWidgetId })
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Update markdown content
|
|
557
|
+
await canvasApi.updateWidget(CANVAS_NAME, markdownId, {
|
|
558
|
+
props: { content: '# Goodbye World' },
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
await new Promise((r) => setTimeout(r, 2000))
|
|
562
|
+
|
|
563
|
+
if (agentTmuxSession) {
|
|
564
|
+
tmux.sendText(agentTmuxSession, 'What does your connected markdown say now?')
|
|
565
|
+
tmux.sendKeys(agentTmuxSession, 'Enter')
|
|
566
|
+
|
|
567
|
+
let responseOutput = ''
|
|
568
|
+
try {
|
|
569
|
+
responseOutput = await tmux.waitForOutput(agentTmuxSession, /goodbye\s*world/i, 90000, 3000)
|
|
570
|
+
} catch (err) {
|
|
571
|
+
responseOutput = err.lastCapture || tmux.capturePane(agentTmuxSession)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
expect.soft(responseOutput).toMatch(/goodbye\s*world/i)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Delete markdown
|
|
578
|
+
await canvasApi.deleteWidget(CANVAS_NAME, markdownId)
|
|
579
|
+
|
|
580
|
+
const canvas = await canvasApi.readCanvas(CANVAS_NAME)
|
|
581
|
+
const md = canvas.widgets?.find((w) => w.id === markdownId)
|
|
582
|
+
expect(md).toBeUndefined()
|
|
583
|
+
}, 180_000)
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
// End the chain timer in the last afterAll
|
|
587
|
+
afterAll(() => {
|
|
588
|
+
if (agentDescribe.__chainTimer) {
|
|
589
|
+
agentDescribe.__chainTimer.end()
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
})
|
|
593
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-browser helpers for integration tests.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the agent-browser CLI for headless browser interaction.
|
|
5
|
+
* Each agent chain should use its own session name.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from 'node:child_process'
|
|
9
|
+
import * as perf from './perf.js'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TIMEOUT = 30000
|
|
12
|
+
|
|
13
|
+
function run(args, timeout = DEFAULT_TIMEOUT) {
|
|
14
|
+
try {
|
|
15
|
+
return execSync(`agent-browser ${args}`, { encoding: 'utf8', timeout })
|
|
16
|
+
} catch (err) {
|
|
17
|
+
const msg = err.stderr || err.stdout || err.message
|
|
18
|
+
throw new Error(`[agent-browser] ${args} failed: ${msg}`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Open a URL in a browser session. */
|
|
23
|
+
export function open(sessionName, url) {
|
|
24
|
+
const timer = perf.start('browser.open', { session: sessionName, url })
|
|
25
|
+
const out = run(`--session ${sessionName} open "${url}"`)
|
|
26
|
+
timer.end()
|
|
27
|
+
return out
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Take an accessibility snapshot. */
|
|
31
|
+
export function snapshot(sessionName, options = '') {
|
|
32
|
+
const timer = perf.start('browser.snapshot', { session: sessionName })
|
|
33
|
+
const out = run(`--session ${sessionName} snapshot ${options}`)
|
|
34
|
+
timer.end()
|
|
35
|
+
return out
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Take a screenshot. */
|
|
39
|
+
export function screenshot(sessionName, path, options = '') {
|
|
40
|
+
const timer = perf.start('browser.screenshot', { session: sessionName })
|
|
41
|
+
const out = run(`--session ${sessionName} screenshot ${options} ${path}`)
|
|
42
|
+
timer.end()
|
|
43
|
+
return out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Check if an element is visible. */
|
|
47
|
+
export function isVisible(sessionName, selector) {
|
|
48
|
+
try {
|
|
49
|
+
const out = run(`--session ${sessionName} is visible "${selector}"`)
|
|
50
|
+
return out.toLowerCase().includes('true') || out.toLowerCase().includes('visible')
|
|
51
|
+
} catch {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Get text content of an element. */
|
|
57
|
+
export function getText(sessionName, selectorOrRef) {
|
|
58
|
+
return run(`--session ${sessionName} get text "${selectorOrRef}"`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Click an element. */
|
|
62
|
+
export function click(sessionName, selectorOrRef) {
|
|
63
|
+
return run(`--session ${sessionName} click "${selectorOrRef}"`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Fill an input with text. */
|
|
67
|
+
export function fill(sessionName, selectorOrRef, text) {
|
|
68
|
+
return run(`--session ${sessionName} fill "${selectorOrRef}" "${text}"`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Wait for a specific condition. */
|
|
72
|
+
export function waitForLoad(sessionName, condition = 'networkidle') {
|
|
73
|
+
const timer = perf.start('browser.wait', { session: sessionName, condition })
|
|
74
|
+
const out = run(`--session ${sessionName} wait --load ${condition}`, 60000)
|
|
75
|
+
timer.end()
|
|
76
|
+
return out
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Close a browser session. */
|
|
80
|
+
export function close(sessionName) {
|
|
81
|
+
try {
|
|
82
|
+
run(`--session ${sessionName} close`)
|
|
83
|
+
} catch {
|
|
84
|
+
// Session may already be closed
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Close all browser sessions. */
|
|
89
|
+
export function closeAll() {
|
|
90
|
+
try {
|
|
91
|
+
run('close --all')
|
|
92
|
+
} catch {
|
|
93
|
+
// Nothing to close
|
|
94
|
+
}
|
|
95
|
+
}
|