@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
@@ -1,5 +1,53 @@
1
1
  /**
2
- * Shared set of file paths currently being written by the canvas server.
3
- * The data plugin checks this to skip reload for in-flight writes.
2
+ * Canvas write guard coordinates the canvas server with Vite's file watcher
3
+ * to prevent duplicate HMR events.
4
+ *
5
+ * When the canvas server writes to a .canvas.jsonl file, it immediately pushes
6
+ * an HMR event via pushCanvasUpdate(). Vite's file watcher also detects the
7
+ * change ~100-500ms later and the data plugin sends a second HMR event. This
8
+ * duplicate event can cause visible rollbacks when the client has made new edits
9
+ * in the intervening window.
10
+ *
11
+ * The guard tracks files currently being written by the canvas server. The data
12
+ * plugin checks this before sending watcher-triggered HMR events and skips them
13
+ * if the server has already pushed.
14
+ *
15
+ * Uses globalThis with a Symbol key to guarantee the same Map instance is shared
16
+ * across all import paths (e.g., relative imports from server.js vs package
17
+ * imports from data-plugin.js).
4
18
  */
5
- export const canvasWritesInFlight = new Set()
19
+
20
+ const KEY = Symbol.for('sb:canvasWriteGuard')
21
+ if (!globalThis[KEY]) globalThis[KEY] = new Map()
22
+
23
+ /** @type {Map<string, number>} filePath → active write count */
24
+ const guard = globalThis[KEY]
25
+
26
+ /**
27
+ * Mark a file as being written by the canvas server.
28
+ * Call before appendEvent / writeFileSync.
29
+ */
30
+ export function markCanvasWrite(filePath) {
31
+ guard.set(filePath, (guard.get(filePath) || 0) + 1)
32
+ }
33
+
34
+ /**
35
+ * Unmark a file after the watcher has had time to fire.
36
+ * Call via setTimeout after the write + push completes.
37
+ */
38
+ export function unmarkCanvasWrite(filePath) {
39
+ const count = (guard.get(filePath) || 1) - 1
40
+ if (count <= 0) guard.delete(filePath)
41
+ else guard.set(filePath, count)
42
+ }
43
+
44
+ /**
45
+ * Check if a file is currently being written by the canvas server.
46
+ * The data plugin uses this to skip duplicate watcher-triggered HMR events.
47
+ */
48
+ export function isCanvasWriteInFlight(filePath) {
49
+ return guard.has(filePath)
50
+ }
51
+
52
+ // Legacy export for backward compatibility
53
+ export const canvasWritesInFlight = guard
@@ -21,6 +21,8 @@
21
21
 
22
22
  let _pasteRules = []
23
23
  let _terminal = {}
24
+ let _agents = {}
25
+ let _zoom = { min: 10, max: 250, step: 10 }
24
26
 
25
27
  // ---------------------------------------------------------------------------
26
28
  // Configuration
@@ -30,11 +32,15 @@ let _terminal = {}
30
32
  * Initialize canvas config from storyboard.config.json's "canvas" key.
31
33
  * Called by mountStoryboardCore.
32
34
  *
33
- * @param {{ pasteRules?: object[], terminal?: object }} [config]
35
+ * @param {{ pasteRules?: object[], terminal?: object, agents?: object, zoom?: object }} [config]
34
36
  */
35
37
  export function initCanvasConfig(config = {}) {
36
38
  _pasteRules = Array.isArray(config.pasteRules) ? config.pasteRules : []
37
39
  _terminal = config.terminal && typeof config.terminal === 'object' ? config.terminal : {}
40
+ _agents = config.agents && typeof config.agents === 'object' ? config.agents : {}
41
+ _zoom = config.zoom && typeof config.zoom === 'object'
42
+ ? { min: config.zoom.min ?? 10, max: config.zoom.max ?? 250, step: config.zoom.step ?? 10 }
43
+ : { min: 10, max: 250, step: 10 }
38
44
  }
39
45
 
