@dfosco/storyboard-core 4.2.0-beta.4 → 4.2.1

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 (419) hide show
  1. package/commandpalette.config.json +109 -24
  2. package/dist/storyboard-ui.css +1 -1
  3. package/dist/storyboard-ui.js +16711 -27239
  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 -10
  11. package/scaffold/skills/canvas/SKILL.md +5 -4
  12. package/scaffold/skills/ship/SKILL.md +1 -1
  13. package/scaffold/storyboard.config.json +11 -1
  14. package/src/ActionMenuButton.jsx +103 -0
  15. package/src/AutosyncMenuButton.css +67 -0
  16. package/src/AutosyncMenuButton.jsx +242 -0
  17. package/src/BranchSelect.jsx +29 -0
  18. package/src/BranchSelect.module.css +30 -0
  19. package/src/CanvasAgentsMenu.jsx +89 -0
  20. package/src/CanvasCreateMenu.jsx +611 -0
  21. package/src/CanvasSnap.css +27 -0
  22. package/src/CanvasSnap.jsx +51 -0
  23. package/src/CanvasUndoRedo.css +36 -0
  24. package/src/CanvasUndoRedo.jsx +62 -0
  25. package/src/CanvasZoomControl.css +53 -0
  26. package/src/CanvasZoomControl.jsx +49 -0
  27. package/src/CanvasZoomToFit.css +18 -0
  28. package/src/CanvasZoomToFit.jsx +26 -0
  29. package/src/CommandMenu.css +8 -0
  30. package/src/CommandMenu.jsx +287 -0
  31. package/src/CommandPalette.jsx +35 -0
  32. package/src/CommandPaletteTrigger.jsx +25 -0
  33. package/src/CommentsMenuButton.jsx +40 -0
  34. package/src/CoreUIBar.css +47 -0
  35. package/src/CoreUIBar.jsx +858 -0
  36. package/src/CreateMenuButton.jsx +117 -0
  37. package/src/HideChromeTrigger.jsx +40 -0
  38. package/src/InspectorPanel.css +109 -0
  39. package/src/InspectorPanel.jsx +632 -0
  40. package/src/PwaInstallBanner.css +42 -0
  41. package/src/PwaInstallBanner.jsx +124 -0
  42. package/src/SidePanel.jsx +261 -0
  43. package/src/ThemeMenuButton.jsx +139 -0
  44. package/src/autosync/server.js +202 -5
  45. package/src/autosync/server.test.js +111 -0
  46. package/src/canvas/__tests__/agent-integration.test.js +596 -0
  47. package/src/canvas/__tests__/helpers/browser.js +95 -0
  48. package/src/canvas/__tests__/helpers/canvas-api.js +129 -0
  49. package/src/canvas/__tests__/helpers/perf.js +118 -0
  50. package/src/canvas/__tests__/helpers/setup.js +176 -0
  51. package/src/canvas/__tests__/helpers/tmux.js +130 -0
  52. package/src/canvas/__tests__/helpers/transcript.js +132 -0
  53. package/src/canvas/__tests__/terminal-integration.test.js +177 -0
  54. package/src/canvas/hot-pool.js +756 -0
  55. package/src/canvas/materializer.js +31 -0
  56. package/src/canvas/materializer.test.js +56 -0
  57. package/src/canvas/selectedWidgets.js +65 -7
  58. package/src/canvas/server.js +1802 -22
  59. package/src/canvas/server.test.js +239 -0
  60. package/src/canvas/terminal-config.js +330 -0
  61. package/src/canvas/terminal-registry.js +41 -3
  62. package/src/canvas/terminal-server.js +1098 -37
  63. package/src/canvas/writeGuard.js +51 -3
  64. package/src/canvasConfig.js +67 -1
  65. package/src/canvasConfig.test.js +79 -1
  66. package/src/cli/agent.js +85 -0
  67. package/src/cli/branch.js +232 -0
  68. package/src/cli/canvasAdd.js +61 -14
  69. package/src/cli/canvasBatch.js +98 -0
  70. package/src/cli/canvasBounds.js +1 -1
  71. package/src/cli/canvasRead.js +1 -1
  72. package/src/cli/canvasUpdate.js +179 -0
  73. package/src/cli/compact.js +0 -2
  74. package/src/cli/create.js +40 -16
  75. package/src/cli/dev.js +158 -84
  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 +149 -26
  81. package/src/cli/serverUrl.js +8 -3
  82. package/src/cli/sessions.js +133 -7
  83. package/src/cli/setup.js +138 -12
  84. package/src/cli/terminal-commands.js +20 -9
  85. package/src/cli/terminal-messaging.js +231 -0
  86. package/src/cli/terminal-welcome.js +449 -34
  87. package/src/cli/updateVersion.js +1 -1
  88. package/src/commandActions.js +1 -0
  89. package/src/commandPaletteConfig.js +9 -0
  90. package/src/comments/auth.js +2 -1
  91. package/src/comments/ui/AuthModal.jsx +114 -0
  92. package/src/comments/ui/CommentWindow.jsx +329 -0
  93. package/src/comments/ui/CommentsDrawer.jsx +102 -0
  94. package/src/comments/ui/Composer.jsx +64 -0
  95. package/src/comments/ui/authModal.test.js +1 -1
  96. package/src/comments/ui/commentWindow.js +16 -17
  97. package/src/comments/ui/commentsDrawer.js +25 -26
  98. package/src/comments/ui/composer.js +23 -24
  99. package/src/comments/ui/index.js +2 -3
  100. package/src/configSchema.js +59 -1
  101. package/src/configStore.js +161 -0
  102. package/src/core-ui-colors.css +12 -0
  103. package/src/devtools.js +17 -19
  104. package/src/devtools.test.js +18 -9
  105. package/src/featureFlags.js +12 -5
  106. package/src/fuzzySearch.test.js +10 -0
  107. package/src/index.js +15 -3
  108. package/src/lib/components/ui/alert/alert-action.jsx +11 -0
  109. package/src/lib/components/ui/alert/alert-description.jsx +11 -0
  110. package/src/lib/components/ui/alert/alert-title.jsx +11 -0
  111. package/src/lib/components/ui/alert/alert.jsx +25 -0
  112. package/src/lib/components/ui/alert/index.js +15 -15
  113. package/src/lib/components/ui/avatar/avatar-badge.jsx +22 -0
  114. package/src/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
  115. package/src/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
  116. package/src/lib/components/ui/avatar/avatar-group.jsx +19 -0
  117. package/src/lib/components/ui/avatar/avatar-image.jsx +15 -0
  118. package/src/lib/components/ui/avatar/avatar.jsx +19 -0
  119. package/src/lib/components/ui/avatar/index.js +20 -20
  120. package/src/lib/components/ui/badge/badge.jsx +31 -0
  121. package/src/lib/components/ui/badge/index.js +2 -2
  122. package/src/lib/components/ui/button/button.jsx +100 -0
  123. package/src/lib/components/ui/button/index.js +9 -9
  124. package/src/lib/components/ui/card/card-action.jsx +11 -0
  125. package/src/lib/components/ui/card/card-content.jsx +11 -0
  126. package/src/lib/components/ui/card/card-description.jsx +11 -0
  127. package/src/lib/components/ui/card/card-footer.jsx +11 -0
  128. package/src/lib/components/ui/card/card-header.jsx +19 -0
  129. package/src/lib/components/ui/card/card-title.jsx +11 -0
  130. package/src/lib/components/ui/card/card.jsx +17 -0
  131. package/src/lib/components/ui/card/index.js +23 -23
  132. package/src/lib/components/ui/checkbox/checkbox.jsx +29 -0
  133. package/src/lib/components/ui/checkbox/index.js +5 -5
  134. package/src/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
  135. package/src/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
  136. package/src/lib/components/ui/collapsible/collapsible.jsx +7 -0
  137. package/src/lib/components/ui/collapsible/index.js +11 -11
  138. package/src/lib/components/ui/dialog/dialog-close.jsx +7 -0
  139. package/src/lib/components/ui/dialog/dialog-content.jsx +34 -0
  140. package/src/lib/components/ui/dialog/dialog-description.jsx +15 -0
  141. package/src/lib/components/ui/dialog/dialog-footer.jsx +23 -0
  142. package/src/lib/components/ui/dialog/dialog-header.jsx +11 -0
  143. package/src/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
  144. package/src/lib/components/ui/dialog/dialog-portal.jsx +4 -0
  145. package/src/lib/components/ui/dialog/dialog-title.jsx +15 -0
  146. package/src/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
  147. package/src/lib/components/ui/dialog/dialog.jsx +4 -0
  148. package/src/lib/components/ui/dialog/index.js +32 -32
  149. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
  150. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
  151. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
  152. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
  153. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
  154. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
  155. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
  156. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
  157. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
  158. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
  159. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
  160. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
  161. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
  162. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
  163. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
  164. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
  165. package/src/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
  166. package/src/lib/components/ui/dropdown-menu/index.js +52 -52
  167. package/src/lib/components/ui/input/index.js +5 -5
  168. package/src/lib/components/ui/input/input.jsx +19 -0
  169. package/src/lib/components/ui/label/index.js +5 -5
  170. package/src/lib/components/ui/label/label.jsx +19 -0
  171. package/src/lib/components/ui/panel/index.js +21 -21
  172. package/src/lib/components/ui/panel/panel-body.jsx +11 -0
  173. package/src/lib/components/ui/panel/panel-close.jsx +16 -0
  174. package/src/lib/components/ui/panel/panel-content.jsx +29 -0
  175. package/src/lib/components/ui/panel/panel-footer.jsx +11 -0
  176. package/src/lib/components/ui/panel/panel-header.jsx +11 -0
  177. package/src/lib/components/ui/panel/panel-title.jsx +12 -0
  178. package/src/lib/components/ui/panel/panel.jsx +4 -0
  179. package/src/lib/components/ui/popover/index.js +26 -26
  180. package/src/lib/components/ui/popover/popover-close.jsx +7 -0
  181. package/src/lib/components/ui/popover/popover-content.jsx +22 -0
  182. package/src/lib/components/ui/popover/popover-description.jsx +11 -0
  183. package/src/lib/components/ui/popover/popover-header.jsx +11 -0
  184. package/src/lib/components/ui/popover/popover-portal.jsx +4 -0
  185. package/src/lib/components/ui/popover/popover-title.jsx +11 -0
  186. package/src/lib/components/ui/popover/popover-trigger.jsx +8 -0
  187. package/src/lib/components/ui/popover/popover.jsx +4 -0
  188. package/src/lib/components/ui/searchable-list.jsx +160 -0
  189. package/src/lib/components/ui/select/index.js +35 -35
  190. package/src/lib/components/ui/select/select-content.jsx +30 -0
  191. package/src/lib/components/ui/select/select-group-heading.jsx +17 -0
  192. package/src/lib/components/ui/select/select-group.jsx +15 -0
  193. package/src/lib/components/ui/select/select-item.jsx +26 -0
  194. package/src/lib/components/ui/select/select-label.jsx +11 -0
  195. package/src/lib/components/ui/select/select-portal.jsx +4 -0
  196. package/src/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
  197. package/src/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
  198. package/src/lib/components/ui/select/select-separator.jsx +15 -0
  199. package/src/lib/components/ui/select/select-trigger.jsx +25 -0
  200. package/src/lib/components/ui/select/select.jsx +4 -0
  201. package/src/lib/components/ui/separator/index.js +5 -5
  202. package/src/lib/components/ui/separator/separator.jsx +22 -0
  203. package/src/lib/components/ui/sheet/index.js +32 -32
  204. package/src/lib/components/ui/sheet/sheet-close.jsx +7 -0
  205. package/src/lib/components/ui/sheet/sheet-content.jsx +35 -0
  206. package/src/lib/components/ui/sheet/sheet-description.jsx +15 -0
  207. package/src/lib/components/ui/sheet/sheet-footer.jsx +11 -0
  208. package/src/lib/components/ui/sheet/sheet-header.jsx +11 -0
  209. package/src/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
  210. package/src/lib/components/ui/sheet/sheet-portal.jsx +4 -0
  211. package/src/lib/components/ui/sheet/sheet-title.jsx +15 -0
  212. package/src/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
  213. package/src/lib/components/ui/sheet/sheet.jsx +4 -0
  214. package/src/lib/components/ui/textarea/index.js +5 -5
  215. package/src/lib/components/ui/textarea/textarea.jsx +18 -0
  216. package/src/lib/components/ui/toggle/index.js +6 -9
  217. package/src/lib/components/ui/toggle/toggle.jsx +36 -0
  218. package/src/lib/components/ui/toggle-group/index.js +8 -8
  219. package/src/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
  220. package/src/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
  221. package/src/lib/components/ui/tooltip/index.js +3 -3
  222. package/src/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
  223. package/src/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
  224. package/src/lib/components/ui/tooltip/tooltip.jsx +11 -0
  225. package/src/lib/components/ui/trigger-button/index.js +3 -3
  226. package/src/lib/components/ui/trigger-button/trigger-button.css +38 -0
  227. package/src/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
  228. package/src/logger/devLogger.js +238 -0
  229. package/src/logger/devLogger.test.js +193 -0
  230. package/src/modes.test.js +4 -4
  231. package/src/mountStoryboardCore.js +133 -42
  232. package/src/paletteProviders.js +3 -0
  233. package/src/paletteProviders.test.js +2 -2
  234. package/src/server/index.js +104 -40
  235. package/src/sidepanel.css +214 -0
  236. package/src/smoothCorners.js +1 -1
  237. package/src/styles/tailwind.css +1 -1
  238. package/src/svelte-plugin-ui/__tests__/ModeSwitch.test.ts +8 -8
  239. package/src/svelte-plugin-ui/__tests__/ToolbarShell.test.ts +11 -10
  240. package/src/svelte-plugin-ui/components/Icon.css +11 -0
  241. package/src/svelte-plugin-ui/components/Icon.jsx +281 -0
  242. package/src/svelte-plugin-ui/components/ModeSwitch.css +90 -0
  243. package/src/svelte-plugin-ui/components/ModeSwitch.jsx +47 -0
  244. package/src/svelte-plugin-ui/components/ToolbarShell.css +80 -0
  245. package/src/svelte-plugin-ui/components/ToolbarShell.jsx +84 -0
  246. package/src/svelte-plugin-ui/components/Viewfinder.css +412 -0
  247. package/src/svelte-plugin-ui/components/Viewfinder.jsx +513 -0
  248. package/src/svelte-plugin-ui/mount.ts +12 -16
  249. package/src/toolRegistry.js +4 -4
  250. package/src/toolbarConfigStore.js +30 -0
  251. package/src/tools/handlers/autosync.js +1 -1
  252. package/src/tools/handlers/canvasAddWidget.js +1 -1
  253. package/src/tools/handlers/canvasAgents.js +20 -0
  254. package/src/tools/handlers/canvasToolbar.js +8 -8
  255. package/src/tools/handlers/commandPalette.js +9 -0
  256. package/src/tools/handlers/comments.js +2 -2
  257. package/src/tools/handlers/create.js +1 -1
  258. package/src/tools/handlers/devtools.js +19 -3
  259. package/src/tools/handlers/devtools.test.js +38 -0
  260. package/src/tools/handlers/featureFlags.js +1 -1
  261. package/src/tools/handlers/flows.js +3 -3
  262. package/src/tools/handlers/hideChrome.js +9 -0
  263. package/src/tools/handlers/paletteTheme.js +35 -0
  264. package/src/tools/handlers/theme.js +1 -1
  265. package/src/tools/registry.js +4 -1
  266. package/src/tools/surfaces/commandList.js +3 -3
  267. package/src/tools/surfaces/mainToolbar.js +3 -3
  268. package/src/tools/surfaces/registry.js +4 -4
  269. package/src/ui/design-modes.ts +2 -2
  270. package/src/ui/viewfinder.ts +1 -1
  271. package/src/vite/server-plugin.js +243 -61
  272. package/src/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
  273. package/src/workshop/features/createCanvas/index.js +1 -1
  274. package/src/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
  275. package/src/workshop/features/createFlow/index.js +1 -1
  276. package/src/workshop/features/createPage/CreatePageForm.jsx +304 -0
  277. package/src/workshop/features/createPage/index.js +1 -1
  278. package/src/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
  279. package/src/workshop/features/createPrototype/index.js +1 -1
  280. package/src/workshop/features/createPrototype/server.js +98 -0
  281. package/src/workshop/features/createStory/CreateStoryForm.jsx +208 -0
  282. package/src/workshop/features/createStory/index.js +1 -1
  283. package/src/workshop/ui/WorkshopPanel.jsx +98 -0
  284. package/src/workshop/ui/mount.ts +1 -1
  285. package/src/worktree/port.js +48 -0
  286. package/src/worktree/serverRegistry.js +120 -0
  287. package/toolbar.config.json +93 -42
  288. package/widgets.config.json +580 -12
  289. package/scaffold/commandpalette.config.json +0 -4
  290. package/scaffold/toolbar.config.json +0 -4
  291. package/src/ActionMenuButton.svelte +0 -119
  292. package/src/AutosyncMenuButton.svelte +0 -397
  293. package/src/CanvasCreateMenu.svelte +0 -295
  294. package/src/CanvasSnap.svelte +0 -87
  295. package/src/CanvasUndoRedo.svelte +0 -108
  296. package/src/CanvasZoomControl.svelte +0 -111
  297. package/src/CanvasZoomToFit.svelte +0 -52
  298. package/src/CommandMenu.svelte +0 -249
  299. package/src/CommandPalette.svelte +0 -33
  300. package/src/CommentsMenuButton.svelte +0 -53
  301. package/src/CoreUIBar.svelte +0 -847
  302. package/src/CreateMenuButton.svelte +0 -133
  303. package/src/DocPanel.svelte +0 -299
  304. package/src/InspectorPanel.svelte +0 -745
  305. package/src/PwaInstallBanner.svelte +0 -124
  306. package/src/SidePanel.svelte +0 -480
  307. package/src/ThemeMenuButton.svelte +0 -132
  308. package/src/comments/ui/AuthModal.svelte +0 -108
  309. package/src/comments/ui/CommentWindow.svelte +0 -333
  310. package/src/comments/ui/CommentsDrawer.svelte +0 -96
  311. package/src/comments/ui/Composer.svelte +0 -65
  312. package/src/lib/components/ui/alert/alert-action.svelte +0 -19
  313. package/src/lib/components/ui/alert/alert-description.svelte +0 -22
  314. package/src/lib/components/ui/alert/alert-title.svelte +0 -22
  315. package/src/lib/components/ui/alert/alert.svelte +0 -38
  316. package/src/lib/components/ui/avatar/avatar-badge.svelte +0 -25
  317. package/src/lib/components/ui/avatar/avatar-fallback.svelte +0 -20
  318. package/src/lib/components/ui/avatar/avatar-group-count.svelte +0 -22
  319. package/src/lib/components/ui/avatar/avatar-group.svelte +0 -22
  320. package/src/lib/components/ui/avatar/avatar-image.svelte +0 -17
  321. package/src/lib/components/ui/avatar/avatar.svelte +0 -24
  322. package/src/lib/components/ui/badge/badge.svelte +0 -44
  323. package/src/lib/components/ui/button/button.svelte +0 -108
  324. package/src/lib/components/ui/card/card-action.svelte +0 -21
  325. package/src/lib/components/ui/card/card-content.svelte +0 -19
  326. package/src/lib/components/ui/card/card-description.svelte +0 -19
  327. package/src/lib/components/ui/card/card-footer.svelte +0 -18
  328. package/src/lib/components/ui/card/card-header.svelte +0 -21
  329. package/src/lib/components/ui/card/card-title.svelte +0 -14
  330. package/src/lib/components/ui/card/card.svelte +0 -21
  331. package/src/lib/components/ui/checkbox/checkbox.svelte +0 -39
  332. package/src/lib/components/ui/collapsible/collapsible-content.svelte +0 -7
  333. package/src/lib/components/ui/collapsible/collapsible-trigger.svelte +0 -7
  334. package/src/lib/components/ui/collapsible/collapsible.svelte +0 -11
  335. package/src/lib/components/ui/dialog/dialog-close.svelte +0 -11
  336. package/src/lib/components/ui/dialog/dialog-content.svelte +0 -42
  337. package/src/lib/components/ui/dialog/dialog-description.svelte +0 -17
  338. package/src/lib/components/ui/dialog/dialog-footer.svelte +0 -29
  339. package/src/lib/components/ui/dialog/dialog-header.svelte +0 -19
  340. package/src/lib/components/ui/dialog/dialog-overlay.svelte +0 -17
  341. package/src/lib/components/ui/dialog/dialog-portal.svelte +0 -7
  342. package/src/lib/components/ui/dialog/dialog-title.svelte +0 -17
  343. package/src/lib/components/ui/dialog/dialog-trigger.svelte +0 -11
  344. package/src/lib/components/ui/dialog/dialog.svelte +0 -7
  345. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +0 -16
  346. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -40
  347. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -27
  348. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +0 -18
  349. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +0 -7
  350. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -24
  351. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +0 -20
  352. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +0 -7
  353. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +0 -16
  354. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +0 -34
  355. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -17
  356. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +0 -19
  357. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +0 -17
  358. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +0 -27
  359. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +0 -7
  360. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -7
  361. package/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +0 -7
  362. package/src/lib/components/ui/input/input.svelte +0 -40
  363. package/src/lib/components/ui/label/label.svelte +0 -20
  364. package/src/lib/components/ui/panel/panel-body.svelte +0 -13
  365. package/src/lib/components/ui/panel/panel-close.svelte +0 -16
  366. package/src/lib/components/ui/panel/panel-content.svelte +0 -33
  367. package/src/lib/components/ui/panel/panel-footer.svelte +0 -13
  368. package/src/lib/components/ui/panel/panel-header.svelte +0 -16
  369. package/src/lib/components/ui/panel/panel-title.svelte +0 -14
  370. package/src/lib/components/ui/panel/panel.svelte +0 -15
  371. package/src/lib/components/ui/popover/popover-close.svelte +0 -7
  372. package/src/lib/components/ui/popover/popover-content.svelte +0 -27
  373. package/src/lib/components/ui/popover/popover-description.svelte +0 -19
  374. package/src/lib/components/ui/popover/popover-header.svelte +0 -19
  375. package/src/lib/components/ui/popover/popover-portal.svelte +0 -7
  376. package/src/lib/components/ui/popover/popover-title.svelte +0 -19
  377. package/src/lib/components/ui/popover/popover-trigger.svelte +0 -17
  378. package/src/lib/components/ui/popover/popover.svelte +0 -7
  379. package/src/lib/components/ui/select/select-content.svelte +0 -40
  380. package/src/lib/components/ui/select/select-group-heading.svelte +0 -19
  381. package/src/lib/components/ui/select/select-group.svelte +0 -17
  382. package/src/lib/components/ui/select/select-item.svelte +0 -38
  383. package/src/lib/components/ui/select/select-label.svelte +0 -18
  384. package/src/lib/components/ui/select/select-portal.svelte +0 -7
  385. package/src/lib/components/ui/select/select-scroll-down-button.svelte +0 -20
  386. package/src/lib/components/ui/select/select-scroll-up-button.svelte +0 -20
  387. package/src/lib/components/ui/select/select-separator.svelte +0 -17
  388. package/src/lib/components/ui/select/select-trigger.svelte +0 -27
  389. package/src/lib/components/ui/select/select.svelte +0 -11
  390. package/src/lib/components/ui/separator/separator.svelte +0 -23
  391. package/src/lib/components/ui/sheet/sheet-close.svelte +0 -7
  392. package/src/lib/components/ui/sheet/sheet-content.svelte +0 -43
  393. package/src/lib/components/ui/sheet/sheet-description.svelte +0 -17
  394. package/src/lib/components/ui/sheet/sheet-footer.svelte +0 -18
  395. package/src/lib/components/ui/sheet/sheet-header.svelte +0 -19
  396. package/src/lib/components/ui/sheet/sheet-overlay.svelte +0 -17
  397. package/src/lib/components/ui/sheet/sheet-portal.svelte +0 -7
  398. package/src/lib/components/ui/sheet/sheet-title.svelte +0 -17
  399. package/src/lib/components/ui/sheet/sheet-trigger.svelte +0 -7
  400. package/src/lib/components/ui/sheet/sheet.svelte +0 -7
  401. package/src/lib/components/ui/textarea/textarea.svelte +0 -21
  402. package/src/lib/components/ui/toggle/toggle.svelte +0 -45
  403. package/src/lib/components/ui/toggle-group/toggle-group-item.svelte +0 -35
  404. package/src/lib/components/ui/toggle-group/toggle-group.svelte +0 -63
  405. package/src/lib/components/ui/tooltip/tooltip-content.svelte +0 -24
  406. package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +0 -27
  407. package/src/lib/components/ui/tooltip/tooltip.svelte +0 -9
  408. package/src/lib/components/ui/trigger-button/trigger-button.svelte +0 -106
  409. package/src/svelte-plugin-ui/components/Icon.svelte +0 -181
  410. package/src/svelte-plugin-ui/components/ModeSwitch.svelte +0 -121
  411. package/src/svelte-plugin-ui/components/ToolbarShell.svelte +0 -150
  412. package/src/svelte-plugin-ui/components/Viewfinder.svelte +0 -1001
  413. package/src/tools/handlers/docs.js +0 -11
  414. package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +0 -139
  415. package/src/workshop/features/createFlow/CreateFlowForm.svelte +0 -314
  416. package/src/workshop/features/createPage/CreatePageForm.svelte +0 -249
  417. package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +0 -287
  418. package/src/workshop/features/createStory/CreateStoryForm.svelte +0 -161
  419. package/src/workshop/ui/WorkshopPanel.svelte +0 -97
