@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
package/src/cli/proxy.js CHANGED
@@ -1,18 +1,22 @@
1
1
  /**
2
- * storyboard proxy — Generate Caddyfile from ports.json and start/reload Caddy.
2
+ * storyboard proxy — Manage the Caddy reverse proxy.
3
+ *
4
+ * Usage:
5
+ * storyboard proxy start Start or reload Caddy proxy
6
+ * storyboard proxy close Stop Caddy proxy
3
7
  *
4
8
  * Caddy listens on port 80 for storyboard.localhost, routing:
5
9
  * /storyboard/* → main (port 1234)
6
10
  * /<branchname>/storyboard/* → worktree (assigned port)
7
11
  *
8
12
  * Requires Caddy to be installed (brew install caddy).
9
- * Port 80 requires `sudo caddy start` — handled by `storyboard setup`.
10
13
  */
11
14
 
12
15
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
13
16
  import { execSync } from 'child_process'
14
17
  import { dirname, resolve } from 'path'
15
18
  import { portsFilePath } from '../worktree/port.js'
19
+ import { list as listRunningServers } from '../worktree/serverRegistry.js'
16
20
 
17
21
  export function readDevDomain(cwd) {
18
22
  try {
@@ -26,25 +30,48 @@ export function readDevDomain(cwd) {
26
30
 
27
31
  const DOMAIN = readDevDomain()
28
32
 
33
+ /**
34
+ * Build branch→port map from running servers (servers.json) + explicit overrides.
35
+ * Only includes branches with live processes — prevents stale routes from
36
+ * creating redirect loops when ports get reused by different Vite instances.
37
+ * When multiple servers run for the same worktree, latest startedAt wins.
38
+ */
39
+ function livePortMap(portOverrides = {}) {
40
+ const map = {}
41
+ try {
42
+ for (const srv of listRunningServers()) {
43
+ if (srv.worktree === 'main') continue
44
+ const existing = map[srv.worktree]
45
+ if (!existing || srv.startedAt > existing.startedAt) {
46
+ map[srv.worktree] = { port: srv.port, startedAt: srv.startedAt }
47
+ }
48
+ }
49
+ } catch { /* registry unavailable — rely on overrides only */ }
50
+
51
+ // Build final branch→port, overrides always win
52
+ const result = {}
53
+ for (const [name, info] of Object.entries(map)) result[name] = info.port
54
+ Object.assign(result, portOverrides)
55
+ // Remove 'main' if accidentally passed — handled separately
56
+ delete result.main
57
+ return result
58
+ }
59
+
29
60
  export function generateCaddyfile(portOverrides = {}) {
30
61
  const portsFile = portsFilePath()
31
62
  const dir = dirname(portsFile)
32
63
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
33
64
 
34
- let ports = { main: 1234 }
65
+ // Main port from ports.json (always 1234 unless overridden)
66
+ let mainPort = 1234
35
67
  if (existsSync(portsFile)) {
36
68
  try {
37
- ports = JSON.parse(readFileSync(portsFile, 'utf8'))
38
- } catch {
39
- // Corrupted use defaults
40
- }
69
+ const ports = JSON.parse(readFileSync(portsFile, 'utf8'))
70
+ mainPort = ports.main || 1234
71
+ } catch { /* use default */ }
41
72
  }
42
73
 
43
- // Apply runtime overrides (e.g. when assigned port was in use)
44
- Object.assign(ports, portOverrides)
45
-
46
- const mainPort = ports.main || 1234
47
- const branches = Object.entries(ports).filter(([name]) => name !== 'main')
74
+ const branches = Object.entries(livePortMap(portOverrides))
48
75
 
49
76
  // Build Caddy route blocks — branches first (more specific), main last (fallback)
50
77
  const branchBlocks = branches
@@ -104,15 +131,17 @@ const CADDY_ADMIN = 'http://localhost:2019'
104
131
  * Tagged with @id so it can be upserted independently of other repos.
105
132
  */
106
133
  export function generateRouteConfig(portOverrides = {}) {
134
+ // Main port from ports.json
107
135
  const portsFile = portsFilePath()
108
- let ports = { main: 1234 }
136
+ let mainPort = 1234
109
137
  if (existsSync(portsFile)) {
110
- try { ports = JSON.parse(readFileSync(portsFile, 'utf8')) } catch { /* use defaults */ }
138
+ try {
139
+ const ports = JSON.parse(readFileSync(portsFile, 'utf8'))
140
+ mainPort = ports.main || 1234
141
+ } catch { /* use default */ }
111
142
  }
112
- Object.assign(ports, portOverrides)
113
143
 
114
- const mainPort = ports.main || 1234
115
- const branches = Object.entries(ports).filter(([name]) => name !== 'main')
144
+ const branches = Object.entries(livePortMap(portOverrides))
116
145
 
117
146
  // Branch subroutes first (more specific), then main fallback
118
147
  const subroutes = branches.map(([name, port]) => ({
@@ -215,7 +244,16 @@ export function upsertCaddyRoute(routeConfig) {
215
244
 
216
245
  export function startCaddy(caddyfilePath) {
217
246
  try {
218
- execSync(`sudo caddy start --config "${caddyfilePath}" >/dev/null 2>&1`, { stdio: ['inherit', 'pipe', 'pipe'] })
247
+ execSync(`caddy start --config "${caddyfilePath}" >/dev/null 2>&1`, { stdio: ['inherit', 'pipe', 'pipe'] })
248
+ return true
249
+ } catch {
250
+ return false
251
+ }
252
+ }
253
+
254
+ export function stopCaddy() {
255
+ try {
256
+ execSync('curl -sf -X POST http://localhost:2019/stop', { timeout: 5000, stdio: 'ignore' })
219
257
  return true
220
258
  } catch {
221
259
  return false
@@ -226,35 +264,56 @@ export function startCaddy(caddyfilePath) {
226
264
  const isDirectRun = process.argv[2] === 'proxy'
227
265
  if (isDirectRun) {
228
266
  const { intro, outro, log, spinner } = await import('@clack/prompts')
229
- intro('storyboard proxy')
230
-
231
- const caddyfilePath = generateCaddyfile()
267
+ const subcommand = process.argv[3]
232
268
 
233
269
  if (!isCaddyInstalled()) {
270
+ intro('storyboard proxy')
234
271
  log.error('Caddy is not installed. Run: brew install caddy')
235
272
  process.exit(1)
236
273
  }
237
274
 
238
- const s = spinner()
239
- if (isCaddyRunning()) {
240
- s.start('Updating proxy routes...')
241
- const routeConfig = generateRouteConfig()
242
- if (upsertCaddyRoute(routeConfig)) {
243
- s.stop('Proxy routes updated (admin API)')
244
- } else if (reloadCaddy(caddyfilePath)) {
245
- s.stop('Proxy reloaded (Caddyfile fallback)')
275
+ if (subcommand === 'close' || subcommand === 'stop') {
276
+ intro('storyboard proxy close')
277
+ if (!isCaddyRunning()) {
278
+ log.info('Proxy is not running.')
279
+ outro('')
280
+ } else if (stopCaddy()) {
281
+ log.success('Proxy stopped.')
282
+ outro('')
246
283
  } else {
247
- s.stop('Failed to update proxy')
284
+ log.error('Failed to stop proxy.')
248
285
  process.exit(1)
249
286
  }
250
- } else {
251
- s.start('Starting proxy (requires sudo for port 80)...')
252
- if (startCaddy(caddyfilePath)) {
253
- s.stop('Proxy started')
287
+ } else if (subcommand === 'start' || !subcommand) {
288
+ intro('storyboard proxy start')
289
+ const caddyfilePath = generateCaddyfile()
290
+
291
+ const s = spinner()
292
+ if (isCaddyRunning()) {
293
+ s.start('Updating proxy routes...')
294
+ const routeConfig = generateRouteConfig()
295
+ if (upsertCaddyRoute(routeConfig)) {
296
+ s.stop('Proxy routes updated (admin API)')
297
+ } else if (reloadCaddy(caddyfilePath)) {
298
+ s.stop('Proxy reloaded (Caddyfile fallback)')
299
+ } else {
300
+ s.stop('Failed to update proxy')
301
+ process.exit(1)
302
+ }
254
303
  } else {
255
- s.stop('Failed to start')
256
- process.exit(1)
304
+ s.start('Starting proxy...')
305
+ if (startCaddy(caddyfilePath)) {
306
+ s.stop('Proxy started')
307
+ } else {
308
+ s.stop('Failed to start proxy')
309
+ process.exit(1)
310
+ }
257
311
  }
312
+ outro(`Proxy ready at http://${DOMAIN}/`)
313
+ } else {
314
+ intro('storyboard proxy')
315
+ log.error(`Unknown subcommand: "${subcommand}"`)
316
+ log.info('Available: start, close')
317
+ process.exit(1)
258
318
  }
259
- outro(`Proxy ready at http://${DOMAIN}/`)
260
319
  }
@@ -170,13 +170,26 @@ export const widgetSchema = {
170
170
  },
171
171
  x: {
172
172
  type: 'number',
173
- default: 0,
174
- description: 'X position',
173
+ description: 'X position (omit for auto-positioning)',
175
174
  },
176
175
  y: {
177
176
  type: 'number',
178
- default: 0,
179
- description: 'Y position',
177
+ description: 'Y position (omit for auto-positioning)',
178
+ },
179
+ near: {
180
+ type: 'string',
181
+ description: 'Place near this widget ID (default: auto-selects last widget). Use --near false to disable',
182
+ },
183
+ direction: {
184
+ type: 'string',
185
+ default: 'right',
186
+ description: 'Direction from reference widget: right, left, above, below',
187
+ aliases: ['dir'],
188
+ },
189
+ resolve: {
190
+ type: 'boolean',
191
+ default: false,
192
+ description: 'Run server-side collision detection on the target position',
180
193
  },
181
194
  props: {
182
195
  type: 'string',
@@ -187,6 +200,11 @@ export const widgetSchema = {
187
200
  description: 'Path to a JSON file containing widget props (avoids shell escaping)',
188
201
  aliases: ['pf'],
189
202
  },
203
+ json: {
204
+ type: 'boolean',
205
+ default: false,
206
+ description: 'Output result as JSON (includes widget id)',
207
+ },
190
208
  }
191
209
 
192
210
  /** @type {FlagSchema} */
package/src/cli/server.js CHANGED
@@ -1,35 +1,75 @@
1
1
  /**
2
- * storyboard server [branch] Start the persistent Storyboard dev server.
3
- *
4
- * Manages Vite child processes and serves the /_storyboard/ API.
5
- * If a branch is given, also starts Vite for that branch immediately.
2
+ * storyboard server — Dev server lifecycle management.
6
3
  *
7
4
  * Usage:
8
- * storyboard server # start server, detect branch from cwd
9
- * storyboard server main # start server + dev for main
10
- * storyboard server my-feature # start server + dev for my-feature
5
+ * storyboard server List running dev servers
6
+ * storyboard server list List running dev servers
7
+ * storyboard server start [wt] Start the persistent server + Vite for a worktree
8
+ * storyboard server stop <id> Stop a dev server by worktree name or ID
11
9
  */
12
10
 
13
11
  import * as p from '@clack/prompts'
14
12
  import { startServer, SERVER_PORT, spawnViteForBranch } from '../server/index.js'
15
13
  import { parseFlags } from './flags.js'
16
14
  import { readDevDomain, generateCaddyfile, generateRouteConfig, upsertCaddyRoute, isCaddyRunning } from './proxy.js'
17
- import { detectWorktreeName, getPort } from '../worktree/port.js'
15
+ import { detectWorktreeName, getPort, releasePort, repoRoot } from '../worktree/port.js'
16
+ import {
17
+ list,
18
+ findByWorktree,
19
+ findById,
20
+ unregister,
21
+ prune,
22
+ } from '../worktree/serverRegistry.js'
18
23
 
19
24
  const flagSchema = {
20
25
  port: { type: 'number', description: 'Server port (default: 4100)' },
26
+ background: { type: 'boolean', default: false, description: 'Run in background (--bg alias)' },
27
+ bg: { type: 'boolean', default: false, description: 'Run in background' },
28
+ multiple: { type: 'boolean', default: false, description: 'Allow multiple servers per worktree' },
21
29
  }
22
30
 
23
- async function main() {
24
- const { flags, positional } = parseFlags(process.argv.slice(3), flagSchema)
25
- const port = flags.port || SERVER_PORT
26
- const branchArg = positional[0] || undefined
31
+ // ─── Commands ────────────────────────────────────────────
27
32
 
28
- p.intro('storyboard server')
33
+ function serverList() {
34
+ const servers = list()
35
+ if (servers.length === 0) {
36
+ p.log.info('No dev servers running.')
37
+ return
38
+ }
29
39
 
30
40
  const devDomain = readDevDomain()
31
41
 
32
- // Register server itself with Caddy so it's reachable at the dev domain
42
+ p.log.info('Running dev servers:\n')
43
+ for (const s of servers) {
44
+ const isMain = s.worktree === 'main'
45
+ const basePath = isMain ? '/' : `/branch--${s.worktree}/`
46
+ const proxyUrl = `http://${devDomain}${basePath}`
47
+ const bg = s.background ? ' (bg)' : ''
48
+ console.log(` ${s.id} ${s.worktree.padEnd(32)} :${String(s.port).padEnd(5)} PID ${String(s.pid).padEnd(7)} ${proxyUrl}${bg}`)
49
+ }
50
+ console.log()
51
+ }
52
+
53
+ async function serverStart(branchArg, flags) {
54
+ const { background, bg, multiple } = flags
55
+ const isBackground = background || bg
56
+ const worktreeName = branchArg || detectWorktreeName()
57
+
58
+ // Check for duplicate worktree servers
59
+ if (!multiple) {
60
+ const existing = findByWorktree(worktreeName)
61
+ if (existing.length > 0) {
62
+ p.log.error(`A dev server is already running for "${worktreeName}" (ID: ${existing[0].id}, PID: ${existing[0].pid}).`)
63
+ p.log.info(`To stop it: npx storyboard server stop ${existing[0].id}`)
64
+ p.log.info(`To allow multiple: npx storyboard server start ${worktreeName} --multiple`)
65
+ process.exit(1)
66
+ }
67
+ }
68
+
69
+ const devDomain = readDevDomain()
70
+ const port = flags.port || SERVER_PORT
71
+
72
+ // Register server itself with Caddy
33
73
  try {
34
74
  const serverRoute = generateRouteConfig({ __server__: port })
35
75
  if (isCaddyRunning()) {
@@ -37,33 +77,116 @@ async function main() {
37
77
  }
38
78
  } catch { /* Caddy not available */ }
39
79
 
40
- const server = startServer(port)
80
+ try {
81
+ await startServer(port)
82
+ } catch (err) {
83
+ if (err.code === 'EADDRINUSE') {
84
+ p.log.error(`Port ${port} is already in use — another server may be running.`)
85
+ p.log.info('Try: npx storyboard server list')
86
+ process.exit(1)
87
+ }
88
+ throw err
89
+ }
41
90
 
42
- // Determine initial branch to start
43
- const initialBranch = branchArg || detectWorktreeName()
44
- const isMain = initialBranch === 'main'
45
- const basePath = isMain ? '/' : `/branch--${initialBranch}/`
91
+ const isMain = worktreeName === 'main'
92
+ const basePath = isMain ? '/' : `/branch--${worktreeName}/`
46
93
  const proxyUrl = `http://${devDomain}${basePath}`
47
94
 
48
- p.log.step(`Starting dev session for ${initialBranch}...`)
95
+ p.log.step(`Starting dev session for ${worktreeName}...`)
49
96
 
50
97
  try {
51
- const entry = spawnViteForBranch(initialBranch)
52
-
53
- // Wait for ready
98
+ const entry = spawnViteForBranch(worktreeName)
54
99
  const { waitForPort } = await import('../server/index.js')
55
100
  const ready = await waitForPort(entry.port)
56
101
 
57
102
  if (ready) {
58
- p.log.success(proxyUrl)
103
+ p.log.success(`[${entry.serverId}] ${proxyUrl}`)
59
104
  } else {
60
105
  p.log.warning(`Vite started but may not be ready yet — check ${proxyUrl}`)
61
106
  }
62
107
  } catch (err) {
63
- p.log.error(`Failed to start dev for ${initialBranch}: ${err.message}`)
108
+ p.log.error(`Failed to start dev for ${worktreeName}: ${err.message}`)
64
109
  }
65
110
 
66
111
  p.outro('Server running')
67
112
  }
68
113
 
114
+ function serverStop(target) {
115
+ if (!target) {
116
+ p.log.error('Usage: storyboard server stop <worktree|ID>')
117
+ process.exit(1)
118
+ }
119
+
120
+ // Try by ID first
121
+ let entry = findById(target)
122
+
123
+ // Then by worktree name
124
+ if (!entry) {
125
+ const matches = findByWorktree(target)
126
+ if (matches.length === 0) {
127
+ p.log.error(`No server found for "${target}".`)
128
+ p.log.info('Run `npx storyboard server list` to see running servers.')
129
+ process.exit(1)
130
+ }
131
+ if (matches.length > 1) {
132
+ p.log.error(`Multiple servers running for worktree "${target}":`)
133
+ for (const s of matches) {
134
+ console.log(` ${s.id} PID: ${s.pid} Port: ${s.port}`)
135
+ }
136
+ p.log.info('Specify an ID: npx storyboard server stop <ID>')
137
+ process.exit(1)
138
+ }
139
+ entry = matches[0]
140
+ }
141
+
142
+ try {
143
+ process.kill(entry.pid, 'SIGTERM')
144
+ p.log.success(`Stopped server ${entry.id} (PID: ${entry.pid}, worktree: "${entry.worktree}")`)
145
+ } catch (err) {
146
+ if (err.code === 'ESRCH') {
147
+ p.log.info(`Server ${entry.id} (PID: ${entry.pid}) was already dead.`)
148
+ } else {
149
+ p.log.error(`Failed to kill PID ${entry.pid}: ${err.message}`)
150
+ }
151
+ }
152
+
153
+ unregister(entry.id)
154
+ releasePort(entry.worktree)
155
+ }
156
+
157
+ // ─── Dispatch ────────────────────────────────────────────
158
+
159
+ async function main() {
160
+ const { flags, positional } = parseFlags(process.argv.slice(3), flagSchema)
161
+ const subcommand = positional[0]
162
+
163
+ p.intro('storyboard server')
164
+
165
+ switch (subcommand) {
166
+ case undefined:
167
+ case 'list':
168
+ serverList()
169
+ break
170
+ case 'start':
171
+ await serverStart(positional[1], flags)
172
+ break
173
+ case 'stop':
174
+ serverStop(positional[1])
175
+ break
176
+ default: {
177
+ // Catch unknown subcommands before falling through to legacy branch behavior
178
+ const knownSubcommands = ['list', 'start', 'stop']
179
+ if (subcommand && !subcommand.match(/^[a-z0-9]/) || ['exit', 'help', 'status'].includes(subcommand)) {
180
+ p.log.error(`Unknown subcommand: "${subcommand}"`)
181
+ p.log.info(`Available: ${knownSubcommands.join(', ')}`)
182
+ p.log.info(`To start a branch: npx storyboard server start <branch>`)
183
+ process.exit(1)
184
+ }
185
+ // Legacy behavior: treat argument as branch name (storyboard server <branch>)
186
+ await serverStart(subcommand, flags)
187
+ break
188
+ }
189
+ }
190
+ }
191
+
69
192
  main()
@@ -6,11 +6,16 @@
6
6
  * dev server. Falls back to ports.json if Caddy isn't reachable.
7
7
  */
8
8
 
9
- import { detectWorktreeName, getPort } from '../worktree/port.js'
9
+ import { detectWorktreeName, resolveRunningPort } from '../worktree/port.js'
10
10
  import { readDevDomain } from './proxy.js'
11
11
  import { execSync } from 'child_process'
12
12
 
13
13
  export function getServerUrl() {
14
+ // Prefer explicit env var (set by terminal agent sessions)
15
+ if (process.env.STORYBOARD_SERVER_URL) {
16
+ return process.env.STORYBOARD_SERVER_URL.replace(/\/$/, '')
17
+ }
18
+
14
19
  const name = detectWorktreeName()
15
20
 
16
21
  // Try Caddy admin API for the real port
@@ -50,7 +55,7 @@ export function getServerUrl() {
50
55
  // Caddy not running or not reachable — fall through
51
56
  }
52
57
 
53
- // Fallback to ports.json
54
- const port = getPort(name)
58
+ // Fallback to server registry / ports.json
59
+ const port = resolveRunningPort(name)
55
60
  return `http://localhost:${port}`
56
61
  }
@@ -12,13 +12,14 @@
12
12
 
13
13
  import * as p from '@clack/prompts'
14
14
  import { execSync as execSyncFn } from 'node:child_process'
15
- import { detectWorktreeName, getPort } from '../worktree/port.js'
15
+ import { detectWorktreeName, resolveRunningPort } from '../worktree/port.js'
16
16
  import { readDevDomain } from './proxy.js'
17
17
  import { parseFlags } from './flags.js'
18
18
  import { dim, cyan, bold, yellow } from './intro.js'
19
19
 
20
20
  const blue = (s) => `\x1b[34m${s}\x1b[0m`
21
21
  const orange = (s) => `\x1b[38;5;180m${s}\x1b[0m`
22
+ const green = (s) => `\x1b[32m${s}\x1b[0m`
22
23
 
23
24
  const flagSchema = {
24
25
  all: { type: 'boolean', description: 'Show sessions from all branches' },
@@ -74,13 +75,13 @@ function summaryText(entry, showCanvas = true) {
74
75
  return canvas ? `${canvas} › ${name}` : name
75
76
  }
76
77
 
77
- /** Detect the current tmux session name (the one running this CLI) */
78
+ /** Detect the current tmux session name (only if it's a storyboard session) */
78
79
  function getCurrentTmuxSession() {
79
80
  try {
80
81
  const result = execSyncFn('tmux display-message -p "#{session_name}" 2>/dev/null', {
81
82
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
82
- })
83
- return result.trim()
83
+ }).trim()
84
+ return result.startsWith('sb-') ? result : null
84
85
  } catch {
85
86
  return null
86
87
  }
@@ -126,9 +127,130 @@ function formatRow(idx, entry, isCurrent = false, showCanvas = true) {
126
127
  return ` ${dim(num)}${statusColored}${dim(modified)}${dim(created)}${summaryColored}${badges}`
127
128
  }
128
129
 
130
+ /** Call the bulk cleanup API */
131
+ async function cleanupSessions(worktreeName, port, statuses) {
132
+ const { proxyBase, directBase } = getBaseUrl(worktreeName, port)
133
+ for (const base of [proxyBase, directBase]) {
134
+ try {
135
+ const res = await fetch(`${base}_storyboard/terminal/sessions/cleanup`, {
136
+ method: 'POST',
137
+ headers: { 'Content-Type': 'application/json' },
138
+ body: JSON.stringify({ statuses }),
139
+ signal: AbortSignal.timeout(5000),
140
+ })
141
+ if (res.ok) return await res.json()
142
+ } catch { continue }
143
+ }
144
+ return null
145
+ }
146
+
147
+ /** Wait for a single keypress and return it. Returns null if not a TTY. */
148
+ function waitForKey() {
149
+ const { stdin } = process
150
+ if (!stdin.isTTY) return Promise.resolve(null)
151
+ return new Promise((resolve) => {
152
+ const wasRaw = stdin.isRaw
153
+ stdin.setRawMode(true)
154
+ stdin.resume()
155
+ stdin.once('data', (data) => {
156
+ stdin.setRawMode(wasRaw)
157
+ stdin.pause()
158
+ resolve(data)
159
+ })
160
+ })
161
+ }
162
+
163
+ /** Count sessions by status */
164
+ function countByStatus(sessions) {
165
+ let live = 0, background = 0, archived = 0
166
+ for (const s of sessions) {
167
+ if (s.status === 'live') live++
168
+ else if (s.status === 'background') background++
169
+ else if (s.status === 'archived') archived++
170
+ }
171
+ return { live, background, archived }
172
+ }
173
+
174
+ /** Show cleanup pre-prompt. Returns true if user chose cleanup (and it was handled). */
175
+ async function showCleanupPrompt(sessions, worktreeName, port) {
176
+ const { live, background, archived } = countByStatus(sessions)
177
+ const cleanable = background + archived
178
+ if (cleanable === 0) return false
179
+
180
+ const parts = []
181
+ if (live > 0) parts.push(blue(`${live} live`))
182
+ if (background > 0) parts.push(orange(`${background} background`))
183
+ if (archived > 0) parts.push(dim(`${archived} archived`))
184
+
185
+ p.log.info(`${sessions.length} sessions: ${parts.join(dim(' · '))}`)
186
+ process.stdout.write(`\n ${dim('Press')} Tab ${dim('to clean up sessions, or')} Enter ${dim('to browse')}\n\n`)
187
+
188
+ const key = await waitForKey()
189
+ if (!key) return false // non-TTY, skip pre-prompt
190
+ const keyStr = key.toString()
191
+
192
+ // Tab = \t, Enter = \r or \n, Ctrl+C = \x03
193
+ if (keyStr === '\x03') {
194
+ p.outro(dim('Done'))
195
+ process.exit(0)
196
+ }
197
+
198
+ if (keyStr !== '\t') return false // Enter or any other key → continue to session list
199
+
200
+ // Build cleanup options
201
+ const options = []
202
+ if (archived > 0) {
203
+ options.push({
204
+ value: 'archived',
205
+ label: `Remove ${bold(String(archived))} archived session${archived !== 1 ? 's' : ''}`,
206
+ })
207
+ }
208
+ if (cleanable > 0 && (archived > 0 && background > 0)) {
209
+ options.push({
210
+ value: 'all',
211
+ label: `Remove ${bold(String(cleanable))} archived + background sessions`,
212
+ })
213
+ } else if (background > 0 && archived === 0) {
214
+ options.push({
215
+ value: 'all',
216
+ label: `Remove ${bold(String(background))} background session${background !== 1 ? 's' : ''}`,
217
+ })
218
+ }
219
+ options.push({ value: 'cancel', label: dim('Cancel') })
220
+
221
+ const action = await p.select({
222
+ message: 'Clean up sessions',
223
+ options,
224
+ })
225
+
226
+ if (p.isCancel(action) || action === 'cancel') return false
227
+
228
+ const statuses = action === 'archived' ? ['archived'] : ['archived', 'background']
229
+ const targetCount = action === 'archived' ? archived : cleanable
230
+ const statusLabel = action === 'archived' ? 'archived' : 'archived + background'
231
+
232
+ const confirm = await p.confirm({
233
+ message: `Remove ${bold(String(targetCount))} ${statusLabel} session${targetCount !== 1 ? 's' : ''}? This cannot be undone.`,
234
+ })
235
+ if (p.isCancel(confirm) || !confirm) return false
236
+
237
+ const result = await cleanupSessions(worktreeName, port, statuses)
238
+ if (result && result.success) {
239
+ p.log.success(green(`Removed ${result.removed} session${result.removed !== 1 ? 's' : ''}`))
240
+ const rem = result.remaining
241
+ if (rem && rem.total > 0) {
242
+ p.log.info(dim(`${rem.total} remaining: ${rem.live} live, ${rem.background} background, ${rem.archived} archived`))
243
+ }
244
+ } else {
245
+ p.log.error('Cleanup failed — could not reach dev server')
246
+ }
247
+
248
+ return true // handled — re-enter loop to refresh list
249
+ }
250
+
129
251
  async function main() {
130
252
  const worktreeName = detectWorktreeName()
131
- const port = getPort(worktreeName)
253
+ const port = resolveRunningPort(worktreeName)
132
254
  const currentTmuxSession = getCurrentTmuxSession()
133
255
 
134
256
  // Session list loop — user can navigate back here after actions
@@ -155,6 +277,10 @@ async function main() {
155
277
  process.exit(0)
156
278
  }
157
279
 
280
+ // Show Tab cleanup pre-prompt when there are cleanable sessions
281
+ const didCleanup = await showCleanupPrompt(sessions, worktreeName, port)
282
+ if (didCleanup) continue // re-fetch and re-render after cleanup
283
+
158
284
  // Determine if all sessions are from the same canvas (hide canvas name if so)
159
285
  const canvasIds = new Set(sessions.map(s => s.canvasId).filter(c => c && c !== 'unknown'))
160
286
  const showCanvas = canvasIds.size > 1