40
46
  /**
@@ -55,6 +61,64 @@ export function getTerminalConfig() {
55
61
  return _terminal
56
62
  }
57
63
 
64
+ /**
65
+ * Get agent configurations.
66
+ *
67
+ * @returns {object}
68
+ */
69
+ export function getAgentsConfig() {
70
+ return _agents
71
+ }
72
+
73
+ /**
74
+ * Get canvas zoom configuration (min, max, step).
75
+ *
76
+ * @returns {{ min: number, max: number, step: number }}
77
+ */
78
+ export function getCanvasZoom() {
79
+ return _zoom
80
+ }
81
+
82
+ /**
83
+ * Check if a terminal/agent widget should be resizable based on config.
84
+ * Agent-level `resizable` overrides the base `terminal.resizable`.
85
+ *
86
+ * @param {string|null} [agentId] — agent ID to check for overrides
87
+ * @returns {boolean}
88
+ */
89
+ export function isTerminalResizable(agentId = null) {
90
+ if (agentId) {
91
+ const agentCfg = _agents[agentId]
92
+ if (agentCfg && agentCfg.resizable !== undefined) return agentCfg.resizable
93
+ }
94
+ return _terminal.resizable ?? false
95
+ }
96
+
97
+ /**
98
+ * Get effective default dimensions for a terminal/agent widget.
99
+ * Cascade: agent config > terminal config > provided fallbacks.
100
+ *
101
+ * @param {string|null} [agentId] — agent ID to check for overrides
102
+ * @param {{ width: number, height: number }} [fallback] — schema-level fallbacks
103
+ * @returns {{ width: number, height: number }}
104
+ */
105
+ export function getTerminalDimensions(agentId = null, fallback = { width: 800, height: 450 }) {
106
+ const base = {
107
+ width: _terminal.defaultWidth ?? fallback.width,
108
+ height: _terminal.defaultHeight ?? fallback.height,
109
+ }
110
+ if (agentId) {
111
+ const agentCfg = _agents[agentId]
112
+ if (agentCfg) {
113
+ return {
114
+ width: agentCfg.defaultWidth ?? base.width,
115
+ height: agentCfg.defaultHeight ?? base.height,
116
+ }
117
+ }
118
+ }
119
+ return base
120
+ }
121
+
58
122
  // ---------------------------------------------------------------------------
59
123
  // Test helpers
60
124
  // ---------------------------------------------------------------------------