@@ -330,6 +330,104 @@ export function createPrototypesHandler(ctx) {
330
330
  return
331
331
  }
332
332
 
333
+ // DELETE /prototypes — delete a prototype directory
334
+ if (routePath === '/prototypes' && method === 'DELETE') {
335
+ const { name, folder } = body
336
+
337
+ if (!name || typeof name !== 'string') {
338
+ sendJson(res, 400, { error: 'Prototype name is required' })
339
+ return
340
+ }
341
+
342
+ const prototypesDir = path.join(root, 'src', 'prototypes')
343
+ let targetDir
344
+
345
+ if (folder) {
346
+ targetDir = path.join(prototypesDir, `${folder}.folder`, name)
347
+ if (!fs.existsSync(targetDir)) {
348
+ targetDir = path.join(prototypesDir, folder, name)
349
+ }
350
+ } else {
351
+ targetDir = path.join(prototypesDir, name)
352
+ }
353
+
354
+ if (!fs.existsSync(targetDir)) {
355
+ sendJson(res, 404, { error: `Prototype "${name}" not found` })
356
+ return
357
+ }
358
+
359
+ // Safety: verify it's actually inside src/prototypes/
360
+ const resolved = path.resolve(targetDir)
361
+ if (!resolved.startsWith(path.resolve(prototypesDir))) {
362
+ sendJson(res, 400, { error: 'Invalid path' })
363
+ return
364
+ }
365
+
366
+ try {
367
+ fs.rmSync(targetDir, { recursive: true, force: true })
368
+ sendJson(res, 200, { success: true, deleted: name })
369
+ } catch (err) {
370
+ sendJson(res, 500, { error: `Failed to delete prototype: ${err.message}` })
371
+ }
372
+ return
373
+ }
374
+
375
+ // PUT /prototypes — update prototype metadata
376
+ if (routePath === '/prototypes' && method === 'PUT') {
377
+ const { name, folder, title, description, author } = body
378
+
379
+ if (!name || typeof name !== 'string') {
380
+ sendJson(res, 400, { error: 'Prototype name is required' })
381
+ return
382
+ }
383
+
384
+ const prototypesDir = path.join(root, 'src', 'prototypes')
385
+ let targetDir
386
+
387
+ if (folder) {
388
+ targetDir = path.join(prototypesDir, `${folder}.folder`, name)
389
+ if (!fs.existsSync(targetDir)) {
390
+ targetDir = path.join(prototypesDir, folder, name)
391
+ }
392
+ } else {
393
+ targetDir = path.join(prototypesDir, name)
394
+ }
395
+
396
+ if (!fs.existsSync(targetDir)) {
397
+ sendJson(res, 404, { error: `Prototype "${name}" not found` })
398
+ return
399
+ }
400
+
401
+ // Find the .prototype.json file
402
+ const files = fs.readdirSync(targetDir)
403
+ const protoJsonFile = files.find(f => f.endsWith('.prototype.json'))
404
+
405
+ if (!protoJsonFile) {
406
+ sendJson(res, 404, { error: `No .prototype.json file found in "${name}"` })
407
+ return
408
+ }
409
+
410
+ try {
411
+ const protoJsonPath = path.join(targetDir, protoJsonFile)
412
+ const json = JSON.parse(fs.readFileSync(protoJsonPath, 'utf-8'))
413
+
414
+ if (!json.meta) json.meta = {}
415
+ if (title !== undefined) json.meta.title = title
416
+ if (description !== undefined) json.meta.description = description
417
+ if (author !== undefined) {
418
+ json.meta.author = typeof author === 'string'
419
+ ? author.split(',').map(a => a.trim()).filter(Boolean)
420
+ : author
421
+ }
422
+
423
+ fs.writeFileSync(protoJsonPath, JSON.stringify(json, null, 2) + '\n', 'utf-8')
424
+ sendJson(res, 200, { success: true, updated: name })
425
+ } catch (err) {
426
+ sendJson(res, 500, { error: `Failed to update prototype metadata: ${err.message}` })
427
+ }
428
+ return
429
+ }
430
+
333
431
  // Unmatched routes fall through — the server plugin compositor handles 404
