@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.
Files changed (414) hide show
  1. package/commandpalette.config.json +109 -24
  2. package/dist/storyboard-ui.css +1 -1
  3. package/dist/storyboard-ui.js +17379 -28568
  4. package/dist/storyboard-ui.js.map +1 -1
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +5 -2
  7. package/scaffold/agents/prompt-agent.agent.md +181 -0
  8. package/scaffold/agents/terminal-agent.agent.md +351 -0
  9. package/scaffold/codex/config.toml +246 -0
  10. package/scaffold/manifest.json +5 -0
  11. package/scaffold/skills/canvas/SKILL.md +5 -4
  12. package/scaffold/skills/ship/SKILL.md +1 -1
  13. package/scaffold/storyboard.config.json +14 -1
  14. package/scaffold/toolbar.config.json +1 -1
  15. package/src/ActionMenuButton.jsx +100 -0
  16. package/src/AutosyncMenuButton.css +67 -0
  17. package/src/AutosyncMenuButton.jsx +241 -0
  18. package/src/BranchSelect.jsx +29 -0
  19. package/src/BranchSelect.module.css +30 -0
  20. package/src/CanvasAgentsMenu.jsx +87 -0
  21. package/src/CanvasCreateMenu.jsx +609 -0
  22. package/src/CanvasSnap.css +27 -0
  23. package/src/CanvasSnap.jsx +51 -0
  24. package/src/CanvasUndoRedo.css +36 -0
  25. package/src/CanvasUndoRedo.jsx +62 -0
  26. package/src/CanvasZoomControl.css +53 -0
  27. package/src/CanvasZoomControl.jsx +49 -0
  28. package/src/CanvasZoomToFit.css +18 -0
  29. package/src/CanvasZoomToFit.jsx +26 -0
  30. package/src/CommandMenu.css +8 -0
  31. package/src/CommandMenu.jsx +286 -0
  32. package/src/CommandPalette.jsx +35 -0
  33. package/src/CommandPaletteTrigger.jsx +25 -0
  34. package/src/CommentsMenuButton.jsx +38 -0
  35. package/src/CoreUIBar.css +47 -0
  36. package/src/CoreUIBar.jsx +855 -0
  37. package/src/CreateMenuButton.jsx +116 -0
  38. package/src/HideChromeTrigger.jsx +40 -0
  39. package/src/InspectorPanel.css +109 -0
  40. package/src/InspectorPanel.jsx +629 -0
  41. package/src/PwaInstallBanner.css +42 -0
  42. package/src/PwaInstallBanner.jsx +124 -0
  43. package/src/SidePanel.jsx +260 -0
  44. package/src/ThemeMenuButton.jsx +136 -0
  45. package/src/autosync/server.js +202 -5
  46. package/src/autosync/server.test.js +112 -0
  47. package/src/canvas/__tests__/agent-integration.test.js +593 -0
  48. package/src/canvas/__tests__/helpers/browser.js +95 -0
  49. package/src/canvas/__tests__/helpers/canvas-api.js +129 -0
  50. package/src/canvas/__tests__/helpers/perf.js +118 -0
  51. package/src/canvas/__tests__/helpers/setup.js +176 -0
  52. package/src/canvas/__tests__/helpers/tmux.js +130 -0
  53. package/src/canvas/__tests__/helpers/transcript.js +129 -0
  54. package/src/canvas/__tests__/terminal-integration.test.js +175 -0
  55. package/src/canvas/hot-pool.js +757 -0
  56. package/src/canvas/materializer.js +31 -0
  57. package/src/canvas/materializer.test.js +56 -0
  58. package/src/canvas/selectedWidgets.js +65 -7
  59. package/src/canvas/server.js +1801 -22
  60. package/src/canvas/server.test.js +239 -0
  61. package/src/canvas/terminal-config.js +331 -0
  62. package/src/canvas/terminal-registry.js +38 -0
  63. package/src/canvas/terminal-server.js +1037 -29
  64. package/src/canvas/writeGuard.js +51 -3
  65. package/src/canvasConfig.js +67 -1
  66. package/src/canvasConfig.test.js +79 -1
  67. package/src/cli/agent.js +85 -0
  68. package/src/cli/branch.js +232 -0
  69. package/src/cli/canvasAdd.js +59 -12
  70. package/src/cli/canvasBatch.js +98 -0
  71. package/src/cli/canvasBounds.js +1 -1
  72. package/src/cli/canvasRead.js +1 -1
  73. package/src/cli/canvasUpdate.js +179 -0
  74. package/src/cli/create.js +38 -14
  75. package/src/cli/dev.js +157 -83
  76. package/src/cli/exit.js +23 -24
  77. package/src/cli/index.js +55 -2
  78. package/src/cli/proxy.js +96 -37
  79. package/src/cli/schemas.js +22 -4
  80. package/src/cli/server.js +148 -25
  81. package/src/cli/serverUrl.js +8 -3
  82. package/src/cli/sessions.js +131 -5
  83. package/src/cli/setup.js +109 -11
  84. package/src/cli/terminal-commands.js +16 -8
  85. package/src/cli/terminal-messaging.js +231 -0
  86. package/src/cli/terminal-welcome.js +365 -33
  87. package/src/commandActions.js +1 -0
  88. package/src/commandPaletteConfig.js +9 -0
  89. package/src/comments/auth.js +2 -1
  90. package/src/comments/ui/AuthModal.jsx +114 -0
  91. package/src/comments/ui/CommentWindow.jsx +329 -0
  92. package/src/comments/ui/CommentsDrawer.jsx +102 -0
  93. package/src/comments/ui/Composer.jsx +64 -0
  94. package/src/comments/ui/authModal.test.js +1 -1
  95. package/src/comments/ui/commentWindow.js +16 -17
  96. package/src/comments/ui/commentsDrawer.js +25 -26
  97. package/src/comments/ui/composer.js +23 -24
  98. package/src/comments/ui/index.js +2 -3
  99. package/src/configSchema.js +59 -1
  100. package/src/configStore.js +161 -0
  101. package/src/core-ui-colors.css +12 -0
  102. package/src/devtools.js +17 -19
  103. package/src/devtools.test.js +18 -9
  104. package/src/featureFlags.js +12 -5
  105. package/src/fuzzySearch.test.js +10 -0
  106. package/src/index.js +14 -2
  107. package/src/lib/components/ui/alert/alert-action.jsx +11 -0
  108. package/src/lib/components/ui/alert/alert-description.jsx +11 -0
  109. package/src/lib/components/ui/alert/alert-title.jsx +11 -0
  110. package/src/lib/components/ui/alert/alert.jsx +25 -0
  111. package/src/lib/components/ui/alert/index.js +15 -15
  112. package/src/lib/components/ui/avatar/avatar-badge.jsx +22 -0
  113. package/src/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
  114. package/src/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
  115. package/src/lib/components/ui/avatar/avatar-group.jsx +19 -0
  116. package/src/lib/components/ui/avatar/avatar-image.jsx +15 -0
  117. package/src/lib/components/ui/avatar/avatar.jsx +19 -0
  118. package/src/lib/components/ui/avatar/index.js +20 -20
  119. package/src/lib/components/ui/badge/badge.jsx +31 -0
  120. package/src/lib/components/ui/badge/index.js +2 -2
  121. package/src/lib/components/ui/button/button.jsx +100 -0
  122. package/src/lib/components/ui/button/index.js +9 -9
  123. package/src/lib/components/ui/card/card-action.jsx +11 -0
  124. package/src/lib/components/ui/card/card-content.jsx +11 -0
  125. package/src/lib/components/ui/card/card-description.jsx +11 -0
  126. package/src/lib/components/ui/card/card-footer.jsx +11 -0
  127. package/src/lib/components/ui/card/card-header.jsx +19 -0
  128. package/src/lib/components/ui/card/card-title.jsx +11 -0
  129. package/src/lib/components/ui/card/card.jsx +17 -0
  130. package/src/lib/components/ui/card/index.js +23 -23
  131. package/src/lib/components/ui/checkbox/checkbox.jsx +29 -0
  132. package/src/lib/components/ui/checkbox/index.js +5 -5
  133. package/src/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
  134. package/src/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
  135. package/src/lib/components/ui/collapsible/collapsible.jsx +7 -0
  136. package/src/lib/components/ui/collapsible/index.js +11 -11
  137. package/src/lib/components/ui/dialog/dialog-close.jsx +7 -0
  138. package/src/lib/components/ui/dialog/dialog-content.jsx +34 -0
  139. package/src/lib/components/ui/dialog/dialog-description.jsx +15 -0
  140. package/src/lib/components/ui/dialog/dialog-footer.jsx +23 -0
  141. package/src/lib/components/ui/dialog/dialog-header.jsx +11 -0
  142. package/src/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
  143. package/src/lib/components/ui/dialog/dialog-portal.jsx +4 -0
  144. package/src/lib/components/ui/dialog/dialog-title.jsx +15 -0
  145. package/src/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
  146. package/src/lib/components/ui/dialog/dialog.jsx +4 -0
  147. package/src/lib/components/ui/dialog/index.js +32 -32
  148. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
  149. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
  150. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
  151. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
  152. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
  153. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
  154. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
  155. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
  156. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
  157. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
  158. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
  159. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
  160. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
  161. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
  162. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
  163. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
  164. package/src/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
  165. package/src/lib/components/ui/dropdown-menu/index.js +52 -52
  166. package/src/lib/components/ui/input/index.js +5 -5
  167. package/src/lib/components/ui/input/input.jsx +19 -0
  168. package/src/lib/components/ui/label/index.js +5 -5
  169. package/src/lib/components/ui/label/label.jsx +19 -0
  170. package/src/lib/components/ui/panel/index.js +21 -21
  171. package/src/lib/components/ui/panel/panel-body.jsx +11 -0
  172. package/src/lib/components/ui/panel/panel-close.jsx +16 -0
  173. package/src/lib/components/ui/panel/panel-content.jsx +29 -0
  174. package/src/lib/components/ui/panel/panel-footer.jsx +11 -0
  175. package/src/lib/components/ui/panel/panel-header.jsx +11 -0
  176. package/src/lib/components/ui/panel/panel-title.jsx +12 -0
  177. package/src/lib/components/ui/panel/panel.jsx +4 -0
  178. package/src/lib/components/ui/popover/index.js +26 -26
  179. package/src/lib/components/ui/popover/popover-close.jsx +7 -0
  180. package/src/lib/components/ui/popover/popover-content.jsx +22 -0
  181. package/src/lib/components/ui/popover/popover-description.jsx +11 -0
  182. package/src/lib/components/ui/popover/popover-header.jsx +11 -0
  183. package/src/lib/components/ui/popover/popover-portal.jsx +4 -0
  184. package/src/lib/components/ui/popover/popover-title.jsx +11 -0
  185. package/src/lib/components/ui/popover/popover-trigger.jsx +8 -0
  186. package/src/lib/components/ui/popover/popover.jsx +4 -0
  187. package/src/lib/components/ui/searchable-list.jsx +159 -0
  188. package/src/lib/components/ui/select/index.js +35 -35
  189. package/src/lib/components/ui/select/select-content.jsx +30 -0
  190. package/src/lib/components/ui/select/select-group-heading.jsx +17 -0
  191. package/src/lib/components/ui/select/select-group.jsx +15 -0
  192. package/src/lib/components/ui/select/select-item.jsx +26 -0
  193. package/src/lib/components/ui/select/select-label.jsx +11 -0
  194. package/src/lib/components/ui/select/select-portal.jsx +4 -0
  195. package/src/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
  196. package/src/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
  197. package/src/lib/components/ui/select/select-separator.jsx +15 -0
  198. package/src/lib/components/ui/select/select-trigger.jsx +25 -0
  199. package/src/lib/components/ui/select/select.jsx +4 -0
  200. package/src/lib/components/ui/separator/index.js +5 -5
  201. package/src/lib/components/ui/separator/separator.jsx +22 -0
  202. package/src/lib/components/ui/sheet/index.js +32 -32
  203. package/src/lib/components/ui/sheet/sheet-close.jsx +7 -0
  204. package/src/lib/components/ui/sheet/sheet-content.jsx +35 -0
  205. package/src/lib/components/ui/sheet/sheet-description.jsx +15 -0
  206. package/src/lib/components/ui/sheet/sheet-footer.jsx +11 -0
  207. package/src/lib/components/ui/sheet/sheet-header.jsx +11 -0
  208. package/src/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
  209. package/src/lib/components/ui/sheet/sheet-portal.jsx +4 -0
  210. package/src/lib/components/ui/sheet/sheet-title.jsx +15 -0
  211. package/src/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
  212. package/src/lib/components/ui/sheet/sheet.jsx +4 -0
  213. package/src/lib/components/ui/textarea/index.js +5 -5
  214. package/src/lib/components/ui/textarea/textarea.jsx +18 -0
  215. package/src/lib/components/ui/toggle/index.js +6 -9
  216. package/src/lib/components/ui/toggle/toggle.jsx +36 -0
  217. package/src/lib/components/ui/toggle-group/index.js +8 -8
  218. package/src/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
  219. package/src/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
  220. package/src/lib/components/ui/tooltip/index.js +3 -3
  221. package/src/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
  222. package/src/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
  223. package/src/lib/components/ui/tooltip/tooltip.jsx +11 -0
  224. package/src/lib/components/ui/trigger-button/index.js +3 -3
  225. package/src/lib/components/ui/trigger-button/trigger-button.css +38 -0
  226. package/src/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
  227. package/src/logger/devLogger.js +238 -0
  228. package/src/logger/devLogger.test.js +193 -0
  229. package/src/modes.test.js +4 -4
  230. package/src/mountStoryboardCore.js +123 -27
  231. package/src/paletteProviders.js +3 -0
  232. package/src/paletteProviders.test.js +2 -2
  233. package/src/server/index.js +98 -36
  234. package/src/sidepanel.css +214 -0
  235. package/src/styles/tailwind.css +1 -1
  236. package/src/svelte-plugin-ui/__tests__/ModeSwitch.test.ts +8 -8
  237. package/src/svelte-plugin-ui/__tests__/ToolbarShell.test.ts +11 -10
  238. package/src/svelte-plugin-ui/components/Icon.css +11 -0
  239. package/src/svelte-plugin-ui/components/Icon.jsx +281 -0
  240. package/src/svelte-plugin-ui/components/ModeSwitch.css +90 -0
  241. package/src/svelte-plugin-ui/components/ModeSwitch.jsx +47 -0
  242. package/src/svelte-plugin-ui/components/ToolbarShell.css +80 -0
  243. package/src/svelte-plugin-ui/components/ToolbarShell.jsx +84 -0
  244. package/src/svelte-plugin-ui/components/Viewfinder.css +412 -0
  245. package/src/svelte-plugin-ui/components/Viewfinder.jsx +512 -0
  246. package/src/svelte-plugin-ui/mount.ts +12 -16
  247. package/src/toolRegistry.js +4 -4
  248. package/src/toolbarConfigStore.js +30 -0
  249. package/src/tools/handlers/autosync.js +1 -1
  250. package/src/tools/handlers/canvasAddWidget.js +1 -1
  251. package/src/tools/handlers/canvasAgents.js +19 -0
  252. package/src/tools/handlers/canvasToolbar.js +8 -8
  253. package/src/tools/handlers/commandPalette.js +9 -0
  254. package/src/tools/handlers/comments.js +1 -1
  255. package/src/tools/handlers/create.js +1 -1
  256. package/src/tools/handlers/devtools.js +16 -0
  257. package/src/tools/handlers/devtools.test.js +38 -0
  258. package/src/tools/handlers/flows.js +1 -1
  259. package/src/tools/handlers/hideChrome.js +9 -0
  260. package/src/tools/handlers/paletteTheme.js +35 -0
  261. package/src/tools/handlers/theme.js +1 -1
  262. package/src/tools/registry.js +4 -1
  263. package/src/tools/surfaces/commandList.js +3 -3
  264. package/src/tools/surfaces/mainToolbar.js +3 -3
  265. package/src/tools/surfaces/registry.js +4 -4
  266. package/src/ui/design-modes.ts +2 -2
  267. package/src/ui/viewfinder.ts +1 -1
  268. package/src/vite/server-plugin.js +242 -60
  269. package/src/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
  270. package/src/workshop/features/createCanvas/index.js +1 -1
  271. package/src/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
  272. package/src/workshop/features/createFlow/index.js +1 -1
  273. package/src/workshop/features/createPage/CreatePageForm.jsx +304 -0
  274. package/src/workshop/features/createPage/index.js +1 -1
  275. package/src/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
  276. package/src/workshop/features/createPrototype/index.js +1 -1
  277. package/src/workshop/features/createPrototype/server.js +98 -0
  278. package/src/workshop/features/createStory/CreateStoryForm.jsx +208 -0
  279. package/src/workshop/features/createStory/index.js +1 -1
  280. package/src/workshop/ui/WorkshopPanel.jsx +98 -0
  281. package/src/workshop/ui/mount.ts +1 -1
  282. package/src/worktree/port.js +48 -0
  283. package/src/worktree/serverRegistry.js +120 -0
  284. package/toolbar.config.json +93 -42
  285. package/widgets.config.json +580 -12
  286. package/src/ActionMenuButton.svelte +0 -119
  287. package/src/AutosyncMenuButton.svelte +0 -397
  288. package/src/CanvasCreateMenu.svelte +0 -295
  289. package/src/CanvasSnap.svelte +0 -87
  290. package/src/CanvasUndoRedo.svelte +0 -108
  291. package/src/CanvasZoomControl.svelte +0 -111
  292. package/src/CanvasZoomToFit.svelte +0 -52
  293. package/src/CommandMenu.svelte +0 -249
  294. package/src/CommandPalette.svelte +0 -33
  295. package/src/CommentsMenuButton.svelte +0 -53
  296. package/src/CoreUIBar.svelte +0 -847
  297. package/src/CreateMenuButton.svelte +0 -133
  298. package/src/DocPanel.svelte +0 -299
  299. package/src/InspectorPanel.svelte +0 -745
  300. package/src/PwaInstallBanner.svelte +0 -124
  301. package/src/SidePanel.svelte +0 -480
  302. package/src/ThemeMenuButton.svelte +0 -132
  303. package/src/comments/ui/AuthModal.svelte +0 -108
  304. package/src/comments/ui/CommentWindow.svelte +0 -333
  305. package/src/comments/ui/CommentsDrawer.svelte +0 -96
  306. package/src/comments/ui/Composer.svelte +0 -65
  307. package/src/lib/components/ui/alert/alert-action.svelte +0 -19
  308. package/src/lib/components/ui/alert/alert-description.svelte +0 -22
  309. package/src/lib/components/ui/alert/alert-title.svelte +0 -22
  310. package/src/lib/components/ui/alert/alert.svelte +0 -38
  311. package/src/lib/components/ui/avatar/avatar-badge.svelte +0 -25
  312. package/src/lib/components/ui/avatar/avatar-fallback.svelte +0 -20
  313. package/src/lib/components/ui/avatar/avatar-group-count.svelte +0 -22
  314. package/src/lib/components/ui/avatar/avatar-group.svelte +0 -22
  315. package/src/lib/components/ui/avatar/avatar-image.svelte +0 -17
  316. package/src/lib/components/ui/avatar/avatar.svelte +0 -24
  317. package/src/lib/components/ui/badge/badge.svelte +0 -44
  318. package/src/lib/components/ui/button/button.svelte +0 -108
  319. package/src/lib/components/ui/card/card-action.svelte +0 -21
  320. package/src/lib/components/ui/card/card-content.svelte +0 -19
  321. package/src/lib/components/ui/card/card-description.svelte +0 -19
  322. package/src/lib/components/ui/card/card-footer.svelte +0 -18
  323. package/src/lib/components/ui/card/card-header.svelte +0 -21
  324. package/src/lib/components/ui/card/card-title.svelte +0 -14
  325. package/src/lib/components/ui/card/card.svelte +0 -21
  326. package/src/lib/components/ui/checkbox/checkbox.svelte +0 -39
  327. package/src/lib/components/ui/collapsible/collapsible-content.svelte +0 -7
  328. package/src/lib/components/ui/collapsible/collapsible-trigger.svelte +0 -7
  329. package/src/lib/components/ui/collapsible/collapsible.svelte +0 -11
  330. package/src/lib/components/ui/dialog/dialog-close.svelte +0 -11
  331. package/src/lib/components/ui/dialog/dialog-content.svelte +0 -42
  332. package/src/lib/components/ui/dialog/dialog-description.svelte +0 -17
  333. package/src/lib/components/ui/dialog/dialog-footer.svelte +0 -29
  334. package/src/lib/components/ui/dialog/dialog-header.svelte +0 -19
  335. package/src/lib/components/ui/dialog/dialog-overlay.svelte +0 -17
  336. package/src/lib/components/ui/dialog/dialog-portal.svelte +0 -7
  337. package/src/lib/components/ui/dialog/dialog-title.svelte +0 -17
  338. package/src/lib/components/ui/dialog/dialog-trigger.svelte +0 -11
  339. package/src/lib/components/ui/dialog/dialog.svelte +0 -7
  340. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +0 -16
  341. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -40
  342. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -27
  343. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +0 -18
  344. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +0 -7
  345. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -24
  346. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +0 -20
  347. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +0 -7
  348. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +0 -16
  349. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +0 -34
  350. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -17
  351. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +0 -19
  352. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +0 -17
  353. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +0 -27
  354. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +0 -7
  355. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -7
  356. package/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +0 -7
  357. package/src/lib/components/ui/input/input.svelte +0 -40
  358. package/src/lib/components/ui/label/label.svelte +0 -20
  359. package/src/lib/components/ui/panel/panel-body.svelte +0 -13
  360. package/src/lib/components/ui/panel/panel-close.svelte +0 -16
  361. package/src/lib/components/ui/panel/panel-content.svelte +0 -33
  362. package/src/lib/components/ui/panel/panel-footer.svelte +0 -13
  363. package/src/lib/components/ui/panel/panel-header.svelte +0 -16
  364. package/src/lib/components/ui/panel/panel-title.svelte +0 -14
  365. package/src/lib/components/ui/panel/panel.svelte +0 -15
  366. package/src/lib/components/ui/popover/popover-close.svelte +0 -7
  367. package/src/lib/components/ui/popover/popover-content.svelte +0 -27
  368. package/src/lib/components/ui/popover/popover-description.svelte +0 -19
  369. package/src/lib/components/ui/popover/popover-header.svelte +0 -19
  370. package/src/lib/components/ui/popover/popover-portal.svelte +0 -7
  371. package/src/lib/components/ui/popover/popover-title.svelte +0 -19
  372. package/src/lib/components/ui/popover/popover-trigger.svelte +0 -17
  373. package/src/lib/components/ui/popover/popover.svelte +0 -7
  374. package/src/lib/components/ui/select/select-content.svelte +0 -40
  375. package/src/lib/components/ui/select/select-group-heading.svelte +0 -19
  376. package/src/lib/components/ui/select/select-group.svelte +0 -17
  377. package/src/lib/components/ui/select/select-item.svelte +0 -38
  378. package/src/lib/components/ui/select/select-label.svelte +0 -18
  379. package/src/lib/components/ui/select/select-portal.svelte +0 -7
  380. package/src/lib/components/ui/select/select-scroll-down-button.svelte +0 -20
  381. package/src/lib/components/ui/select/select-scroll-up-button.svelte +0 -20
  382. package/src/lib/components/ui/select/select-separator.svelte +0 -17
  383. package/src/lib/components/ui/select/select-trigger.svelte +0 -27
  384. package/src/lib/components/ui/select/select.svelte +0 -11
  385. package/src/lib/components/ui/separator/separator.svelte +0 -23
  386. package/src/lib/components/ui/sheet/sheet-close.svelte +0 -7
  387. package/src/lib/components/ui/sheet/sheet-content.svelte +0 -43
  388. package/src/lib/components/ui/sheet/sheet-description.svelte +0 -17
  389. package/src/lib/components/ui/sheet/sheet-footer.svelte +0 -18
  390. package/src/lib/components/ui/sheet/sheet-header.svelte +0 -19
  391. package/src/lib/components/ui/sheet/sheet-overlay.svelte +0 -17
  392. package/src/lib/components/ui/sheet/sheet-portal.svelte +0 -7
  393. package/src/lib/components/ui/sheet/sheet-title.svelte +0 -17
  394. package/src/lib/components/ui/sheet/sheet-trigger.svelte +0 -7
  395. package/src/lib/components/ui/sheet/sheet.svelte +0 -7
  396. package/src/lib/components/ui/textarea/textarea.svelte +0 -21
  397. package/src/lib/components/ui/toggle/toggle.svelte +0 -45
  398. package/src/lib/components/ui/toggle-group/toggle-group-item.svelte +0 -35
  399. package/src/lib/components/ui/toggle-group/toggle-group.svelte +0 -63
  400. package/src/lib/components/ui/tooltip/tooltip-content.svelte +0 -24
  401. package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +0 -27
  402. package/src/lib/components/ui/tooltip/tooltip.svelte +0 -9
  403. package/src/lib/components/ui/trigger-button/trigger-button.svelte +0 -106
  404. package/src/svelte-plugin-ui/components/Icon.svelte +0 -181
  405. package/src/svelte-plugin-ui/components/ModeSwitch.svelte +0 -121
  406. package/src/svelte-plugin-ui/components/ToolbarShell.svelte +0 -150
  407. package/src/svelte-plugin-ui/components/Viewfinder.svelte +0 -1001
  408. package/src/tools/handlers/docs.js +0 -11
  409. package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +0 -139
  410. package/src/workshop/features/createFlow/CreateFlowForm.svelte +0 -314
  411. package/src/workshop/features/createPage/CreatePageForm.svelte +0 -249
  412. package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +0 -287
  413. package/src/workshop/features/createStory/CreateStoryForm.svelte +0 -161
  414. package/src/workshop/ui/WorkshopPanel.svelte +0 -97
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Canvas HTTP API helpers for integration tests.
3
+ *
4
+ * Wraps fetch calls to /_storyboard/canvas/* endpoints.
5
+ * Every call is automatically timed via perf.js.
6
+ */
7
+
8
+ import * as perf from './perf.js'
9
+
10
+ let baseUrl = 'http://localhost:1234'
11
+
12
+ /** Set the base URL for all API calls. */
13
+ export function setBaseUrl(url) {
14
+ baseUrl = url.replace(/\/$/, '')
15
+ }
16
+
17
+ /** Get the current base URL. */
18
+ export function getBaseUrl() {
19
+ return baseUrl
20
+ }
21
+
22
+ async function apiFetch(path, options = {}) {
23
+ const url = `${baseUrl}/_storyboard/canvas${path}`
24
+ const res = await fetch(url, {
25
+ headers: { 'Content-Type': 'application/json', ...options.headers },
26
+ ...options,
27
+ })
28
+ const text = await res.text()
29
+ let json
30
+ try {
31
+ json = JSON.parse(text)
32
+ } catch {
33
+ json = { raw: text }
34
+ }
35
+ if (!res.ok && !options.allowFailure) {
36
+ throw new Error(`API ${options.method || 'GET'} ${path} returned ${res.status}: ${JSON.stringify(json)}`)
37
+ }
38
+ return { status: res.status, ok: res.ok, data: json }
39
+ }
40
+
41
+ /** Create a widget on a canvas. */
42
+ export async function createWidget(canvasName, type, props = {}, position = { x: 0, y: 0 }) {
43
+ const timer = perf.start(`widget.create`, { type })
44
+ const res = await apiFetch('/widget', {
45
+ method: 'POST',
46
+ body: JSON.stringify({ name: canvasName, type, props, position }),
47
+ })
48
+ timer.end({ widgetId: res.data?.widget?.id })
49
+ return res.data
50
+ }
51
+
52
+ /** Read full canvas state. */
53
+ export async function readCanvas(canvasName) {
54
+ const timer = perf.start('canvas.read', { canvas: canvasName })
55
+ const res = await apiFetch(`/read?name=${encodeURIComponent(canvasName)}`)
56
+ timer.end({ widgetCount: res.data?.widgets?.length })
57
+ return res.data
58
+ }
59
+
60
+ /** Read a specific widget from a canvas. */
61
+ export async function readWidget(canvasName, widgetId) {
62
+ const timer = perf.start('canvas.readWidget', { canvas: canvasName, widgetId })
63
+ const res = await apiFetch(`/read?name=${encodeURIComponent(canvasName)}&widget=${encodeURIComponent(widgetId)}`)
64
+ timer.end()
65
+ return res.data
66
+ }
67
+
68
+ /** Update a widget's props and/or position. */
69
+ export async function updateWidget(canvasName, widgetId, { props, position } = {}) {
70
+ const timer = perf.start('widget.update', { widgetId })
71
+ const body = { name: canvasName, widgetId }
72
+ if (props) body.props = props
73
+ if (position) body.position = position
74
+ const res = await apiFetch('/widget', {
75
+ method: 'PATCH',
76
+ body: JSON.stringify(body),
77
+ })
78
+ timer.end()
79
+ return res.data
80
+ }
81
+
82
+ /** Delete a widget from a canvas. */
83
+ export async function deleteWidget(canvasName, widgetId) {
84
+ const timer = perf.start('widget.delete', { widgetId })
85
+ const res = await apiFetch('/widget', {
86
+ method: 'DELETE',
87
+ body: JSON.stringify({ name: canvasName, widgetId }),
88
+ })
89
+ timer.end()
90
+ return res.data
91
+ }
92
+
93
+ /** Add a connector between two widgets. */
94
+ export async function addConnector(canvasName, startWidgetId, startAnchor, endWidgetId, endAnchor) {
95
+ const timer = perf.start('connector.add')
96
+ const res = await apiFetch('/connector', {
97
+ method: 'POST',
98
+ body: JSON.stringify({ name: canvasName, startWidgetId, startAnchor, endWidgetId, endAnchor }),
99
+ })
100
+ timer.end({ connectorId: res.data?.connector?.id })
101
+ return res.data
102
+ }
103
+
104
+ /** Remove a connector. */
105
+ export async function removeConnector(canvasName, connectorId) {
106
+ const timer = perf.start('connector.remove')
107
+ const res = await apiFetch('/connector', {
108
+ method: 'DELETE',
109
+ body: JSON.stringify({ name: canvasName, connectorId }),
110
+ })
111
+ timer.end()
112
+ return res.data
113
+ }
114
+
115
+ /** List terminal sessions. */
116
+ export async function listTerminalSessions() {
117
+ const res = await apiFetch('/../terminal/sessions')
118
+ return res.data
119
+ }
120
+
121
+ /** Check if the dev server is reachable. */
122
+ export async function healthCheck() {
123
+ try {
124
+ const res = await fetch(`${baseUrl}/_storyboard/canvas/list`, { signal: AbortSignal.timeout(5000) })
125
+ return res.ok
126
+ } catch {
127
+ return false
128
+ }
129
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Performance timing and reporting for integration tests.
3
+ *
4
+ * Every timed operation records metric name, duration, and optional metadata.
5
+ * After all tests, call report() for a summary table and toJSON() for file output.
6
+ */
7
+
8
+ const metrics = []
9
+
10
+ const THRESHOLDS = {
11
+ 'widget.create': 1000,
12
+ 'widget.update': 1000,
13
+ 'widget.delete': 1000,
14
+ 'canvas.read': 1000,
15
+ 'connector.add': 1000,
16
+ 'connector.remove': 1000,
17
+ 'tmux.session.start': 5000,
18
+ 'tmux.welcome.render': 10000,
19
+ 'agent.startup': 60000,
20
+ 'agent.response': 45000,
21
+ 'agent.full_chain': 300000,
22
+ }
23
+
24
+ /** Start a timer. Returns { end() } that records the metric. */
25
+ export function start(name, meta = {}) {
26
+ const t0 = performance.now()
27
+ return {
28
+ end(extraMeta = {}) {
29
+ const duration = performance.now() - t0
30
+ const entry = { name, duration, ...meta, ...extraMeta, timestamp: new Date().toISOString() }
31
+ metrics.push(entry)
32
+
33
+ // Check threshold — emit warning inline
34
+ const thresholdKey = Object.keys(THRESHOLDS).find((k) => name.startsWith(k) || name === k)
35
+ if (thresholdKey && duration > THRESHOLDS[thresholdKey]) {
36
+ console.warn(`[SLOW] ${name}: ${(duration / 1000).toFixed(1)}s (threshold: ${(THRESHOLDS[thresholdKey] / 1000).toFixed(1)}s)`)
37
+ }
38
+
39
+ return entry
40
+ },
41
+ }
42
+ }
43
+
44
+ /** Manually record a metric. */
45
+ export function record(name, durationMs, meta = {}) {
46
+ metrics.push({ name, duration: durationMs, ...meta, timestamp: new Date().toISOString() })
47
+ }
48
+
49
+ /** Get all recorded metrics. */
50
+ export function getMetrics() {
51
+ return [...metrics]
52
+ }
53
+
54
+ /** Get metrics exceeding thresholds. */
55
+ export function getWarnings() {
56
+ return metrics.filter((m) => {
57
+ const key = Object.keys(THRESHOLDS).find((k) => m.name.startsWith(k) || m.name === k)
58
+ return key && m.duration > THRESHOLDS[key]
59
+ })
60
+ }
61
+
62
+ /** Print a summary table to console. */
63
+ export function report() {
64
+ if (metrics.length === 0) {
65
+ console.log('\n[perf] No metrics recorded.\n')
66
+ return
67
+ }
68
+
69
+ // Group by metric name
70
+ const groups = {}
71
+ for (const m of metrics) {
72
+ if (!groups[m.name]) groups[m.name] = []
73
+ groups[m.name].push(m.duration)
74
+ }
75
+
76
+ console.log('\n┌─────────────────────────────────────────────────────────────────┐')
77
+ console.log('│ Performance Summary │')
78
+ console.log('├───────────────────────────────┬───────┬───────┬───────┬─────────┤')
79
+ console.log('│ Metric │ Min │ Avg │ Max │ Warning │')
80
+ console.log('├───────────────────────────────┼───────┼───────┼───────┼─────────┤')
81
+
82
+ for (const [name, durations] of Object.entries(groups).sort((a, b) => a[0].localeCompare(b[0]))) {
83
+ const min = Math.min(...durations)
84
+ const max = Math.max(...durations)
85
+ const avg = durations.reduce((a, b) => a + b, 0) / durations.length
86
+ const thresholdKey = Object.keys(THRESHOLDS).find((k) => name.startsWith(k) || name === k)
87
+ const warn = thresholdKey && max > THRESHOLDS[thresholdKey] ? ' ⚠️' : ' ✓'
88
+ const pad = (s, n) => s.slice(0, n).padEnd(n)
89
+ const fmt = (ms) => {
90
+ if (ms < 1000) return `${Math.round(ms)}ms`.padStart(5)
91
+ return `${(ms / 1000).toFixed(1)}s`.padStart(5)
92
+ }
93
+ console.log(`│ ${pad(name, 29)} │ ${fmt(min)} │ ${fmt(avg)} │ ${fmt(max)} │ ${pad(warn, 7)} │`)
94
+ }
95
+
96
+ console.log('└───────────────────────────────┴───────┴───────┴───────┴─────────┘')
97
+
98
+ const warnings = getWarnings()
99
+ if (warnings.length > 0) {
100
+ console.log(`\n⚠️ ${warnings.length} operation(s) exceeded performance thresholds.`)
101
+ }
102
+ console.log('')
103
+ }
104
+
105
+ /** Return JSON-serializable metrics for file output. */
106
+ export function toJSON() {
107
+ return {
108
+ timestamp: new Date().toISOString(),
109
+ totalMetrics: metrics.length,
110
+ metrics: metrics.map((m) => ({ ...m, duration: Math.round(m.duration) })),
111
+ warnings: getWarnings().map((m) => ({ ...m, duration: Math.round(m.duration) })),
112
+ }
113
+ }
114
+
115
+ /** Reset all metrics (for test isolation). */
116
+ export function reset() {
117
+ metrics.length = 0
118
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Test setup helpers — canvas lifecycle, server URL, agent availability.
3
+ */
4
+
5
+ import { writeFileSync, unlinkSync, existsSync, readFileSync, mkdirSync } from 'node:fs'
6
+ import { join } from 'node:path'
7
+ import { execSync } from 'node:child_process'
8
+ import * as canvasApi from './canvas-api.js'
9
+
10
+ const ROOT = process.cwd()
11
+
12
+ /** Resolve the dev server URL from ports.json or worktree registry. */
13
+ export function resolveServerUrl() {
14
+ // Try .storyboard/ports.json
15
+ try {
16
+ const portsPath = join(ROOT, '.storyboard', 'ports.json')
17
+ if (existsSync(portsPath)) {
18
+ const ports = JSON.parse(readFileSync(portsPath, 'utf8'))
19
+ if (ports.vite) return `http://localhost:${ports.vite}`
20
+ }
21
+ } catch { /* ignore */ }
22
+
23
+ // Try worktree server registry
24
+ try {
25
+ const registryPath = join(ROOT, '.storyboard', 'servers.json')
26
+ if (existsSync(registryPath)) {
27
+ const servers = JSON.parse(readFileSync(registryPath, 'utf8'))
28
+ if (Array.isArray(servers) && servers.length > 0) {
29
+ return `http://localhost:${servers[0].port}`
30
+ }
31
+ }
32
+ } catch { /* ignore */ }
33
+
34
+ return 'http://localhost:1234'
35
+ }
36
+
37
+ /** Create a fresh test canvas file. Returns the canvas name. */
38
+ export function createTestCanvas(name = '__test__-terminal') {
39
+ const canvasDir = join(ROOT, 'src', 'canvas')
40
+ if (!existsSync(canvasDir)) mkdirSync(canvasDir, { recursive: true })
41
+
42
+ const filePath = join(canvasDir, `${name}.canvas.jsonl`)
43
+ const event = JSON.stringify({
44
+ event: 'canvas_created',
45
+ timestamp: new Date().toISOString(),
46
+ meta: { name, description: 'Integration test canvas' },
47
+ })
48
+ writeFileSync(filePath, event + '\n')
49
+ return name
50
+ }
51
+
52
+ /** Delete the test canvas file. */
53
+ export function deleteTestCanvas(name = '__test__-terminal') {
54
+ const filePath = join(ROOT, 'src', 'canvas', `${name}.canvas.jsonl`)
55
+ try {
56
+ if (existsSync(filePath)) unlinkSync(filePath)
57
+ } catch { /* ignore */ }
58
+ }
59
+
60
+ /**
61
+ * Load configured agents from storyboard.config.json.
62
+ * Returns array of { id, label, startupCommand }.
63
+ */
64
+ export function loadConfiguredAgents() {
65
+ try {
66
+ const raw = readFileSync(join(ROOT, 'storyboard.config.json'), 'utf8')
67
+ const config = JSON.parse(raw)
68
+ const agents = config?.canvas?.agents
69
+ if (!agents || typeof agents !== 'object') return []
70
+ return Object.entries(agents)
71
+ .map(([id, cfg]) => ({
72
+ id,
73
+ label: cfg.label || id,
74
+ startupCommand: cfg.startupCommand || null,
75
+ }))
76
+ .filter((a) => a.startupCommand)
77
+ } catch {
78
+ return []
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Check if an agent binary is available on this machine.
84
+ * Returns true if the binary is found, false otherwise.
85
+ */
86
+ export function checkAgentAvailability(agentId) {
87
+ // Map agent IDs to binary names
88
+ const binaryMap = {
89
+ copilot: 'copilot',
90
+ claude: 'claude',
91
+ }
92
+ const binary = binaryMap[agentId] || agentId
93
+ try {
94
+ execSync(`which ${binary} 2>/dev/null`, { encoding: 'utf8', timeout: 5000 })
95
+ return true
96
+ } catch {
97
+ return false
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Kill all tmux sessions for the test canvas.
103
+ * Finds sessions by the canvas name pattern in their tmux name.
104
+ */
105
+ export function cleanupTerminalSessions() {
106
+ try {
107
+ const out = execSync('tmux list-sessions -F "#{session_name}" 2>/dev/null', { encoding: 'utf8', timeout: 5000 })
108
+ const sessions = out.trim().split('\n').filter(Boolean)
109
+ for (const s of sessions) {
110
+ // Kill sessions that are from our test canvas (sb- prefix + test widget IDs)
111
+ if (s.startsWith('sb-')) {
112
+ // We can't know for sure which are test sessions, so we track widget IDs
113
+ // in the test and clean those up specifically. This is a fallback.
114
+ }
115
+ }
116
+ } catch { /* no tmux sessions */ }
117
+ }
118
+
119
+ /**
120
+ * Full preflight check. Verifies:
121
+ * 1. Dev server is reachable
122
+ * 2. tmux is available
123
+ * 3. agent-browser is available
124
+ *
125
+ * Throws with a clear message if any check fails.
126
+ */
127
+ export async function preflight() {
128
+ const url = resolveServerUrl()
129
+ canvasApi.setBaseUrl(url)
130
+
131
+ // Check dev server
132
+ const serverOk = await canvasApi.healthCheck()
133
+ if (!serverOk) {
134
+ throw new Error(
135
+ `[preflight] Dev server not reachable at ${url}. Start it with 'storyboard dev' or 'npm run dev' before running integration tests.`,
136
+ )
137
+ }
138
+
139
+ // Check tmux
140
+ try {
141
+ execSync('which tmux', { timeout: 5000 })
142
+ } catch {
143
+ throw new Error('[preflight] tmux is not installed. Install it with: brew install tmux')
144
+ }
145
+
146
+ // Check agent-browser
147
+ try {
148
+ execSync('which agent-browser', { timeout: 5000 })
149
+ } catch {
150
+ throw new Error('[preflight] agent-browser is not installed. Install it with: npm install -g agent-browser')
151
+ }
152
+
153
+ return { url }
154
+ }
155
+
156
+ /** Write the perf + summary results to test-results/. */
157
+ export function writeResults(perfData, transcriptPaths = []) {
158
+ const dir = join(ROOT, 'test-results')
159
+ mkdirSync(dir, { recursive: true })
160
+
161
+ // Write perf JSON
162
+ writeFileSync(join(dir, 'integration-perf.json'), JSON.stringify(perfData, null, 2))
163
+
164
+ // Write summary
165
+ const summary = [
166
+ `Integration Test Run — ${new Date().toISOString()}`,
167
+ '',
168
+ `Metrics recorded: ${perfData.totalMetrics}`,
169
+ `Warnings: ${perfData.warnings.length}`,
170
+ '',
171
+ 'Transcripts:',
172
+ ...transcriptPaths.map((p) => ` ${p}`),
173
+ '',
174
+ ].join('\n')
175
+ writeFileSync(join(dir, 'summary.txt'), summary)
176
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * tmux helpers for integration tests.
3
+ *
4
+ * Every operation is timed via perf.js and recorded via transcript.js.
5
+ */
6
+
7
+ import { execSync } from 'node:child_process'
8
+ import * as perf from './perf.js'
9
+ import * as transcript from './transcript.js'
10
+
11
+ /** List all tmux sessions. Returns array of session name strings. */
12
+ export function listSessions() {
13
+ try {
14
+ const out = execSync('tmux list-sessions -F "#{session_name}"', { encoding: 'utf8', timeout: 5000 })
15
+ return out.trim().split('\n').filter(Boolean)
16
+ } catch {
17
+ return []
18
+ }
19
+ }
20
+
21
+ /** Check if a tmux session exists. */
22
+ export function hasSession(name) {
23
+ try {
24
+ execSync(`tmux has-session -t "${name}" 2>/dev/null`, { timeout: 5000 })
25
+ return true
26
+ } catch {
27
+ return false
28
+ }
29
+ }
30
+
31
+ /** Capture the full pane content of a tmux session. */
32
+ export function capturePane(sessionName) {
33
+ const timer = perf.start('tmux.capture', { session: sessionName })
34
+ try {
35
+ const out = execSync(`tmux capture-pane -t "${sessionName}" -p`, { encoding: 'utf8', timeout: 10000 })
36
+ timer.end()
37
+ transcript.logStdout(sessionName, out)
38
+ return out
39
+ } catch (err) {
40
+ timer.end({ error: err.message })
41
+ return ''
42
+ }
43
+ }
44
+
45
+ /** Send keystrokes to a tmux session. */
46
+ export function sendKeys(sessionName, keys) {
47
+ transcript.logStdin(sessionName, keys)
48
+ try {
49
+ execSync(`tmux send-keys -t "${sessionName}" ${keys}`, { timeout: 5000 })
50
+ } catch (err) {
51
+ transcript.logEvent(sessionName, `sendKeys error: ${err.message}`)
52
+ throw err
53
+ }
54
+ }
55
+
56
+ /** Send literal text (properly escaped) to a tmux session. */
57
+ export function sendText(sessionName, text) {
58
+ transcript.logStdin(sessionName, text)
59
+ try {
60
+ // Use -- to prevent tmux from interpreting flags in the text
61
+ execSync(`tmux send-keys -t "${sessionName}" -l -- ${JSON.stringify(text)}`, { timeout: 5000 })
62
+ } catch (err) {
63
+ transcript.logEvent(sessionName, `sendText error: ${err.message}`)
64
+ throw err
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Poll capturePane until the output matches a pattern, or timeout.
70
+ * Returns the matched output on success, throws on timeout.
71
+ */
72
+ export async function waitForOutput(sessionName, pattern, timeoutMs = 30000, intervalMs = 1000) {
73
+ const timer = perf.start('tmux.waitForOutput', { session: sessionName, pattern: String(pattern) })
74
+ const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern
75
+ const deadline = Date.now() + timeoutMs
76
+
77
+ while (Date.now() < deadline) {
78
+ const output = capturePane(sessionName)
79
+ if (regex.test(output)) {
80
+ timer.end({ matched: true })
81
+ return output
82
+ }
83
+ await new Promise((r) => setTimeout(r, intervalMs))
84
+ }
85
+
86
+ // Final attempt
87
+ const output = capturePane(sessionName)
88
+ if (regex.test(output)) {
89
+ timer.end({ matched: true })
90
+ return output
91
+ }
92
+
93
+ timer.end({ matched: false, timedOut: true })
94
+ const err = new Error(`[tmux] waitForOutput timed out after ${timeoutMs}ms waiting for ${pattern}\nLast capture:\n${output}`)
95
+ err.lastCapture = output
96
+ throw err
97
+ }
98
+
99
+ /**
100
+ * Wait for a tmux session to appear. Polls tmux list-sessions.
101
+ * Returns the matching session name, or throws on timeout.
102
+ */
103
+ export async function waitForSession(namePattern, timeoutMs = 15000, intervalMs = 500) {
104
+ const timer = perf.start('tmux.session.start')
105
+ const regex = typeof namePattern === 'string' ? new RegExp(namePattern) : namePattern
106
+ const deadline = Date.now() + timeoutMs
107
+
108
+ while (Date.now() < deadline) {
109
+ const sessions = listSessions()
110
+ const match = sessions.find((s) => regex.test(s))
111
+ if (match) {
112
+ timer.end({ session: match })
113
+ return match
114
+ }
115
+ await new Promise((r) => setTimeout(r, intervalMs))
116
+ }
117
+
118
+ timer.end({ timedOut: true })
119
+ throw new Error(`[tmux] No session matching ${namePattern} appeared within ${timeoutMs}ms`)
120
+ }
121
+
122
+ /** Kill a tmux session. */
123
+ export function killSession(name) {
124
+ try {
125
+ execSync(`tmux kill-session -t "${name}" 2>/dev/null`, { timeout: 5000 })
126
+ transcript.logEvent(name, 'Session killed')
127
+ } catch {
128
+ // Session may already be dead
129
+ }
130
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Session transcript recording for integration tests.
3
+ *
4
+ * Wraps tmux interactions to produce a full stdin/stdout log per session.
5
+ * Transcripts are written to test-results/transcripts/ on every run.
6
+ */
7
+
8
+ import { mkdirSync, writeFileSync } from 'node:fs'
9
+ import { join } from 'node:path'
10
+
11
+ const transcripts = new Map()
12
+
13
+ const TRANSCRIPTS_DIR = join(process.cwd(), 'test-results', 'transcripts')
14
+
15
+ function ensureDir() {
16
+ mkdirSync(TRANSCRIPTS_DIR, { recursive: true })
17
+ }
18
+
19
+ function elapsed(startTime) {
20
+ const ms = Date.now() - startTime
21
+ const secs = Math.floor(ms / 1000)
22
+ const mins = Math.floor(secs / 60)
23
+ const s = secs % 60
24
+ const tenths = Math.floor((ms % 1000) / 100)
25
+ if (mins > 0) return `${String(mins).padStart(2, '0')}:${String(s).padStart(2, '0')}.${tenths}`
26
+ return `${String(s).padStart(2, '0')}.${tenths}`
27
+ }
28
+
29
+ /** Initialize a transcript for a session. */
30
+ export function createTranscript(sessionName, metadata = {}) {
31
+ const transcript = {
32
+ sessionName,
33
+ metadata,
34
+ startTime: Date.now(),
35
+ lines: [],
36
+ }
37
+ transcript.lines.push(`=== Session: ${sessionName} ===`)
38
+ transcript.lines.push(`Started: ${new Date().toISOString()}`)
39
+ if (metadata.widgetId) transcript.lines.push(`Widget ID: ${metadata.widgetId}`)
40
+ if (metadata.canvasName) transcript.lines.push(`Canvas: ${metadata.canvasName}`)
41
+ if (metadata.agentId) transcript.lines.push(`Agent: ${metadata.agentId}`)
42
+ transcript.lines.push('')
43
+ transcripts.set(sessionName, transcript)
44
+ return transcript
45
+ }
46
+
47
+ /** Log a test section header. */
48
+ export function logSection(sessionName, testId) {
49
+ const t = transcripts.get(sessionName)
50
+ if (!t) return
51
+ t.lines.push(`--- [${testId}] ---`)
52
+ }
53
+
54
+ /** Log stdin (input sent via sendKeys). */
55
+ export function logStdin(sessionName, input, testId = '') {
56
+ const t = transcripts.get(sessionName)
57
+ if (!t) return
58
+ const ts = elapsed(t.startTime)
59
+ const prefix = `[${ts}] [stdin] `
60
+ // Split multi-line input
61
+ const lines = String(input).split('\n')
62
+ for (const line of lines) {
63
+ t.lines.push(`${prefix}${line}`)
64
+ }
65
+ }
66
+
67
+ /** Log stdout (captured pane output). Only logs the diff from previous capture. */
68
+ export function logStdout(sessionName, fullOutput, testId = '') {
69
+ const t = transcripts.get(sessionName)
70
+ if (!t) return
71
+ const ts = elapsed(t.startTime)
72
+ const prefix = `[${ts}] [stdout] `
73
+
74
+ // Compute diff from last stdout
75
+ const prevLen = t._lastStdoutLen || 0
76
+ const lines = String(fullOutput).split('\n')
77
+ const newLines = lines.slice(prevLen)
78
+ t._lastStdoutLen = lines.length
79
+
80
+ for (const line of newLines) {
81
+ // Skip empty trailing lines
82
+ if (line.trim() === '' && newLines.indexOf(line) === newLines.length - 1) continue
83
+ t.lines.push(`${prefix}${line}`)
84
+ }
85
+ }
86
+
87
+ /** Log a non-IO event (session start, resize, etc.). */
88
+ export function logEvent(sessionName, event, testId = '') {
89
+ const t = transcripts.get(sessionName)
90
+ if (!t) return
91
+ const ts = elapsed(t.startTime)
92
+ t.lines.push(`[${ts}] [event] ${event}`)
93
+ }
94
+
95
+ /** Get the in-memory transcript text. */
96
+ export function getTranscript(sessionName) {
97
+ const t = transcripts.get(sessionName)
98
+ if (!t) return ''
99
+ return t.lines.join('\n')
100
+ }
101
+
102
+ /** Flush a single transcript to disk. */
103
+ export function flush(sessionName) {
104
+ const t = transcripts.get(sessionName)
105
+ if (!t) return null
106
+
107
+ const totalMs = Date.now() - t.startTime
108
+ t.lines.push('')
109
+ t.lines.push(`=== Session ended: ${new Date().toISOString()} (total: ${(totalMs / 1000).toFixed(1)}s) ===`)
110
+
111
+ ensureDir()
112
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
113
+ const filename = `${sessionName}-${ts}.log`
114
+ const filepath = join(TRANSCRIPTS_DIR, filename)
115
+ writeFileSync(filepath, t.lines.join('\n') + '\n')
116
+ console.log(`[transcript] Written: ${filepath}`)
117
+ return filepath
118
+ }
119
+
120
+ /** Flush all open transcripts. Call in afterAll. */
121
+ export function flushAll() {
122
+ const paths = []
123
+ for (const sessionName of transcripts.keys()) {
124
+ const p = flush(sessionName)
125
+ if (p) paths.push(p)
126
+ }
127
+ transcripts.clear()
128
+ return paths
129
+ }