@@ -65,4 +129,6 @@ export function getTerminalConfig() {
65
129
  export function _resetCanvasConfig() {
66
130
  _pasteRules = []
67
131
  _terminal = {}
132
+ _agents = {}
133
+ _zoom = { min: 10, max: 250, step: 10 }
68
134
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest'
2
- import { initCanvasConfig, getPasteRules, _resetCanvasConfig } from './canvasConfig.js'
2
+ import { initCanvasConfig, getPasteRules, isTerminalResizable, getTerminalDimensions, _resetCanvasConfig } from './canvasConfig.js'
3
3
 
4
4
  describe('canvasConfig', () => {
5
5
  beforeEach(() => {
@@ -40,3 +40,81 @@ describe('canvasConfig', () => {
40
40
  expect(getPasteRules()).toEqual([])
41
41
  })
42
42
  })
43
+
44
+ describe('isTerminalResizable', () => {
45
+ beforeEach(() => {
46
+ _resetCanvasConfig()
47
+ })
48
+
49
+ it('returns false by default', () => {
50
+ expect(isTerminalResizable()).toBe(false)
51
+ })
52
+
53
+ it('returns terminal.resizable when set', () => {
54
+ initCanvasConfig({ terminal: { resizable: true } })
55
+ expect(isTerminalResizable()).toBe(true)
56
+ })
57
+
58
+ it('agent overrides terminal resizable', () => {
59
+ initCanvasConfig({
60
+ terminal: { resizable: false },
61
+ agents: { myAgent: { resizable: true } },
62
+ })
63
+ expect(isTerminalResizable('myAgent')).toBe(true)
64
+ expect(isTerminalResizable()).toBe(false)
65
+ })
66
+
67
+ it('agent can disable resizable even when terminal enables it', () => {
68
+ initCanvasConfig({
69
+ terminal: { resizable: true },
70
+ agents: { fixedAgent: { resizable: false } },
71
+ })
72
+ expect(isTerminalResizable('fixedAgent')).toBe(false)
73
+ expect(isTerminalResizable()).toBe(true)
74
+ })
75
+
76
+ it('falls back to terminal config for unknown agent', () => {
77
+ initCanvasConfig({ terminal: { resizable: true } })
78
+ expect(isTerminalResizable('nonexistent')).toBe(true)
79
+ })
80
+ })
81
+
82
+ describe('getTerminalDimensions', () => {
83
+ beforeEach(() => {
84
+ _resetCanvasConfig()
85
+ })
86
+
87
+ it('returns fallback defaults when no config', () => {
88
+ expect(getTerminalDimensions()).toEqual({ width: 800, height: 450 })
89
+ })
90
+
91
+ it('returns terminal config dimensions', () => {
92
+ initCanvasConfig({ terminal: { defaultWidth: 1000, defaultHeight: 600 } })
93
+ expect(getTerminalDimensions()).toEqual({ width: 1000, height: 600 })
94
+ })
95
+
96
+ it('agent overrides terminal dimensions', () => {
97
+ initCanvasConfig({
98
+ terminal: { defaultWidth: 1000, defaultHeight: 600 },
99
+ agents: { bigAgent: { defaultWidth: 1400, defaultHeight: 800 } },
100
+ })
101
+ expect(getTerminalDimensions('bigAgent')).toEqual({ width: 1400, height: 800 })
102
+ })
103
+
104
+ it('agent partial override inherits from terminal for unset dimensions', () => {
105
+ initCanvasConfig({
106
+ terminal: { defaultWidth: 1000, defaultHeight: 600 },
107
+ agents: { wideAgent: { defaultWidth: 1400 } },
108
+ })
109
+ expect(getTerminalDimensions('wideAgent')).toEqual({ width: 1400, height: 600 })
110
+ })
111
+
112
+ it('falls back to terminal config for unknown agent', () => {
113
+ initCanvasConfig({ terminal: { defaultWidth: 900, defaultHeight: 500 } })
114
+ expect(getTerminalDimensions('nonexistent')).toEqual({ width: 900, height: 500 })
115
+ })
116
+
117
+ it('uses custom fallback when provided', () => {
118
+ expect(getTerminalDimensions(null, { width: 1200, height: 450 })).toEqual({ width: 1200, height: 450 })
119
+ })
120
+ })
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * storyboard agent signal — signal agent status to the canvas server.
4
+ *
5
+ * Usage:
6
+ * npx storyboard agent signal --widget <id> --canvas <canvasId> --status done|error|running [--message "..."]
7
+ *
8
+ * Environment variables (auto-set by terminal server):
9
+ * STORYBOARD_WIDGET_ID — widget ID (fallback if --widget not provided)
10
+ * STORYBOARD_CANVAS_ID — canvas ID (fallback if --canvas not provided)
11
+ * STORYBOARD_SERVER_URL — server base URL
12
+ * STORYBOARD_BRANCH — current branch
13
+ */
14
+
15
+ import { parseFlags } from './flags.js'
16
+ import { dim, bold, cyan, yellow } from './intro.js'
17
+
18
+ const subcommand = process.argv[3]
19
+
20
+ if (subcommand === 'signal') {
21
+ const flagSchema = {
22
+ widget: { type: 'string', description: 'Widget ID' },
23
+ canvas: { type: 'string', description: 'Canvas ID' },
24
+ status: { type: 'string', required: true, description: 'Status: done, error, or running' },
25
+ message: { type: 'string', description: 'Optional status message' },
26
+ }
27
+
28
+ const { flags } = parseFlags(process.argv.slice(4), flagSchema)
29
+
30
+ const widgetId = flags.widget || process.env.STORYBOARD_WIDGET_ID
31
+ const canvasId = flags.canvas || process.env.STORYBOARD_CANVAS_ID
32
+ const status = flags.status
33
+ const message = flags.message || null
34
+ const serverUrl = process.env.STORYBOARD_SERVER_URL || 'http://localhost:1234'
35
+ const branch = process.env.STORYBOARD_BRANCH || 'unknown'
36
+
37
+ if (!widgetId || !canvasId || !status) {
38
+ console.error(`${bold('Usage:')} npx storyboard agent signal --status done|error|running`)
39
+ console.error(`${dim('Widget and canvas IDs are read from environment if not provided.')}`)
40
+ process.exit(1)
41
+ }
42
+
43
+ const validStatuses = ['done', 'error', 'running']
44
+ if (!validStatuses.includes(status)) {
45
+ console.error(`${bold('Error:')} Status must be one of: ${validStatuses.join(', ')}`)
46
+ process.exit(1)
47
+ }
48
+
49
+ try {
50
+ const url = `${serverUrl}/_storyboard/canvas/agent/signal`
51
+ const res = await fetch(url, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({ widgetId, canvasId, branch, status, message }),
55
+ })
56
+
57
+ if (res.ok) {
58
+ console.log(`${cyan('✓')} Agent status: ${bold(status)}${message ? ` — ${message}` : ''}`)
59
+ } else {
60
+ const data = await res.json().catch(() => ({}))
61
+ console.error(`${yellow('⚠')} Server returned ${res.status}: ${data.error || 'unknown error'}`)
62
+ // Fallback: write directly to terminal config
63
+ await fallbackWrite({ branch, canvasId, widgetId, status, message })
64
+ }
65
+ } catch {
66
+ // Server not reachable — write directly to terminal config file
67
+ await fallbackWrite({ branch, canvasId, widgetId, status, message })
68
+ }
69
+ } else {
70
+ console.error(`${bold('Usage:')} npx storyboard agent <signal>`)
71
+ console.error(`${dim('Subcommands: signal')}`)
72
+ process.exit(1)
73
+ }
74
+
75
+ async function fallbackWrite({ branch, canvasId, widgetId, status, message }) {
76
+ try {
77
+ const { updateAgentStatus, initTerminalConfig } = await import('../canvas/terminal-config.js')
78
+ initTerminalConfig(process.cwd())
79
+ updateAgentStatus({ branch, canvasId, widgetId, status, message })
80
+ console.log(`${cyan('✓')} Agent status written to config file (server offline): ${bold(status)}`)
81
+ } catch (err) {
82
+ console.error(`Failed to write agent status: ${err.message}`)
83
+ process.exit(1)
84
+ }
85
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * storyboard branch — Interactive guide to switch to a branch worktree.
3
+ *
4
+ * Deterministic flow (no AI):
5
+ * 1. Ask which branch to work on
6
+ * 2. Stash uncommitted work (named stash for safety)
7
+ * 3. Create worktree if needed (git worktree add + npm install)
8
+ * 4. Pull --rebase from origin
9
+ * 5. Apply stash into the new worktree
10
+ * 6. Confirm to user
11
+ *
12
+ * Also available as post-setup prompt in setup.js.
13
+ *
14
+ * Usage:
15
+ * npx storyboard branch
16
+ * npx storyboard branch <name> # skip the prompt
17
+ */
18
+
19
+ import * as p from '@clack/prompts'
20
+ import { execFileSync } from 'child_process'
21
+ import { existsSync } from 'fs'
22
+ import { resolve } from 'path'
23
+ import { repoRoot, worktreeDir, listWorktrees, getPort, detectWorktreeName } from '../worktree/port.js'
24
+ import { hasUncommittedChanges, localBranchExists } from './dev-helpers.js'
25
+ import { dim, green, yellow, bold, cyan } from './intro.js'
26
+
27
+ /** Check if a remote branch exists on origin. */
28
+ function remoteBranchExists(name, cwd) {
29
+ try {
30
+ const result = execFileSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', name], { cwd, encoding: 'utf8' })
31
+ return result.trim().length > 0
32
+ } catch {
33
+ return false
34
+ }
35
+ }
36
+
37
+ /** Get the current branch name. */
38
+ function currentBranch(cwd) {
39
+ try {
40
+ return execFileSync('git', ['branch', '--show-current'], { cwd, encoding: 'utf8' }).trim()
41
+ } catch {
42
+ return 'unknown'
43
+ }
44
+ }
45
+
46
+ /** Validate a branch name for git. */
47
+ function isValidBranchName(name) {
48
+ if (!name || name.trim().length === 0) return 'Branch name cannot be empty'
49
+ const n = name.trim()
50
+ if (/\s/.test(n)) return 'Branch name cannot contain spaces'
51
+ if (/\.\./.test(n)) return 'Branch name cannot contain ".."'
52
+ if (/[~^:\\]/.test(n)) return 'Branch name cannot contain ~, ^, :, or \\'
53
+ if (n.startsWith('-')) return 'Branch name cannot start with "-"'
54
+ if (n.endsWith('.lock')) return 'Branch name cannot end with ".lock"'
55
+ return undefined
56
+ }
57
+
58
+ export async function runBranchGuide(branchArg) {
59
+ const root = repoRoot()
60
+ const existing = listWorktrees()
61
+ const fromBranch = currentBranch(root)
62
+ const fromWorktree = detectWorktreeName()
63
+
64
+ // 1. Get branch name
65
+ let targetBranch = branchArg?.trim()
66
+
67
+ if (!targetBranch) {
68
+ if (existing.length > 0) {
69
+ // Render as columns that fit the terminal width
70
+ const maxLen = Math.max(...existing.map(w => w.length))
71
+ const colWidth = maxLen + 2
72
+ const termWidth = process.stdout.columns || 80
73
+ const cols = Math.max(1, Math.floor(termWidth / colWidth))
74
+ const rows = Math.ceil(existing.length / cols)
75
+ const lines = []
76
+ for (let r = 0; r < rows; r++) {
77
+ const parts = []
78
+ for (let c = 0; c < cols; c++) {
79
+ const idx = c * rows + r
80
+ if (idx < existing.length) {
81
+ parts.push(cyan(existing[idx].padEnd(colWidth)))
82
+ }
83
+ }
84
+ lines.push(` ${parts.join('')}`)
85
+ }
86
+ p.log.info(`${dim('Existing worktrees:')}\n${lines.join('\n')}`)
87
+ }
88
+
89
+ const result = await p.text({
90
+ message: 'Which branch do you want to work on? Select one from above or type a new one',
91
+ placeholder: 'e.g. 4.3.0--my-feature',
92
+ validate: isValidBranchName,
93
+ })
94
+
95
+ if (p.isCancel(result)) {
96
+ p.cancel('Cancelled')
97
+ process.exit(0)
98
+ }
99
+ targetBranch = result.trim()
100
+ }
101
+
102
+ // 2. Check if worktree already exists
103
+ const wtDir = worktreeDir(targetBranch)
104
+ if (existsSync(resolve(wtDir, '.git'))) {
105
+ p.log.success(`Worktree ${bold(targetBranch)} already exists`)
106
+ p.note(
107
+ [
108
+ ` ${green('cd')} ${dim(`.worktrees/${targetBranch}`)}`,
109
+ ` ${green('npx storyboard dev')} ${dim('to start developing')}`,
110
+ ].join('\n'),
111
+ 'Ready to go'
112
+ )
113
+ p.outro('')
114
+ return
115
+ }
116
+
117
+ // 3. Stash uncommitted work
118
+ let didStash = false
119
+ const sourceDir = fromWorktree === 'main' ? root : worktreeDir(fromWorktree)
120
+
121
+ if (hasUncommittedChanges(sourceDir)) {
122
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
123
+ const stashName = `${fromBranch}→${targetBranch}@${timestamp}`
124
+
125
+ p.log.step('Stashing uncommitted work…')
126
+ try {
127
+ execFileSync('git', ['stash', 'push', '-m', stashName], { cwd: sourceDir, stdio: 'pipe' })
128
+ didStash = true
129
+ p.log.success(`Work stashed: ${dim(stashName)}`)
130
+ } catch {
131
+ p.log.warning('Could not stash changes — proceeding anyway')
132
+ }
133
+ }
134
+
135
+ // 4. Resolve branch and create worktree
136
+ const hasLocal = localBranchExists(targetBranch, root)
137
+ const hasRemote = !hasLocal && remoteBranchExists(targetBranch, root)
138
+ const isNew = !hasLocal && !hasRemote
139
+
140
+ if (isNew) {
141
+ p.log.step(`Creating new branch ${bold(targetBranch)} from HEAD`)
142
+ } else if (hasRemote) {
143
+ // Fetch the remote branch so git worktree add can use it
144
+ p.log.step(`Fetching ${bold(targetBranch)} from origin…`)
145
+ try {
146
+ execFileSync('git', ['fetch', 'origin', targetBranch], { cwd: root, stdio: 'pipe' })
147
+ // Create a local tracking branch
148
+ try {
149
+ execFileSync('git', ['branch', targetBranch, `origin/${targetBranch}`], { cwd: root, stdio: 'pipe' })
150
+ } catch { /* may already exist after fetch */ }
151
+ } catch {
152
+ p.log.warning('Could not fetch from origin — using local state')
153
+ }
154
+ } else {
155
+ p.log.step(`Using existing branch ${bold(targetBranch)}`)
156
+ }
157
+
158
+ // Create the worktree
159
+ const targetDir = resolve(root, '.worktrees', targetBranch)
160
+ const spin = p.spinner()
161
+
162
+ try {
163
+ const gitArgs = isNew
164
+ ? ['worktree', 'add', targetDir, '-b', targetBranch]
165
+ : ['worktree', 'add', targetDir, targetBranch]
166
+
167
+ spin.start(`Creating worktree .worktrees/${targetBranch}`)
168
+ execFileSync('git', gitArgs, { cwd: root, stdio: 'pipe' })
169
+ spin.stop(`Worktree created: .worktrees/${targetBranch}`)
170
+ } catch (err) {
171
+ spin.stop('Failed to create worktree')
172
+ p.log.error(err.message || 'git worktree add failed')
173
+ process.exit(1)
174
+ }
175
+
176
+ // Install dependencies
177
+ try {
178
+ spin.start('Installing dependencies…')
179
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
180
+ execFileSync(npmBin, ['install'], { cwd: targetDir, stdio: 'pipe' })
181
+ spin.stop('Dependencies installed')
182
+ } catch {
183
+ spin.stop('npm install failed — you may need to run it manually')
184
+ }
185
+
186
+ // Assign a dev server port
187
+ getPort(targetBranch)
188
+
189
+ // 5. Pull --rebase from origin
190
+ if (!isNew) {
191
+ try {
192
+ spin.start('Pulling latest changes…')
193
+ execFileSync('git', ['pull', '--rebase', 'origin', targetBranch], { cwd: targetDir, stdio: 'pipe' })
194
+ spin.stop('Up to date with origin')
195
+ } catch {
196
+ spin.stop(dim('No remote changes (or origin not available)'))
197
+ }
198
+ }
199
+
200
+ // 6. Apply stash (if we stashed earlier)
201
+ if (didStash) {
202
+ try {
203
+ // Apply (not pop) — keeps the backup in stash list
204
+ execFileSync('git', ['stash', 'apply'], { cwd: targetDir, stdio: 'pipe' })
205
+ p.log.success('Previous work applied to this branch')
206
+ } catch {
207
+ p.log.warning('Stash apply had conflicts — resolve them manually')
208
+ p.log.info(` Your work is safe in ${dim('git stash list')}`)
209
+ }
210
+ }
211
+
212
+ // 7. Summary
213
+ const lines = [
214
+ ` Your branch is set up as a worktree in ${green(`.worktrees/${targetBranch}`)}`,
215
+ ]
216
+ if (didStash) {
217
+ lines.push(` Your uncommitted work has been safely moved`)
218
+ }
219
+ lines.push('')
220
+ lines.push(` ${green('cd')} ${dim(`.worktrees/${targetBranch}`)}`)
221
+ lines.push(` ${green('npx storyboard dev')} ${dim('to start developing')}`)
222
+ lines.push('')
223
+ lines.push(` ${dim('Tip: ask your AI agent about worktrees — they\'re great!')}`)
224
+
225
+ p.note(lines.join('\n'), `Branch ${bold(targetBranch)} is ready`)
226
+ p.outro('')
227
+ }
228
+
229
+ // Direct invocation
230
+ const branchArg = process.argv[3] || undefined
231
+ p.intro('storyboard branch')
232
+ runBranchGuide(branchArg)
@@ -1,10 +1,20 @@
1
1
  /**
2
2
  * storyboard canvas add <widget-type> — Add a widget to an existing canvas.
3
3
  *
4
+ * Widgets are auto-positioned by default near (in priority order):
5
+ * 1. The active agent/terminal running the command ($STORYBOARD_WIDGET_ID)
6
+ * 2. The user's currently selected widget
7
+ * 3. The center of the user's viewport
8
+ * 4. The last widget on the canvas
9
+ *
10
+ * Use --x/--y for explicit coordinates, or --near <widget-id> to place
11
+ * relative to a specific widget. Use --near false to opt out entirely.
12
+ *
4
13
  * Usage:
5
14
  * storyboard canvas add sticky-note --canvas my-canvas
6
- * storyboard canvas add markdown --canvas my-canvas --x 100 --y 200
7
- * storyboard canvas add prototype --canvas my-canvas --props '{"src":"my-proto"}'
15
+ * storyboard canvas add markdown --canvas my-canvas --near widget-abc
16
+ * storyboard canvas add prototype --canvas my-canvas --x 100 --y 200
17
+ * storyboard canvas add sticky-note --canvas my-canvas --near false --x 0 --y 0
8
18
  *
9
19
  * Known widget types: sticky-note, markdown, prototype
10
20
  * The server accepts any type string — new widget types work automatically.
@@ -44,14 +54,15 @@ async function canvasAdd() {
44
54
 
45
55
  const flagMode = hasFlags(rest) || Boolean(widgetType)
46
56
  const { flags, errors } = flagMode ? parseFlags(flagTokens, widgetSchema) : { flags: {}, errors: [] }
57
+ const jsonOutput = flags.json || false
47
58
 
48
59
  if (errors.length) {
49
60
  for (const e of errors) p.log.error(e)
50
61
  process.exit(1)
51
62
  }
52
63
 
53
- p.intro('storyboard canvas add')
54
- await ensureDevServer()
64
+ if (!jsonOutput) p.intro('storyboard canvas add')
65
+ await ensureDevServer({ quiet: jsonOutput })
55
66
 
56
67
  // Widget type
57
68
  if (!widgetType) {
@@ -93,9 +104,24 @@ async function canvasAdd() {
93
104
  return v
94
105
  })()
95
106
 
107
+ // Position flags: only send explicit position if user provided --x/--y.
108
+ // --near=false explicitly opts out of auto-positioning.
109
+ const hasExplicitX = flags.x !== undefined && flags.x !== null
110
+ const hasExplicitY = flags.y !== undefined && flags.y !== null
111
+ const hasExplicitPosition = hasExplicitX || hasExplicitY
96
112
  const x = flags.x ?? 0
97
113
  const y = flags.y ?? 0
98
114
 
115
+ // --near: string widget ID, or literal "false" to opt out
116
+ const nearRaw = flags.near
117
+ const nearOptOut = nearRaw === 'false' || nearRaw === false
118
+ const near = (nearRaw && !nearOptOut) ? nearRaw : null
119
+ const direction = flags.direction || 'right'
120
+ const resolve = flags.resolve || !!near
121
+
122
+ // Pass the calling agent/terminal's widget ID so the server can auto-position near it
123
+ const source = process.env.STORYBOARD_WIDGET_ID || null
124
+
99
125
  let props = {}
100
126
  if (flags['props-file']) {
101
127
  try {
@@ -167,19 +193,40 @@ async function canvasAdd() {
167
193
  }
168
194
 
169
195
  // Submit
196
+ if (jsonOutput) {
197
+ // JSON mode: no spinners/clack UI, just raw JSON output for scripting
198
+ try {
199
+ const body = { name: canvasName, type: widgetType, props }
200
+ if (hasExplicitPosition) body.position = { x, y }
201
+ if (nearOptOut) body.near = false
202
+ if (near) { body.near = near; body.direction = direction }
203
+ if (resolve) body.resolve = true
204
+ if (source) body.source = source
205
+ const result = await serverPost('/_storyboard/canvas/widget', body)
206
+ const widget = result.widget || result
207
+ console.log(JSON.stringify(widget))
208
+ } catch (err) {
209
+ console.error(JSON.stringify({ error: err.message }))
210
+ process.exit(1)
211
+ }
212
+ return
213
+ }
214
+
170
215
  const s = p.spinner()
171
216
  s.start(`Adding ${widgetType} widget...`)
172
217
 
173
218
  try {
174
- const result = await serverPost('/_storyboard/canvas/widget', {
175
- name: canvasName,
176
- type: widgetType,
177
- props,
178
- position: { x, y },
179
- })
219
+ const body = { name: canvasName, type: widgetType, props }
220
+ if (hasExplicitPosition) body.position = { x, y }
221
+ if (nearOptOut) body.near = false
222
+ if (near) { body.near = near; body.direction = direction }
223
+ if (resolve) body.resolve = true
224
+ if (source) body.source = source
225
+ const result = await serverPost('/_storyboard/canvas/widget', body)
180
226
  s.stop(`Widget added!`)
181
- if (result.id) {
182
- p.log.success(` ${widgetType} → ${canvasName} (id: ${result.id})`)
227
+ const widgetId = result.widget?.id || result.id
228
+ if (widgetId) {
229
+ p.log.success(` ${widgetType} → ${canvasName} (id: ${widgetId})`)
183
230
  } else {
184
231
  p.log.success(` ${widgetType} → ${canvasName}`)
185
232
  }