334
432
  }
335
433
  }
@@ -0,0 +1,208 @@
1
+ import { useState, useMemo, useEffect } from 'react'
2
+ import { Button } from '../../../lib/components/ui/button/index.js'
3
+ import { Input } from '../../../lib/components/ui/input/index.js'
4
+ import { Label } from '../../../lib/components/ui/label/index.js'
5
+ import * as Panel from '../../../lib/components/ui/panel/index.js'
6
+ import * as Alert from '../../../lib/components/ui/alert/index.js'
7
+
8
+ const STORY_SUCCESS_KEY = 'sb-story-created'
9
+
10
+ function getApiUrl() {
11
+ const basePath = window.__STORYBOARD_BASE_PATH__ || '/'
12
+ return basePath.replace(/\/$/, '') + '/_storyboard/canvas'
13
+ }
14
+
15
+ export default function CreateStoryForm({ onClose }) {
16
+ const [name, setName] = useState('')
17
+ const [location, setLocation] = useState('canvas')
18
+ const [format, setFormat] = useState('jsx')
19
+ const [submitting, setSubmitting] = useState(false)
20
+ const [error, setError] = useState(null)
21
+ const [success, setSuccess] = useState(null)
22
+ const [createdPath, setCreatedPath] = useState(null)
23
+ const [canvasId, setCanvasId] = useState('')
24
+
25
+ const kebabName = useMemo(
26
+ () => name.replace(/[^a-zA-Z0-9\s_-]/g, '').trim().replace(/[\s_]+/g, '-').toLowerCase().replace(/-+/g, '-').replace(/^-|-$/g, ''),
27
+ [name]
28
+ )
29
+
30
+ const nameError = useMemo(() => {
31
+ if (name.trim() && !kebabName) return 'Name must contain at least one alphanumeric character'
32
+ if (name.trim() && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(kebabName)) return 'Name must be kebab-case'
33
+ return null
34
+ }, [name, kebabName])
35
+
36
+ const filePreview = kebabName ? `${kebabName}.story.${format}` : ''
37
+ const canSubmit = !!kebabName && !nameError && !submitting
38
+
39
+ useEffect(() => {
40
+ try {
41
+ const bridgeState = window.__storyboardCanvasBridgeState
42
+ if (bridgeState?.canvasId) setCanvasId(bridgeState.canvasId)
43
+ else if (bridgeState?.name) setCanvasId(bridgeState.name)
44
+ } catch { /* empty */ }
45
+
46
+ try {
47
+ const saved = sessionStorage.getItem(STORY_SUCCESS_KEY)
48
+ if (saved) {
49
+ const parsed = JSON.parse(saved)
50
+ setSuccess(parsed.success)
51
+ setCreatedPath(parsed.path)
52
+ sessionStorage.removeItem(STORY_SUCCESS_KEY)
53
+ }
54
+ } catch { /* empty */ }
55
+ }, [])
56
+
57
+ async function submit() {
58
+ if (!canSubmit) return
59
+ setSubmitting(true)
60
+ setError(null)
61
+ setSuccess(null)
62
+ setCreatedPath(null)
63
+ try {
64
+ const res = await fetch(getApiUrl() + '/create-story', {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify({
68
+ name: kebabName,
69
+ location,
70
+ format,
71
+ canvasName: location === 'canvas' ? canvasId : undefined,
72
+ }),
73
+ })
74
+ const data = await res.json()
75
+ if (!res.ok) { setError(data.error || 'Failed to create story'); return }
76
+ setSuccess(`Created ${data.path}`)
77
+ setCreatedPath(data.path)
78
+ try {
79
+ sessionStorage.setItem(STORY_SUCCESS_KEY, JSON.stringify({
80
+ success: `Created ${data.name}.story.${format}`,
81
+ path: data.path,
82
+ }))
83
+ } catch { /* empty */ }
84
+ } catch (err) {
85
+ setError(err.message || 'Network error')
86
+ } finally {
87
+ setSubmitting(false)
88
+ }
89
+ }
90
+
91
+ function handleKeydown(e) {
92
+ if (e.key === 'Enter' && canSubmit) submit()
93
+ }
94
+
95
+ return (
96
+ <>
97
+ <Panel.Header>
98
+ <Panel.Title>Create story</Panel.Title>
99
+ <Panel.Close />
100
+ </Panel.Header>
101
+
102
+ <div className="p-4 pt-2 space-y-3" onKeyDown={handleKeydown}>
103
+ <div className="space-y-1">
104
+ <Label htmlFor="sb-story-name">Component name</Label>
105
+ <Input
106
+ id="sb-story-name"
107
+ placeholder="e.g. user-card"
108
+ autoComplete="off"
109
+ spellCheck={false}
110
+ value={name}
111
+ onChange={(e) => setName(e.target.value)}
112
+ />
113
+ {nameError && <p className="text-sm text-destructive">{nameError}</p>}
114
+ {filePreview && (
115
+ <p className="text-xs text-muted-foreground">
116
+ File: <code className="px-1 py-0.5 bg-muted rounded font-mono text-foreground text-xs">{filePreview}</code>
117
+ </p>
118
+ )}
119
+ </div>
120
+
121
+ <fieldset className="space-y-1.5">
122
+ <Label>Location</Label>
123
+ <div className="flex flex-col gap-1.5">
124
+ <label className="flex items-center gap-2 text-sm cursor-pointer">
125
+ <input
126
+ type="radio"
127
+ name="sb-story-location"
128
+ value="canvas"
129
+ checked={location === 'canvas'}
130
+ onChange={() => setLocation('canvas')}
131
+ className="accent-primary"
132
+ />
133
+ This canvas directory
134
+ </label>
135
+ <label className="flex items-center gap-2 text-sm cursor-pointer">
136
+ <input
137
+ type="radio"
138
+ name="sb-story-location"
139
+ value="components"
140
+ checked={location === 'components'}
141
+ onChange={() => setLocation('components')}
142
+ className="accent-primary"
143
+ />
144
+ <code className="text-xs bg-muted px-1 py-0.5 rounded">src/components/</code>
145
+ </label>
146
+ </div>
147
+ </fieldset>
148
+
149
+ <fieldset className="space-y-1.5">
150
+ <Label>Format</Label>
151
+ <div className="flex gap-3">
152
+ <label className="flex items-center gap-2 text-sm cursor-pointer">
153
+ <input
154
+ type="radio"
155
+ name="sb-story-format"
156
+ value="jsx"
157
+ checked={format === 'jsx'}
158
+ onChange={() => setFormat('jsx')}
159
+ className="accent-primary"
160
+ />
161
+ JSX
162
+ </label>
163
+ <label className="flex items-center gap-2 text-sm cursor-pointer">
164
+ <input
165
+ type="radio"
166
+ name="sb-story-format"
167
+ value="tsx"
168
+ checked={format === 'tsx'}
169
+ onChange={() => setFormat('tsx')}
170
+ className="accent-primary"
171
+ />
172
+ TSX
173
+ </label>
174
+ </div>
175
+ </fieldset>
176
+
177
+ {error && (
178
+ <Alert.Root variant="destructive">
179
+ <Alert.Description>{error}</Alert.Description>
180
+ </Alert.Root>
181
+ )}
182
+ {success && (
183
+ <Alert.Root>
184
+ <Alert.Description className="text-success">
185
+ {success}
186
+ {createdPath && (
187
+ <>
188
+ <br />
189
+ <span className="text-xs text-muted-foreground">
190
+ To edit your component, go to{' '}
191
+ <code className="px-1 py-0.5 bg-muted rounded font-mono text-xs">{createdPath}</code>
192
+ </span>
193
+ </>
194
+ )}
195
+ </Alert.Description>
196
+ </Alert.Root>
197
+ )}
198
+ </div>
199
+
200
+ <Panel.Footer>
201
+ <Button variant="outline" onClick={onClose}>Cancel</Button>
202
+ <Button onClick={submit} disabled={!canSubmit}>
203
+ {submitting ? 'Creating\u2026' : 'Create'}
204
+ </Button>
205
+ </Panel.Footer>
206
+ </>
207
+ )
208
+ }
@@ -5,7 +5,7 @@
5
5
  * This feature provides the workshop UI overlay.
