@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
@@ -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 {}
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 {}
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 {}
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
+ }