6
6
  */
7
7
 
8
- import CreateStoryForm from './CreateStoryForm.svelte'
8
+ import CreateStoryForm from './CreateStoryForm.jsx'
9
9
 
10
10
  export const name = 'createStory'
11
11
  export const label = 'Create story'
@@ -0,0 +1,98 @@
1
+ import { useState, useMemo, useEffect, useCallback } from 'react'
2
+
3
+ export default function WorkshopPanel({ features = [] }) {
4
+ const [menuOpen, setMenuOpen] = useState(false)
5
+ const [activeOverlay, setActiveOverlay] = useState(null)
6
+ const [visible, setVisible] = useState(true)
7
+
8
+ const activeFeature = useMemo(
9
+ () => (activeOverlay ? features.find((f) => f.overlayId === activeOverlay) ?? null : null),
10
+ [activeOverlay, features]
11
+ )
12
+
13
+ const showOverlay = useCallback((id) => {
14
+ setActiveOverlay(id)
15
+ setMenuOpen(false)
16
+ }, [])
17
+
18
+ const closeOverlay = useCallback(() => setActiveOverlay(null), [])
19
+
20
+ const handleKeydown = useCallback(
21
+ (e) => {
22
+ if (e.key === '.' && (e.metaKey || e.ctrlKey)) {
23
+ setVisible((v) => !v)
24
+ return
25
+ }
26
+ if (e.key === 'Escape') {
27
+ setActiveOverlay((cur) => {
28
+ if (cur) return null
29
+ setMenuOpen(false)
30
+ return cur
31
+ })
32
+ }
33
+ },
34
+ []
35
+ )
36
+
37
+ const handleClickOutside = useCallback((e) => {
38
+ if (!e.target.closest('[data-workshop-panel]')) {
39
+ setMenuOpen(false)
40
+ }
41
+ }, [])
42
+
43
+ useEffect(() => {
44
+ window.addEventListener('keydown', handleKeydown)
45
+ document.addEventListener('click', handleClickOutside)
46
+ return () => {
47
+ window.removeEventListener('keydown', handleKeydown)
48
+ document.removeEventListener('click', handleClickOutside)
49
+ }
50
+ }, [handleKeydown, handleClickOutside])
51
+
52
+ if (!visible) return null
53
+
54
+ const ActiveOverlay = activeFeature?.overlay
55
+
56
+ return (
57
+ <div data-workshop-panel className="fixed bottom-6 right-[76px] z-[9999] font-sans">
58
+ <button
59
+ className="flex items-center p-3 bg-popover text-muted-foreground border border-border rounded-full cursor-pointer shadow-lg hover:scale-105 active:scale-95 transition-transform select-none"
60
+ aria-label="Workshop"
61
+ onClick={() => setMenuOpen((o) => !o)}
62
+ >
63
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
64
+ <path d="M5.433 2.304A4.494 4.494 0 0 0 3.5 6c0 1.598.832 3.002 2.09 3.802.518.328.929.923.902 1.64v.008l-.164 3.337a.75.75 0 1 1-1.498-.073l.163-3.34c.007-.14-.1-.313-.36-.465A5.986 5.986 0 0 1 2 6a5.994 5.994 0 0 1 2.567-4.92 1.482 1.482 0 0 1 1.673-.04c.462.296.76.827.76 1.423v2.076c0 .332.214.572.491.572.268 0 .492-.24.492-.572V2.463c0-.596.298-1.127.76-1.423a1.482 1.482 0 0 1 1.673.04A5.994 5.994 0 0 1 13 6a5.986 5.986 0 0 1-2.633 4.909c-.26.152-.367.325-.36.465l.164 3.34a.75.75 0 1 1-1.498.073l-.164-3.337v-.008c-.027-.717.384-1.312.902-1.64A4.494 4.494 0 0 0 11.5 6a4.494 4.494 0 0 0-1.933-3.696c-.024.017-.067.067-.067.159v2.076c0 1.074-.84 2.072-1.991 2.072-1.161 0-2.009-.998-2.009-2.072V2.463c0-.092-.043-.142-.067-.16Z" />
65
+ </svg>
66
+ </button>
67
+
68
+ {menuOpen && (
69
+ <div className="absolute bottom-14 right-0 min-w-[200px] bg-popover text-popover-foreground border border-border rounded-xl shadow-lg overflow-hidden">
70
+ <div className="px-4 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">Workshop</div>
71
+ <div className="h-px bg-border" />
72
+ {features.map((f) => (
73
+ <button
74
+ key={f.overlayId}
75
+ className="flex items-center gap-2 w-full px-4 py-2 text-sm bg-transparent border-none cursor-pointer text-left hover:bg-accent transition-colors"
76
+ onClick={() => showOverlay(f.overlayId)}
77
+ >
78
+ <span className="text-sm">{f.icon || ''}</span> {f.label}
79
+ </button>
80
+ ))}
81
+ <div className="h-px bg-border" />
82
+ <div className="px-4 py-1.5 text-[11px] text-muted-foreground">Dev-only tools</div>
83
+ </div>
84
+ )}
85
+
86
+ {ActiveOverlay && (
87
+ <div
88
+ className="fixed inset-0 z-[10000] flex items-center justify-center bg-black/50"
89
+ onClick={(e) => { if (e.target === e.currentTarget) closeOverlay() }}
90
+ >
91
+ <div className="w-full max-w-[480px]">
92
+ <ActiveOverlay onClose={closeOverlay} />
93
+ </div>
94
+ </div>
95
+ )}
96
+ </div>
97
+ )
98
+ }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { mountSveltePlugin, type PluginHandle } from '../../svelte-plugin-ui/mount.js'
11
- import WorkshopPanel from './WorkshopPanel.svelte'
11
+ import WorkshopPanel from './WorkshopPanel.jsx'
12
12
  import { features as allFeatures } from '../features/registry.js'
13
13
  import '../../../dist/tailwind.css'
14
14
 
@@ -14,6 +14,7 @@
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, realpathSync } from 'fs'
15
15
  import { join, dirname, basename } from 'path'
16
16
  import { execSync } from 'child_process'
17
+ import { findByWorktree } from './serverRegistry.js'
17
18
 
18
19
  const BASE_PORT = 1234
19
20
 
@@ -130,6 +131,53 @@ export function getPort(worktreeName) {
130
131
  return ports[worktreeName]
131
132
  }
132
133
 
134
+ /**
135
+ * Release a port assignment for a worktree.
136
+ *
137
+ * Removes the entry from ports.json so the port can be reused.
138
+ * Never removes 'main'.
139
+ *
140
+ * @param {string} worktreeName
141
+ */
142
+ export function releasePort(worktreeName) {
143
+ if (worktreeName === 'main') return
144
+
145
+ const portsFile = portsFilePath()
146
+ if (!existsSync(portsFile)) return
147
+
148
+ try {
149
+ const ports = JSON.parse(readFileSync(portsFile, 'utf8'))
150
+ if (!(worktreeName in ports)) return
151
+ delete ports[worktreeName]
152
+ writeFileSync(portsFile, JSON.stringify(ports, null, 2) + '\n')
153
+ } catch { /* ignore corrupt file */ }
154
+ }
155
+
156
+ /**
157
+ * Resolve the port for a running dev server.
158
+ *
159
+ * Checks the server registry (servers.json) first for a live process,
160
+ * then falls back to ports.json assignment. Use this when connecting
161
+ * to an already-running server — it returns the real bound port even
162
+ * when Vite rebinds to a different port than originally assigned.
163
+ *
164
+ * @param {string} worktreeName
165
+ * @returns {number}
166
+ */
167
+ export function resolveRunningPort(worktreeName) {
168
+ try {
169
+ const servers = findByWorktree(worktreeName)
170
+ if (servers.length > 0) {
171
+ const latest = servers.reduce((a, b) =>
172
+ (a.startedAt || '') >= (b.startedAt || '') ? a : b
173
+ )
174
+ return latest.port
175
+ }
176
+ } catch { /* registry unavailable */ }
177
+
178
+ return resolvePort(worktreeName)
179
+ }
180
+
133
181
  /**
134
182
  * Resolve the port for a worktree from .worktrees/ports.json
135
183
  * without assigning a new one if missing.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Server Registry — tracks running dev servers in .storyboard/servers.json.
3
+ *
4
+ * Each server gets a unique hex ID. The registry is pruned on every read
5
+ * to remove entries whose PIDs are no longer alive.
6
+ */
7
+
8
+ import { randomBytes } from 'crypto'
9
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'fs'
10
+ import { join, dirname } from 'path'
11
+ import { repoRoot } from './port.js'
12
+
13
+ /**
14
+ * Absolute path to .storyboard/servers.json.
15
+ */
16
+ export function registryPath(cwd) {
17
+ return join(repoRoot(cwd), '.storyboard', 'servers.json')
18
+ }
19
+
20
+ /**
21
+ * Generate a short unique hex ID (6 chars).
22
+ */
23
+ export function generateId() {
24
+ return randomBytes(3).toString('hex')
25
+ }
26
+
27
+ /**
28
+ * Check whether a PID is alive.
29
+ */
30
+ function isAlive(pid) {
31
+ try {
32
+ process.kill(pid, 0)
33
+ return true
34
+ } catch {
35
+ return false
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Read the registry file. Returns an object keyed by server ID.
41
+ */
42
+ function readRegistry(cwd) {
43
+ const file = registryPath(cwd)
44
+ if (!existsSync(file)) return {}
45
+ try {
46
+ const data = JSON.parse(readFileSync(file, 'utf8'))
47
+ return data.servers || {}
48
+ } catch {
49
+ return {}
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Write the registry atomically (write tmp then rename).
55
+ */
56
+ function writeRegistry(servers, cwd) {
57
+ const file = registryPath(cwd)
58
+ const dir = dirname(file)
59
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
60
+ const tmp = file + '.tmp'
61
+ writeFileSync(tmp, JSON.stringify({ servers }, null, 2) + '\n')
62
+ renameSync(tmp, file)
63
+ }
64
+
65
+ /**
66
+ * Remove entries whose PIDs are dead.
67
+ * @returns {object} pruned servers map
68
+ */
69
+ export function prune(cwd) {
70
+ const servers = readRegistry(cwd)
71
+ const alive = {}
72
+ for (const [id, entry] of Object.entries(servers)) {
73
+ if (isAlive(entry.pid)) {
74
+ alive[id] = entry
75
+ }
76
+ }
77
+ writeRegistry(alive, cwd)
78
+ return alive
79
+ }
80
+
81
+ /**
82
+ * Register a new server entry.
83
+ */
84
+ export function register({ id, worktree, pid, port, background = false }, cwd) {
85
+ const servers = prune(cwd)
86
+ servers[id] = { id, worktree, pid, port, background, startedAt: new Date().toISOString() }
87
+ writeRegistry(servers, cwd)
88
+ return servers[id]
89
+ }
90
+
91
+ /**
92
+ * Unregister a server by ID.
93
+ */
94
+ export function unregister(id, cwd) {
95
+ const servers = readRegistry(cwd)
96
+ delete servers[id]
97
+ writeRegistry(servers, cwd)
98
+ }
99
+
100
+ /**
101
+ * List all live servers (prunes dead ones first).
102
+ */
103
+ export function list(cwd) {
104
+ return Object.values(prune(cwd))
105
+ }
106
+
107
+ /**
108
+ * Find servers running for a given worktree name.
109
+ */
110
+ export function findByWorktree(name, cwd) {
111
+ return list(cwd).filter((s) => s.worktree === name)
112
+ }
113
+
114
+ /**
115
+ * Find a server by its unique ID.
116
+ */
117
+ export function findById(id, cwd) {
118
+ const servers = prune(cwd)
119
+ return servers[id] || null
120
+ }