@dfosco/storyboard 0.5.0-alpha.0

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 (592) hide show
  1. package/commandpalette.config.json +152 -0
  2. package/dist/storyboard-ui.css +1 -0
  3. package/dist/storyboard-ui.js +21328 -0
  4. package/dist/storyboard-ui.js.map +1 -0
  5. package/dist/tailwind.css +2 -0
  6. package/dist/tiny-canvas.css +1 -0
  7. package/dist/tiny-canvas.js +389 -0
  8. package/package.json +121 -0
  9. package/paste.config.json +67 -0
  10. package/scaffold/AGENTS.md +432 -0
  11. package/scaffold/agents/prompt-agent.agent.md +181 -0
  12. package/scaffold/agents/terminal-agent.agent.md +351 -0
  13. package/scaffold/codex/config.toml +246 -0
  14. package/scaffold/deploy.yml +103 -0
  15. package/scaffold/githooks/pre-push +114 -0
  16. package/scaffold/gitignore +64 -0
  17. package/scaffold/manifest.json +56 -0
  18. package/scaffold/preview.yml +181 -0
  19. package/scaffold/scripts/link.sh +26 -0
  20. package/scaffold/scripts/unlink.sh +10 -0
  21. package/scaffold/skills/agent-browser/SKILL.md +260 -0
  22. package/scaffold/skills/canvas/SKILL.md +364 -0
  23. package/scaffold/skills/create/SKILL.md +501 -0
  24. package/scaffold/skills/ship/SKILL.md +237 -0
  25. package/scaffold/skills/storyboard/SKILL.md +360 -0
  26. package/scaffold/skills/update-storyboard/SKILL.md +16 -0
  27. package/scaffold/skills/update-storyboard/update-storyboard-packages.sh +26 -0
  28. package/scaffold/skills/vitest/GENERATION.md +5 -0
  29. package/scaffold/skills/vitest/SKILL.md +52 -0
  30. package/scaffold/skills/vitest/references/advanced-environments.md +264 -0
  31. package/scaffold/skills/vitest/references/advanced-projects.md +300 -0
  32. package/scaffold/skills/vitest/references/advanced-type-testing.md +237 -0
  33. package/scaffold/skills/vitest/references/advanced-vi.md +249 -0
  34. package/scaffold/skills/vitest/references/core-cli.md +166 -0
  35. package/scaffold/skills/vitest/references/core-config.md +174 -0
  36. package/scaffold/skills/vitest/references/core-describe.md +193 -0
  37. package/scaffold/skills/vitest/references/core-expect.md +219 -0
  38. package/scaffold/skills/vitest/references/core-hooks.md +244 -0
  39. package/scaffold/skills/vitest/references/core-test-api.md +233 -0
  40. package/scaffold/skills/vitest/references/features-concurrency.md +250 -0
  41. package/scaffold/skills/vitest/references/features-context.md +238 -0
  42. package/scaffold/skills/vitest/references/features-coverage.md +207 -0
  43. package/scaffold/skills/vitest/references/features-filtering.md +211 -0
  44. package/scaffold/skills/vitest/references/features-mocking.md +265 -0
  45. package/scaffold/skills/vitest/references/features-snapshots.md +207 -0
  46. package/scaffold/skills/worktree/SKILL.md +93 -0
  47. package/scaffold/storyboard.config.json +44 -0
  48. package/src/canvas/Canvas.jsx +78 -0
  49. package/src/canvas/Draggable.jsx +235 -0
  50. package/src/canvas/index.d.ts +41 -0
  51. package/src/canvas/index.js +6 -0
  52. package/src/canvas/style.css +118 -0
  53. package/src/canvas/useResetCanvas.js +17 -0
  54. package/src/canvas/utils.js +136 -0
  55. package/src/core/assets/fonts/IoskeleyMono-Bold.woff2 +0 -0
  56. package/src/core/assets/fonts/IoskeleyMono-Italic.woff2 +0 -0
  57. package/src/core/assets/fonts/IoskeleyMono-Medium.woff2 +0 -0
  58. package/src/core/assets/fonts/IoskeleyMono-Regular.woff2 +0 -0
  59. package/src/core/assets/fonts/IoskeleyMono-SemiBold.woff2 +0 -0
  60. package/src/core/autosync/server.js +714 -0
  61. package/src/core/autosync/server.test.js +158 -0
  62. package/src/core/canvas/__tests__/agent-integration.test.js +596 -0
  63. package/src/core/canvas/__tests__/helpers/browser.js +95 -0
  64. package/src/core/canvas/__tests__/helpers/canvas-api.js +129 -0
  65. package/src/core/canvas/__tests__/helpers/perf.js +118 -0
  66. package/src/core/canvas/__tests__/helpers/setup.js +176 -0
  67. package/src/core/canvas/__tests__/helpers/tmux.js +130 -0
  68. package/src/core/canvas/__tests__/helpers/transcript.js +132 -0
  69. package/src/core/canvas/__tests__/terminal-integration.test.js +177 -0
  70. package/src/core/canvas/collision.js +292 -0
  71. package/src/core/canvas/collision.test.js +371 -0
  72. package/src/core/canvas/compact.js +83 -0
  73. package/src/core/canvas/deriveCanvasId.test.js +40 -0
  74. package/src/core/canvas/githubEmbeds.js +527 -0
  75. package/src/core/canvas/githubEmbeds.test.js +302 -0
  76. package/src/core/canvas/hot-pool.js +766 -0
  77. package/src/core/canvas/identity.js +107 -0
  78. package/src/core/canvas/identity.test.js +100 -0
  79. package/src/core/canvas/materializer.js +259 -0
  80. package/src/core/canvas/materializer.test.js +356 -0
  81. package/src/core/canvas/selectedWidgets.js +270 -0
  82. package/src/core/canvas/selectedWidgets.test.js +321 -0
  83. package/src/core/canvas/server.js +3134 -0
  84. package/src/core/canvas/server.test.js +379 -0
  85. package/src/core/canvas/terminal-config.js +330 -0
  86. package/src/core/canvas/terminal-registry.js +465 -0
  87. package/src/core/canvas/terminal-server.js +1436 -0
  88. package/src/core/canvas/writeGuard.js +53 -0
  89. package/src/core/cli/agent.js +85 -0
  90. package/src/core/cli/branch.js +386 -0
  91. package/src/core/cli/canvasAdd.js +241 -0
  92. package/src/core/cli/canvasBatch.js +98 -0
  93. package/src/core/cli/canvasBounds.js +160 -0
  94. package/src/core/cli/canvasRead.js +236 -0
  95. package/src/core/cli/canvasUpdate.js +179 -0
  96. package/src/core/cli/code.js +67 -0
  97. package/src/core/cli/compact.js +62 -0
  98. package/src/core/cli/create.js +674 -0
  99. package/src/core/cli/dev-helpers.js +53 -0
  100. package/src/core/cli/dev-helpers.test.js +53 -0
  101. package/src/core/cli/dev.js +430 -0
  102. package/src/core/cli/exit.js +38 -0
  103. package/src/core/cli/flags.js +174 -0
  104. package/src/core/cli/flags.test.js +155 -0
  105. package/src/core/cli/index.js +233 -0
  106. package/src/core/cli/intro.js +37 -0
  107. package/src/core/cli/proxy.js +319 -0
  108. package/src/core/cli/proxy.test.js +63 -0
  109. package/src/core/cli/schemas.js +223 -0
  110. package/src/core/cli/server.js +192 -0
  111. package/src/core/cli/serverUrl.js +61 -0
  112. package/src/core/cli/sessions.js +459 -0
  113. package/src/core/cli/setup.js +404 -0
  114. package/src/core/cli/terminal-commands.js +287 -0
  115. package/src/core/cli/terminal-messaging.js +231 -0
  116. package/src/core/cli/terminal-welcome.js +515 -0
  117. package/src/core/cli/updateVersion.js +124 -0
  118. package/src/core/comments/api.js +284 -0
  119. package/src/core/comments/api.test.js +282 -0
  120. package/src/core/comments/auth.js +151 -0
  121. package/src/core/comments/auth.test.js +167 -0
  122. package/src/core/comments/commentCache.js +109 -0
  123. package/src/core/comments/commentCache.test.js +48 -0
  124. package/src/core/comments/commentDrafts.js +68 -0
  125. package/src/core/comments/commentMode.js +63 -0
  126. package/src/core/comments/commentMode.test.js +90 -0
  127. package/src/core/comments/config.js +47 -0
  128. package/src/core/comments/config.test.js +77 -0
  129. package/src/core/comments/graphql.js +65 -0
  130. package/src/core/comments/graphql.test.js +95 -0
  131. package/src/core/comments/index.js +42 -0
  132. package/src/core/comments/metadata.js +52 -0
  133. package/src/core/comments/metadata.test.js +110 -0
  134. package/src/core/comments/queries.js +245 -0
  135. package/src/core/comments/ui/AuthModal.jsx +114 -0
  136. package/src/core/comments/ui/CommentOverlay.js +52 -0
  137. package/src/core/comments/ui/CommentWindow.jsx +329 -0
  138. package/src/core/comments/ui/CommentsDrawer.jsx +102 -0
  139. package/src/core/comments/ui/Composer.jsx +64 -0
  140. package/src/core/comments/ui/authModal.js +66 -0
  141. package/src/core/comments/ui/authModal.test.js +76 -0
  142. package/src/core/comments/ui/comment-cursor-dark.svg +1 -0
  143. package/src/core/comments/ui/comment-cursor.svg +1 -0
  144. package/src/core/comments/ui/comment-layout.css +142 -0
  145. package/src/core/comments/ui/commentWindow.js +121 -0
  146. package/src/core/comments/ui/comments.css +242 -0
  147. package/src/core/comments/ui/commentsDrawer.js +84 -0
  148. package/src/core/comments/ui/composer.js +136 -0
  149. package/src/core/comments/ui/index.js +14 -0
  150. package/src/core/comments/ui/mount.js +687 -0
  151. package/src/core/comments/ui/mount.test.js +336 -0
  152. package/src/core/data/dotPath.js +53 -0
  153. package/src/core/data/dotPath.test.js +114 -0
  154. package/src/core/data/loader.js +409 -0
  155. package/src/core/data/loader.test.js +599 -0
  156. package/src/core/data/viewfinder.js +363 -0
  157. package/src/core/data/viewfinder.test.js +456 -0
  158. package/src/core/devtools/devtools-consumer.js +28 -0
  159. package/src/core/devtools/devtools.js +144 -0
  160. package/src/core/devtools/devtools.test.js +75 -0
  161. package/src/core/devtools/sceneDebug.js +112 -0
  162. package/src/core/devtools/sceneDebug.test.js +141 -0
  163. package/src/core/index.js +124 -0
  164. package/src/core/inspector/fiberWalker.js +239 -0
  165. package/src/core/inspector/highlighter.js +275 -0
  166. package/src/core/inspector/mouseMode.js +259 -0
  167. package/src/core/lib/components/ui/alert/alert-action.jsx +11 -0
  168. package/src/core/lib/components/ui/alert/alert-description.jsx +11 -0
  169. package/src/core/lib/components/ui/alert/alert-title.jsx +11 -0
  170. package/src/core/lib/components/ui/alert/alert.jsx +25 -0
  171. package/src/core/lib/components/ui/alert/index.js +17 -0
  172. package/src/core/lib/components/ui/avatar/avatar-badge.jsx +22 -0
  173. package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
  174. package/src/core/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
  175. package/src/core/lib/components/ui/avatar/avatar-group.jsx +19 -0
  176. package/src/core/lib/components/ui/avatar/avatar-image.jsx +15 -0
  177. package/src/core/lib/components/ui/avatar/avatar.jsx +19 -0
  178. package/src/core/lib/components/ui/avatar/index.js +22 -0
  179. package/src/core/lib/components/ui/badge/badge.jsx +31 -0
  180. package/src/core/lib/components/ui/badge/index.js +2 -0
  181. package/src/core/lib/components/ui/button/button.jsx +100 -0
  182. package/src/core/lib/components/ui/button/index.js +12 -0
  183. package/src/core/lib/components/ui/card/card-action.jsx +11 -0
  184. package/src/core/lib/components/ui/card/card-content.jsx +11 -0
  185. package/src/core/lib/components/ui/card/card-description.jsx +11 -0
  186. package/src/core/lib/components/ui/card/card-footer.jsx +11 -0
  187. package/src/core/lib/components/ui/card/card-header.jsx +19 -0
  188. package/src/core/lib/components/ui/card/card-title.jsx +11 -0
  189. package/src/core/lib/components/ui/card/card.jsx +17 -0
  190. package/src/core/lib/components/ui/card/index.js +25 -0
  191. package/src/core/lib/components/ui/checkbox/checkbox.jsx +29 -0
  192. package/src/core/lib/components/ui/checkbox/index.js +6 -0
  193. package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
  194. package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
  195. package/src/core/lib/components/ui/collapsible/collapsible.jsx +7 -0
  196. package/src/core/lib/components/ui/collapsible/index.js +13 -0
  197. package/src/core/lib/components/ui/dialog/dialog-close.jsx +7 -0
  198. package/src/core/lib/components/ui/dialog/dialog-content.jsx +34 -0
  199. package/src/core/lib/components/ui/dialog/dialog-description.jsx +15 -0
  200. package/src/core/lib/components/ui/dialog/dialog-footer.jsx +23 -0
  201. package/src/core/lib/components/ui/dialog/dialog-header.jsx +11 -0
  202. package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
  203. package/src/core/lib/components/ui/dialog/dialog-portal.jsx +4 -0
  204. package/src/core/lib/components/ui/dialog/dialog-title.jsx +15 -0
  205. package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
  206. package/src/core/lib/components/ui/dialog/dialog.jsx +4 -0
  207. package/src/core/lib/components/ui/dialog/index.js +34 -0
  208. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
  209. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
  210. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
  211. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
  212. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
  213. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
  214. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
  215. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
  216. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
  217. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
  218. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
  219. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
  220. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
  221. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
  222. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
  223. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
  224. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
  225. package/src/core/lib/components/ui/dropdown-menu/index.js +54 -0
  226. package/src/core/lib/components/ui/input/index.js +7 -0
  227. package/src/core/lib/components/ui/input/input.jsx +19 -0
  228. package/src/core/lib/components/ui/label/index.js +7 -0
  229. package/src/core/lib/components/ui/label/label.jsx +19 -0
  230. package/src/core/lib/components/ui/panel/index.js +24 -0
  231. package/src/core/lib/components/ui/panel/panel-body.jsx +11 -0
  232. package/src/core/lib/components/ui/panel/panel-close.jsx +16 -0
  233. package/src/core/lib/components/ui/panel/panel-content.jsx +29 -0
  234. package/src/core/lib/components/ui/panel/panel-footer.jsx +11 -0
  235. package/src/core/lib/components/ui/panel/panel-header.jsx +11 -0
  236. package/src/core/lib/components/ui/panel/panel-title.jsx +12 -0
  237. package/src/core/lib/components/ui/panel/panel.jsx +4 -0
  238. package/src/core/lib/components/ui/popover/index.js +28 -0
  239. package/src/core/lib/components/ui/popover/popover-close.jsx +7 -0
  240. package/src/core/lib/components/ui/popover/popover-content.jsx +22 -0
  241. package/src/core/lib/components/ui/popover/popover-description.jsx +11 -0
  242. package/src/core/lib/components/ui/popover/popover-header.jsx +11 -0
  243. package/src/core/lib/components/ui/popover/popover-portal.jsx +4 -0
  244. package/src/core/lib/components/ui/popover/popover-title.jsx +11 -0
  245. package/src/core/lib/components/ui/popover/popover-trigger.jsx +8 -0
  246. package/src/core/lib/components/ui/popover/popover.jsx +4 -0
  247. package/src/core/lib/components/ui/searchable-list.jsx +160 -0
  248. package/src/core/lib/components/ui/select/index.js +37 -0
  249. package/src/core/lib/components/ui/select/select-content.jsx +30 -0
  250. package/src/core/lib/components/ui/select/select-group-heading.jsx +17 -0
  251. package/src/core/lib/components/ui/select/select-group.jsx +15 -0
  252. package/src/core/lib/components/ui/select/select-item.jsx +26 -0
  253. package/src/core/lib/components/ui/select/select-label.jsx +11 -0
  254. package/src/core/lib/components/ui/select/select-portal.jsx +4 -0
  255. package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
  256. package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
  257. package/src/core/lib/components/ui/select/select-separator.jsx +15 -0
  258. package/src/core/lib/components/ui/select/select-trigger.jsx +25 -0
  259. package/src/core/lib/components/ui/select/select.jsx +4 -0
  260. package/src/core/lib/components/ui/separator/index.js +7 -0
  261. package/src/core/lib/components/ui/separator/separator.jsx +22 -0
  262. package/src/core/lib/components/ui/sheet/index.js +34 -0
  263. package/src/core/lib/components/ui/sheet/sheet-close.jsx +7 -0
  264. package/src/core/lib/components/ui/sheet/sheet-content.jsx +35 -0
  265. package/src/core/lib/components/ui/sheet/sheet-description.jsx +15 -0
  266. package/src/core/lib/components/ui/sheet/sheet-footer.jsx +11 -0
  267. package/src/core/lib/components/ui/sheet/sheet-header.jsx +11 -0
  268. package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
  269. package/src/core/lib/components/ui/sheet/sheet-portal.jsx +4 -0
  270. package/src/core/lib/components/ui/sheet/sheet-title.jsx +15 -0
  271. package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
  272. package/src/core/lib/components/ui/sheet/sheet.jsx +4 -0
  273. package/src/core/lib/components/ui/textarea/index.js +7 -0
  274. package/src/core/lib/components/ui/textarea/textarea.jsx +18 -0
  275. package/src/core/lib/components/ui/toggle/index.js +8 -0
  276. package/src/core/lib/components/ui/toggle/toggle.jsx +36 -0
  277. package/src/core/lib/components/ui/toggle-group/index.js +10 -0
  278. package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
  279. package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
  280. package/src/core/lib/components/ui/tooltip/index.js +3 -0
  281. package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
  282. package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
  283. package/src/core/lib/components/ui/tooltip/tooltip.jsx +11 -0
  284. package/src/core/lib/components/ui/trigger-button/index.js +6 -0
  285. package/src/core/lib/components/ui/trigger-button/trigger-button.css +38 -0
  286. package/src/core/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
  287. package/src/core/lib/utils/index.js +6 -0
  288. package/src/core/logger/devLogger.js +238 -0
  289. package/src/core/logger/devLogger.test.js +193 -0
  290. package/src/core/modes/modes.css +98 -0
  291. package/src/core/modes/modes.js +492 -0
  292. package/src/core/modes/modes.test.js +562 -0
  293. package/src/core/mountStoryboardCore.js +478 -0
  294. package/src/core/rename-watcher/config.json +23 -0
  295. package/src/core/rename-watcher/watcher.js +531 -0
  296. package/src/core/scaffold.js +100 -0
  297. package/src/core/server/index.js +391 -0
  298. package/src/core/session/bodyClasses.js +128 -0
  299. package/src/core/session/bodyClasses.test.js +192 -0
  300. package/src/core/session/hashSubscribe.js +19 -0
  301. package/src/core/session/hashSubscribe.test.js +62 -0
  302. package/src/core/session/hideMode.js +424 -0
  303. package/src/core/session/hideMode.test.js +268 -0
  304. package/src/core/session/interceptHideParams.js +35 -0
  305. package/src/core/session/interceptHideParams.test.js +90 -0
  306. package/src/core/session/localStorage.js +134 -0
  307. package/src/core/session/localStorage.test.js +148 -0
  308. package/src/core/session/session.js +76 -0
  309. package/src/core/session/session.test.js +91 -0
  310. package/src/core/stores/canvasConfig.js +134 -0
  311. package/src/core/stores/canvasConfig.test.js +120 -0
  312. package/src/core/stores/commandActions.js +284 -0
  313. package/src/core/stores/commandPaletteConfig.js +31 -0
  314. package/src/core/stores/configSchema.js +232 -0
  315. package/src/core/stores/configSchema.test.js +72 -0
  316. package/src/core/stores/configStore.js +161 -0
  317. package/src/core/stores/customerModeConfig.js +30 -0
  318. package/src/core/stores/featureFlags.js +127 -0
  319. package/src/core/stores/paletteProviders.js +360 -0
  320. package/src/core/stores/paletteProviders.test.js +186 -0
  321. package/src/core/stores/plugins.js +40 -0
  322. package/src/core/stores/plugins.test.js +68 -0
  323. package/src/core/stores/recentArtifacts.js +68 -0
  324. package/src/core/stores/recentArtifacts.test.js +71 -0
  325. package/src/core/stores/sidePanelStore.ts +143 -0
  326. package/src/core/stores/themeStore.ts +291 -0
  327. package/src/core/stores/toolRegistry.js +227 -0
  328. package/src/core/stores/toolStateStore.js +183 -0
  329. package/src/core/stores/toolStateStore.test.js +220 -0
  330. package/src/core/stores/toolbarConfigStore.js +165 -0
  331. package/src/core/stores/uiConfig.js +64 -0
  332. package/src/core/stores/uiConfig.test.js +63 -0
  333. package/src/core/styles/tailwind.css +204 -0
  334. package/src/core/tools/handlers/autosync.js +12 -0
  335. package/src/core/tools/handlers/canvasAddWidget.js +11 -0
  336. package/src/core/tools/handlers/canvasAgents.js +20 -0
  337. package/src/core/tools/handlers/canvasToolbar.js +56 -0
  338. package/src/core/tools/handlers/commandPalette.js +9 -0
  339. package/src/core/tools/handlers/comments.js +16 -0
  340. package/src/core/tools/handlers/create.js +39 -0
  341. package/src/core/tools/handlers/devtools.js +122 -0
  342. package/src/core/tools/handlers/devtools.test.js +87 -0
  343. package/src/core/tools/handlers/featureFlags.js +21 -0
  344. package/src/core/tools/handlers/flows.js +68 -0
  345. package/src/core/tools/handlers/hideChrome.js +9 -0
  346. package/src/core/tools/handlers/hideToolbars.js +25 -0
  347. package/src/core/tools/handlers/inspector.js +19 -0
  348. package/src/core/tools/handlers/paletteTheme.js +35 -0
  349. package/src/core/tools/handlers/theme.js +9 -0
  350. package/src/core/tools/registry.js +26 -0
  351. package/src/core/tools/surfaces/canvasToolbar.js +10 -0
  352. package/src/core/tools/surfaces/commandList.js +10 -0
  353. package/src/core/tools/surfaces/mainToolbar.js +11 -0
  354. package/src/core/tools/surfaces/registry.js +19 -0
  355. package/src/core/ui/ActionMenuButton.jsx +114 -0
  356. package/src/core/ui/AutosyncMenuButton.css +67 -0
  357. package/src/core/ui/AutosyncMenuButton.jsx +242 -0
  358. package/src/core/ui/BranchSelect.jsx +29 -0
  359. package/src/core/ui/BranchSelect.module.css +30 -0
  360. package/src/core/ui/CanvasAgentsMenu.jsx +89 -0
  361. package/src/core/ui/CanvasCreateMenu.jsx +611 -0
  362. package/src/core/ui/CanvasSnap.css +27 -0
  363. package/src/core/ui/CanvasSnap.jsx +51 -0
  364. package/src/core/ui/CanvasUndoRedo.css +36 -0
  365. package/src/core/ui/CanvasUndoRedo.jsx +62 -0
  366. package/src/core/ui/CanvasZoomControl.css +53 -0
  367. package/src/core/ui/CanvasZoomControl.jsx +49 -0
  368. package/src/core/ui/CanvasZoomToFit.css +18 -0
  369. package/src/core/ui/CanvasZoomToFit.jsx +26 -0
  370. package/src/core/ui/CommandMenu.css +8 -0
  371. package/src/core/ui/CommandMenu.jsx +287 -0
  372. package/src/core/ui/CommandPalette.jsx +35 -0
  373. package/src/core/ui/CommandPaletteTrigger.jsx +25 -0
  374. package/src/core/ui/CommentsMenuButton.jsx +40 -0
  375. package/src/core/ui/CoreUIBar.css +47 -0
  376. package/src/core/ui/CoreUIBar.jsx +905 -0
  377. package/src/core/ui/CreateMenuButton.jsx +117 -0
  378. package/src/core/ui/HideChromeTrigger.jsx +48 -0
  379. package/src/core/ui/Icon.jsx +279 -0
  380. package/src/core/ui/InspectorPanel.css +109 -0
  381. package/src/core/ui/InspectorPanel.jsx +632 -0
  382. package/src/core/ui/PwaInstallBanner.css +42 -0
  383. package/src/core/ui/PwaInstallBanner.jsx +124 -0
  384. package/src/core/ui/SidePanel.jsx +261 -0
  385. package/src/core/ui/ThemeMenuButton.jsx +139 -0
  386. package/src/core/ui/core-ui-colors.css +129 -0
  387. package/src/core/ui/design-modes.ts +7 -0
  388. package/src/core/ui/sidepanel.css +301 -0
  389. package/src/core/ui/viewfinder.ts +7 -0
  390. package/src/core/ui-entry.js +30 -0
  391. package/src/core/utils/fuzzySearch.js +117 -0
  392. package/src/core/utils/fuzzySearch.test.js +119 -0
  393. package/src/core/utils/mobileViewport.js +57 -0
  394. package/src/core/utils/mobileViewport.test.js +68 -0
  395. package/src/core/utils/prodMode.js +38 -0
  396. package/src/core/utils/smoothCorners.js +20 -0
  397. package/src/core/vite/docs-handler.js +155 -0
  398. package/src/core/vite/server-plugin.js +797 -0
  399. package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
  400. package/src/core/workshop/features/createCanvas/index.js +14 -0
  401. package/src/core/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
  402. package/src/core/workshop/features/createFlow/index.js +19 -0
  403. package/src/core/workshop/features/createFlow/server.js +663 -0
  404. package/src/core/workshop/features/createPage/CreatePageForm.jsx +304 -0
  405. package/src/core/workshop/features/createPage/index.js +11 -0
  406. package/src/core/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
  407. package/src/core/workshop/features/createPrototype/index.js +19 -0
  408. package/src/core/workshop/features/createPrototype/server.js +433 -0
  409. package/src/core/workshop/features/createStory/CreateStoryForm.jsx +208 -0
  410. package/src/core/workshop/features/createStory/index.js +14 -0
  411. package/src/core/workshop/features/registry-server.js +22 -0
  412. package/src/core/workshop/features/registry.js +28 -0
  413. package/src/core/workshop/features/templateIndex.js +155 -0
  414. package/src/core/workshop/ui/WorkshopPanel.jsx +98 -0
  415. package/src/core/workshop/ui/mount.ts +6 -0
  416. package/src/core/worktree/port.js +268 -0
  417. package/src/core/worktree/port.test.js +222 -0
  418. package/src/core/worktree/serverRegistry.js +120 -0
  419. package/src/internals/AuthModal/AuthModal.jsx +132 -0
  420. package/src/internals/AuthModal/AuthModal.module.css +221 -0
  421. package/src/internals/BranchBar/BranchBar.jsx +87 -0
  422. package/src/internals/BranchBar/BranchBar.module.css +247 -0
  423. package/src/internals/BranchBar/useBranches.js +93 -0
  424. package/src/internals/BranchBar/useBranches.test.js +68 -0
  425. package/src/internals/CommandPalette/CommandPalette.jsx +1361 -0
  426. package/src/internals/CommandPalette/CreateDialog.jsx +219 -0
  427. package/src/internals/CommandPalette/command-palette.css +180 -0
  428. package/src/internals/FlowError.module.css +30 -0
  429. package/src/internals/Icon.jsx +279 -0
  430. package/src/internals/StoryboardContext.js +3 -0
  431. package/src/internals/Viewfinder.jsx +1479 -0
  432. package/src/internals/Viewfinder.module.css +1540 -0
  433. package/src/internals/Workspace.jsx +7 -0
  434. package/src/internals/__mocks__/virtual-storyboard-data-index.js +4 -0
  435. package/src/internals/canvas/CanvasControls.jsx +112 -0
  436. package/src/internals/canvas/CanvasControls.module.css +135 -0
  437. package/src/internals/canvas/CanvasPage.bridge.test.jsx +387 -0
  438. package/src/internals/canvas/CanvasPage.dragdrop.test.jsx +350 -0
  439. package/src/internals/canvas/CanvasPage.jsx +3092 -0
  440. package/src/internals/canvas/CanvasPage.module.css +187 -0
  441. package/src/internals/canvas/CanvasPage.multiselect.test.jsx +358 -0
  442. package/src/internals/canvas/CanvasToolbar.jsx +73 -0
  443. package/src/internals/canvas/CanvasToolbar.module.css +92 -0
  444. package/src/internals/canvas/ComponentErrorBoundary.jsx +50 -0
  445. package/src/internals/canvas/ConnectorLayer.jsx +208 -0
  446. package/src/internals/canvas/ConnectorLayer.module.css +129 -0
  447. package/src/internals/canvas/MarqueeOverlay.jsx +20 -0
  448. package/src/internals/canvas/PageSelector.jsx +587 -0
  449. package/src/internals/canvas/PageSelector.module.css +261 -0
  450. package/src/internals/canvas/PageSelector.test.jsx +113 -0
  451. package/src/internals/canvas/WebGLContextPool.jsx +292 -0
  452. package/src/internals/canvas/WebGLContextPool.test.jsx +165 -0
  453. package/src/internals/canvas/canvasApi.js +164 -0
  454. package/src/internals/canvas/canvasReloadGuard.js +37 -0
  455. package/src/internals/canvas/canvasReloadGuard.test.js +27 -0
  456. package/src/internals/canvas/canvasTheme.js +118 -0
  457. package/src/internals/canvas/componentIsolate.jsx +165 -0
  458. package/src/internals/canvas/componentSetIsolate.jsx +257 -0
  459. package/src/internals/canvas/computeCanvasBounds.test.js +121 -0
  460. package/src/internals/canvas/connectorGeometry.js +132 -0
  461. package/src/internals/canvas/hotPoolDevLogs.js +25 -0
  462. package/src/internals/canvas/textSelection.js +10 -0
  463. package/src/internals/canvas/textSelection.test.js +26 -0
  464. package/src/internals/canvas/useCanvas.js +126 -0
  465. package/src/internals/canvas/useCanvas.test.js +26 -0
  466. package/src/internals/canvas/useMarqueeSelect.js +213 -0
  467. package/src/internals/canvas/useMarqueeSelect.test.js +78 -0
  468. package/src/internals/canvas/useUndoRedo.js +86 -0
  469. package/src/internals/canvas/useUndoRedo.test.js +231 -0
  470. package/src/internals/canvas/widgets/CodePenEmbed.jsx +293 -0
  471. package/src/internals/canvas/widgets/CodePenEmbed.module.css +161 -0
  472. package/src/internals/canvas/widgets/ComponentSetWidget.jsx +2 -0
  473. package/src/internals/canvas/widgets/ComponentSetWidget.module.css +89 -0
  474. package/src/internals/canvas/widgets/ComponentWidget.jsx +14 -0
  475. package/src/internals/canvas/widgets/ComponentWidget.module.css +0 -0
  476. package/src/internals/canvas/widgets/CropOverlay.jsx +179 -0
  477. package/src/internals/canvas/widgets/CropOverlay.module.css +154 -0
  478. package/src/internals/canvas/widgets/ExpandedPane.jsx +474 -0
  479. package/src/internals/canvas/widgets/ExpandedPane.module.css +179 -0
  480. package/src/internals/canvas/widgets/ExpandedPane.test.jsx +240 -0
  481. package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
  482. package/src/internals/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
  483. package/src/internals/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
  484. package/src/internals/canvas/widgets/FigmaEmbed.jsx +296 -0
  485. package/src/internals/canvas/widgets/FigmaEmbed.module.css +222 -0
  486. package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +151 -0
  487. package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +83 -0
  488. package/src/internals/canvas/widgets/ImageWidget.jsx +287 -0
  489. package/src/internals/canvas/widgets/ImageWidget.module.css +81 -0
  490. package/src/internals/canvas/widgets/LinkPreview.jsx +439 -0
  491. package/src/internals/canvas/widgets/LinkPreview.module.css +585 -0
  492. package/src/internals/canvas/widgets/LinkPreview.test.jsx +193 -0
  493. package/src/internals/canvas/widgets/MarkdownBlock.jsx +354 -0
  494. package/src/internals/canvas/widgets/MarkdownBlock.module.css +377 -0
  495. package/src/internals/canvas/widgets/MarkdownBlock.test.jsx +92 -0
  496. package/src/internals/canvas/widgets/PromptWidget.jsx +428 -0
  497. package/src/internals/canvas/widgets/PromptWidget.module.css +273 -0
  498. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +463 -0
  499. package/src/internals/canvas/widgets/PrototypeEmbed.module.css +579 -0
  500. package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +10 -0
  501. package/src/internals/canvas/widgets/ResizeHandle.jsx +67 -0
  502. package/src/internals/canvas/widgets/ResizeHandle.module.css +29 -0
  503. package/src/internals/canvas/widgets/StickyNote.jsx +92 -0
  504. package/src/internals/canvas/widgets/StickyNote.module.css +70 -0
  505. package/src/internals/canvas/widgets/StickyNote.test.jsx +116 -0
  506. package/src/internals/canvas/widgets/StorySetWidget.jsx +208 -0
  507. package/src/internals/canvas/widgets/StorySetWidget.module.css +89 -0
  508. package/src/internals/canvas/widgets/StoryWidget.jsx +334 -0
  509. package/src/internals/canvas/widgets/StoryWidget.module.css +211 -0
  510. package/src/internals/canvas/widgets/TerminalReadWidget.jsx +146 -0
  511. package/src/internals/canvas/widgets/TerminalReadWidget.module.css +94 -0
  512. package/src/internals/canvas/widgets/TerminalWidget.jsx +704 -0
  513. package/src/internals/canvas/widgets/TerminalWidget.module.css +444 -0
  514. package/src/internals/canvas/widgets/TilesWidget.jsx +300 -0
  515. package/src/internals/canvas/widgets/TilesWidget.module.css +133 -0
  516. package/src/internals/canvas/widgets/WidgetChrome.jsx +580 -0
  517. package/src/internals/canvas/widgets/WidgetChrome.module.css +421 -0
  518. package/src/internals/canvas/widgets/WidgetWrapper.jsx +15 -0
  519. package/src/internals/canvas/widgets/WidgetWrapper.module.css +25 -0
  520. package/src/internals/canvas/widgets/codepenUrl.js +75 -0
  521. package/src/internals/canvas/widgets/codepenUrl.test.js +76 -0
  522. package/src/internals/canvas/widgets/embedInteraction.test.jsx +173 -0
  523. package/src/internals/canvas/widgets/embedOverlay.module.css +35 -0
  524. package/src/internals/canvas/widgets/embedTheme.js +148 -0
  525. package/src/internals/canvas/widgets/expandUtils.js +559 -0
  526. package/src/internals/canvas/widgets/expandUtils.test.js +155 -0
  527. package/src/internals/canvas/widgets/figmaUrl.js +118 -0
  528. package/src/internals/canvas/widgets/figmaUrl.test.js +139 -0
  529. package/src/internals/canvas/widgets/githubUrl.js +82 -0
  530. package/src/internals/canvas/widgets/githubUrl.test.js +74 -0
  531. package/src/internals/canvas/widgets/iframeDevLogs.js +49 -0
  532. package/src/internals/canvas/widgets/iframeDevLogs.test.jsx +81 -0
  533. package/src/internals/canvas/widgets/index.js +42 -0
  534. package/src/internals/canvas/widgets/pasteRules.js +295 -0
  535. package/src/internals/canvas/widgets/pasteRules.test.js +474 -0
  536. package/src/internals/canvas/widgets/snapshotDisplay.test.jsx +211 -0
  537. package/src/internals/canvas/widgets/tilePool.js +23 -0
  538. package/src/internals/canvas/widgets/tiles/diagonal-bl.png +0 -0
  539. package/src/internals/canvas/widgets/tiles/diagonal-br.png +0 -0
  540. package/src/internals/canvas/widgets/tiles/diagonal-tl.png +0 -0
  541. package/src/internals/canvas/widgets/tiles/leaf.png +0 -0
  542. package/src/internals/canvas/widgets/tiles/quarter-tl.png +0 -0
  543. package/src/internals/canvas/widgets/tiles/quarter-tr.png +0 -0
  544. package/src/internals/canvas/widgets/tiles/solid-a.png +0 -0
  545. package/src/internals/canvas/widgets/tiles/solid-b.png +0 -0
  546. package/src/internals/canvas/widgets/widgetConfig.js +291 -0
  547. package/src/internals/canvas/widgets/widgetConfig.test.js +68 -0
  548. package/src/internals/canvas/widgets/widgetIcons.jsx +190 -0
  549. package/src/internals/canvas/widgets/widgetProps.js +133 -0
  550. package/src/internals/context/FormContext.js +13 -0
  551. package/src/internals/context/FormContext.test.js +48 -0
  552. package/src/internals/context.jsx +481 -0
  553. package/src/internals/context.test.jsx +296 -0
  554. package/src/internals/hashPreserver.js +73 -0
  555. package/src/internals/hashPreserver.test.js +107 -0
  556. package/src/internals/hooks/useConfig.js +14 -0
  557. package/src/internals/hooks/useFeatureFlag.js +14 -0
  558. package/src/internals/hooks/useFlows.js +50 -0
  559. package/src/internals/hooks/useFlows.test.js +134 -0
  560. package/src/internals/hooks/useHideMode.js +31 -0
  561. package/src/internals/hooks/useHideMode.test.js +43 -0
  562. package/src/internals/hooks/useLocalStorage.js +57 -0
  563. package/src/internals/hooks/useLocalStorage.test.js +75 -0
  564. package/src/internals/hooks/useMode.js +43 -0
  565. package/src/internals/hooks/useObject.js +101 -0
  566. package/src/internals/hooks/useObject.test.js +74 -0
  567. package/src/internals/hooks/useOverride.js +84 -0
  568. package/src/internals/hooks/useOverride.test.js +71 -0
  569. package/src/internals/hooks/usePrototypeReloadGuard.js +64 -0
  570. package/src/internals/hooks/useRecord.js +158 -0
  571. package/src/internals/hooks/useRecord.test.js +221 -0
  572. package/src/internals/hooks/useScene.js +38 -0
  573. package/src/internals/hooks/useScene.test.js +66 -0
  574. package/src/internals/hooks/useSceneData.js +108 -0
  575. package/src/internals/hooks/useSceneData.test.js +136 -0
  576. package/src/internals/hooks/useSession.js +4 -0
  577. package/src/internals/hooks/useSession.test.js +8 -0
  578. package/src/internals/hooks/useThemeState.js +61 -0
  579. package/src/internals/hooks/useThemeState.test.js +66 -0
  580. package/src/internals/hooks/useUndoRedo.js +28 -0
  581. package/src/internals/hooks/useUndoRedo.test.js +64 -0
  582. package/src/internals/index.js +58 -0
  583. package/src/internals/story/ComponentSetPage.jsx +198 -0
  584. package/src/internals/story/ComponentSetPage.module.css +129 -0
  585. package/src/internals/story/StoryPage.jsx +147 -0
  586. package/src/internals/story/StoryPage.module.css +18 -0
  587. package/src/internals/test-utils.js +45 -0
  588. package/src/internals/vite/data-plugin.js +1508 -0
  589. package/src/internals/vite/data-plugin.test.js +1223 -0
  590. package/src/test-utils.js +44 -0
  591. package/toolbar.config.json +271 -0
  592. package/widgets.config.json +1537 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Git helpers for storyboard dev CLI.
3
+ * Extracted for testability — no side effects on import.
4
+ */
5
+
6
+ import { execFileSync } from 'child_process'
7
+
8
+ /**
9
+ * Check if the working tree has uncommitted changes (staged or unstaged).
10
+ */
11
+ export function hasUncommittedChanges(cwd) {
12
+ try {
13
+ const status = execFileSync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8' }).trim()
14
+ return status.length > 0
15
+ } catch {
16
+ return false
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Check if a local branch exists.
22
+ */
23
+ export function localBranchExists(name, cwd) {
24
+ try {
25
+ execFileSync('git', ['show-ref', '--verify', `refs/heads/${name}`], { cwd, stdio: 'ignore' })
26
+ return true
27
+ } catch {
28
+ return false
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Resolve the default branch for the repo root (main, master, or origin/HEAD target).
34
+ * Returns null if none can be determined.
35
+ */
36
+ export function resolveDefaultBranch(cwd) {
37
+ // Verify we're actually inside a git repository
38
+ try {
39
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd, stdio: 'ignore' })
40
+ } catch {
41
+ return null
42
+ }
43
+ for (const candidate of ['main', 'master']) {
44
+ if (localBranchExists(candidate, cwd)) return candidate
45
+ }
46
+ // Try origin/HEAD
47
+ try {
48
+ const ref = execFileSync('git', ['symbolic-ref', 'refs/remotes/origin/HEAD'], { cwd, encoding: 'utf8' }).trim()
49
+ const name = ref.replace('refs/remotes/origin/', '')
50
+ if (name && localBranchExists(name, cwd)) return name
51
+ } catch { /* no origin/HEAD */ }
52
+ return null
53
+ }
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { hasUncommittedChanges, localBranchExists, resolveDefaultBranch } from './dev-helpers.js'
3
+ import { execSync } from 'child_process'
4
+
5
+ // These tests run against the real git repo — they verify the helpers
6
+ // work correctly with actual git state.
7
+
8
+ const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim()
9
+
10
+ describe('hasUncommittedChanges', () => {
11
+ it('returns a boolean', () => {
12
+ const result = hasUncommittedChanges(repoRoot)
13
+ expect(typeof result).toBe('boolean')
14
+ })
15
+
16
+ it('returns false for non-existent directory', () => {
17
+ expect(hasUncommittedChanges('/tmp/nonexistent-repo-12345')).toBe(false)
18
+ })
19
+ })
20
+
21
+ describe('localBranchExists', () => {
22
+ it('returns true for a branch that exists', () => {
23
+ // The current branch must exist
24
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim()
25
+ expect(localBranchExists(branch, repoRoot)).toBe(true)
26
+ })
27
+
28
+ it('returns false for a branch that does not exist', () => {
29
+ expect(localBranchExists('__nonexistent-branch-xyz-99999__', repoRoot)).toBe(false)
30
+ })
31
+
32
+ it('returns false for invalid cwd', () => {
33
+ expect(localBranchExists('main', '/tmp/nonexistent-repo-12345')).toBe(false)
34
+ })
35
+ })
36
+
37
+ describe('resolveDefaultBranch', () => {
38
+ it('returns a string or null', () => {
39
+ const result = resolveDefaultBranch(repoRoot)
40
+ expect(result === null || typeof result === 'string').toBe(true)
41
+ })
42
+
43
+ it('prefers main over master when main exists', () => {
44
+ // If main exists in this repo, it should be the default
45
+ if (localBranchExists('main', repoRoot)) {
46
+ expect(resolveDefaultBranch(repoRoot)).toBe('main')
47
+ }
48
+ })
49
+
50
+ it('returns null for non-git directory', () => {
51
+ expect(resolveDefaultBranch('/tmp')).toBe(null)
52
+ })
53
+ })
@@ -0,0 +1,430 @@
1
+ /**
2
+ * storyboard dev [branch] — Start Vite with correct base path.
3
+ *
4
+ * If a storyboard server is already running (from another `sb dev`),
5
+ * operates in "client mode": asks the existing server to spawn Vite
6
+ * for this branch, prints the URL, and exits. This allows multiple
7
+ * branches to run simultaneously through the same server + Caddy proxy.
8
+ *
9
+ * Otherwise, starts the server in the foreground as the "owner".
10
+ * Ctrl+C kills Vite, releases the port, and exits cleanly.
11
+ *
12
+ * Usage:
13
+ * storyboard dev # detect worktree from cwd
14
+ * storyboard dev main # start dev for repo root
15
+ * storyboard dev <worktree> # start dev for existing worktree
16
+ * storyboard dev <branch> # auto-create worktree + start dev
17
+ *
18
+ * Main: http://<devDomain>.localhost/
19
+ * Branch: http://<devDomain>.localhost/branch--<name>/
20
+ */
21
+
22
+ import * as p from '@clack/prompts'
23
+ import http from 'node:http'
24
+ import { execFileSync } from 'child_process'
25
+ import { existsSync } from 'fs'
26
+ import { resolve } from 'path'
27
+ import { detectWorktreeName, getPort, releasePort, repoRoot, worktreeDir } from '../worktree/port.js'
28
+ import { generateCaddyfile, generateRouteConfig, upsertCaddyRoute, isCaddyRunning, readDevDomain } from './proxy.js'
29
+ import { startRenameWatcher } from '../rename-watcher/watcher.js'
30
+ import { parseFlags } from './flags.js'
31
+ import { hasUncommittedChanges, localBranchExists, resolveDefaultBranch } from './dev-helpers.js'
32
+ import { compactAll } from '../canvas/compact.js'
33
+
34
+ const flagSchema = {
35
+ port: { type: 'number', description: 'Override dev server port' },
36
+ create: { type: 'boolean', default: true, description: 'Allow creating worktrees/branches (disable with --no-create)' },
37
+ }
38
+
39
+ /**
40
+ * Check if a remote branch exists on origin.
41
+ * @param {string} name
42
+ * @param {string} cwd
43
+ * @returns {boolean}
44
+ */
45
+ function remoteBranchExists(name, cwd) {
46
+ try {
47
+ const result = execFileSync('git', ['ls-remote', '--exit-code', '--heads', 'origin', name], { cwd, encoding: 'utf8' })
48
+ return result.trim().length > 0
49
+ } catch {
50
+ return false
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Create a git worktree and install dependencies.
56
+ * @param {string} name — worktree/branch name
57
+ * @param {string} root — repo root path
58
+ * @param {object} opts
59
+ * @param {boolean} opts.newBranch — create a new branch from HEAD
60
+ * @returns {string} path to the new worktree directory
61
+ */
62
+ function createWorktree(name, root, { newBranch = false } = {}) {
63
+ const targetDir = resolve(root, 'worktrees', name)
64
+
65
+ const gitArgs = newBranch
66
+ ? ['worktree', 'add', targetDir, '-b', name]
67
+ : ['worktree', 'add', targetDir, name]
68
+
69
+ p.log.step(`Creating worktree: worktrees/${name}`)
70
+ execFileSync('git', gitArgs, { cwd: root, stdio: 'inherit' })
71
+
72
+ p.log.step('Installing dependencies…')
73
+ const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
74
+ execFileSync(npmBin, ['install'], { cwd: targetDir, stdio: 'inherit' })
75
+
76
+ getPort(name)
77
+ return targetDir
78
+ }
79
+
80
+ /**
81
+ * Resolve the target worktree for `storyboard dev [branch]`.
82
+ *
83
+ * When no argument is given and the repo root is on a non-main branch,
84
+ * prompts the user to convert it to a proper worktree.
85
+ *
86
+ * @param {string|undefined} branchArg — positional branch argument
87
+ * @param {object} opts
88
+ * @param {boolean} opts.allowCreate — whether creation is allowed
89
+ * @returns {Promise<{ worktreeName: string, targetCwd: string, created: boolean }>}
90
+ */
91
+ async function resolveDevTarget(branchArg, { allowCreate = true } = {}) {
92
+ // No argument — detect from cwd
93
+ if (!branchArg) {
94
+ const detectedName = detectWorktreeName()
95
+
96
+ // Already in a worktree or on main — use cwd as-is
97
+ const root = repoRoot()
98
+ const realCwd = resolve(process.cwd())
99
+ const isAtRoot = realCwd === resolve(root)
100
+ if (detectedName === 'main' || !isAtRoot) {
101
+ return { worktreeName: detectedName, targetCwd: process.cwd(), created: false }
102
+ }
103
+
104
+ // Root is on a non-main branch — check for existing worktree first
105
+ const branch = detectedName
106
+ const existingDir = worktreeDir(branch)
107
+ if (existsSync(resolve(existingDir, '.git'))) {
108
+ p.log.info(`Root is on branch "${branch}" — using existing worktree`)
109
+ return { worktreeName: branch, targetCwd: existingDir, created: false }
110
+ }
111
+
112
+ // No worktree exists — prompt the user to convert
113
+ p.log.warning(`Root is on branch "${branch}" instead of main.`)
114
+ const shouldConvert = await p.confirm({
115
+ message: `Convert "${branch}" to a worktree? (moves branch to worktrees/${branch}/)`,
116
+ initialValue: true,
117
+ })
118
+
119
+ if (p.isCancel(shouldConvert) || !shouldConvert) {
120
+ // User declined — proceed with root as-is (legacy behavior)
121
+ return { worktreeName: detectedName, targetCwd: process.cwd(), created: false }
122
+ }
123
+
124
+ // User accepted — validate and convert
125
+ if (!allowCreate) {
126
+ p.log.error('Cannot convert — --no-create flag is set.')
127
+ process.exit(1)
128
+ }
129
+
130
+ if (hasUncommittedChanges(root)) {
131
+ p.log.error('Cannot convert — uncommitted changes in working tree.')
132
+ p.log.info('Commit or stash your changes first, then run `sb dev` again.')
133
+ process.exit(1)
134
+ }
135
+
136
+ const defaultBranch = resolveDefaultBranch(root)
137
+ if (!defaultBranch) {
138
+ p.log.error('Cannot determine default branch (main/master). Switch root manually.')
139
+ process.exit(1)
140
+ }
141
+
142
+ p.log.step(`Switching root to "${defaultBranch}"`)
143
+ execFileSync('git', ['checkout', defaultBranch], { cwd: root, stdio: 'inherit' })
144
+
145
+ const targetDir = createWorktree(branch, root, { newBranch: false })
146
+
147
+ // Offer to open the new worktree in VS Code
148
+ const shouldOpen = await p.confirm({
149
+ message: 'Open this worktree in VS Code?',
150
+ initialValue: true,
151
+ })
152
+ if (shouldOpen && !p.isCancel(shouldOpen)) {
153
+ try {
154
+ execFileSync('code', [targetDir], { stdio: 'inherit' })
155
+ } catch {
156
+ p.log.warning(`Could not open VS Code. Run: code ${targetDir}`)
157
+ }
158
+ }
159
+
160
+ return { worktreeName: branch, targetCwd: targetDir, created: true }
161
+ }
162
+
163
+ const root = repoRoot()
164
+
165
+ // "main" → repo root
166
+ if (branchArg === 'main') {
167
+ return { worktreeName: 'main', targetCwd: root, created: false }
168
+ }
169
+
170
+ // Existing worktree directory
171
+ const existingDir = worktreeDir(branchArg)
172
+ if (existsSync(resolve(existingDir, '.git'))) {
173
+ return { worktreeName: branchArg, targetCwd: existingDir, created: false }
174
+ }
175
+
176
+ // From here on we need to create — check if allowed
177
+ if (!allowCreate) {
178
+ p.log.error(`Worktree "${branchArg}" does not exist. Use without --no-create to auto-create.`)
179
+ process.exit(1)
180
+ }
181
+
182
+ // Branch exists (local or remote) — create worktree from it
183
+ const hasLocal = localBranchExists(branchArg, root)
184
+ const hasRemote = !hasLocal && remoteBranchExists(branchArg, root)
185
+
186
+ if (hasLocal || hasRemote) {
187
+ const targetDir = createWorktree(branchArg, root, { newBranch: false })
188
+ return { worktreeName: branchArg, targetCwd: targetDir, created: true }
189
+ }
190
+
191
+ // Branch doesn't exist — interactive TTY gets a prompt, non-interactive auto-creates
192
+ const isTTY = process.stdin.isTTY
193
+
194
+ if (isTTY) {
195
+ const confirmed = await p.confirm({
196
+ message: `Branch "${branchArg}" doesn't exist. Create it from HEAD?`,
197
+ })
198
+ if (p.isCancel(confirmed) || !confirmed) {
199
+ p.cancel('Cancelled.')
200
+ process.exit(0)
201
+ }
202
+ } else {
203
+ p.log.step(`Branch "${branchArg}" not found — creating from HEAD`)
204
+ }
205
+
206
+ const targetDir = createWorktree(branchArg, root, { newBranch: true })
207
+ return { worktreeName: branchArg, targetCwd: targetDir, created: true }
208
+ }
209
+
210
+ // ─── Server Detection ───
211
+
212
+ /**
213
+ * Check if a storyboard server is already running on the expected port.
214
+ * Returns the server's health response if running, or null.
215
+ */
216
+ function checkExistingServer(port) {
217
+ return new Promise((resolve) => {
218
+ const req = http.get(`http://localhost:${port}/health`, (res) => {
219
+ let data = ''
220
+ res.on('data', (c) => (data += c))
221
+ res.on('end', () => {
222
+ try { resolve(JSON.parse(data)) }
223
+ catch { resolve(null) }
224
+ })
225
+ })
226
+ req.on('error', () => resolve(null))
227
+ req.setTimeout(2000, () => { req.destroy(); resolve(null) })
228
+ })
229
+ }
230
+
231
+ /**
232
+ * Ask an existing server to spawn Vite for a branch via the switch-branch API.
233
+ * Returns the parsed JSON response.
234
+ */
235
+ function requestSwitchBranch(port, branch) {
236
+ return new Promise((resolve, reject) => {
237
+ const body = JSON.stringify({ branch })
238
+ const req = http.request(
239
+ { hostname: 'localhost', port, path: '/_storyboard/switch-branch', method: 'POST',
240
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } },
241
+ (res) => {
242
+ let data = ''
243
+ res.on('data', (c) => (data += c))
244
+ res.on('end', () => {
245
+ try {
246
+ const json = JSON.parse(data)
247
+ if (res.statusCode >= 400) reject(new Error(json.error || `HTTP ${res.statusCode}`))
248
+ else resolve(json)
249
+ } catch { reject(new Error(`Bad response: ${data}`)) }
250
+ })
251
+ }
252
+ )
253
+ req.on('error', reject)
254
+ req.setTimeout(90_000, () => { req.destroy(); reject(new Error('Timeout waiting for Vite to start')) })
255
+ req.end(body)
256
+ })
257
+ }
258
+
259
+ // ─── Client Mode ───
260
+
261
+ /**
262
+ * Run in client mode: an existing server handles Vite lifecycle.
263
+ * We just ask it to start the branch, print URLs, and exit.
264
+ */
265
+ async function runClientMode(serverPort, worktreeName, domain) {
266
+ const isMain = worktreeName === 'main'
267
+ const basePath = isMain ? '/' : `/branch--${worktreeName}/`
268
+ const proxyUrl = `http://${domain}${basePath}`
269
+
270
+ p.log.info(`Server already running on :${serverPort} — requesting dev session for "${worktreeName}"...`)
271
+
272
+ try {
273
+ const result = await requestSwitchBranch(serverPort, worktreeName)
274
+
275
+ if (result.status === 'already_running') {
276
+ p.log.success(`Already running: ${proxyUrl}`)
277
+ } else {
278
+ p.log.success(`Started: ${proxyUrl}`)
279
+ }
280
+
281
+ p.log.info(`Stop with: npx storyboard server stop ${worktreeName}`)
282
+ p.outro('Ready')
283
+ } catch (err) {
284
+ p.log.error(`Failed to start dev for "${worktreeName}": ${err.message}`)
285
+ process.exit(1)
286
+ }
287
+ }
288
+
289
+ // ─── Owner Mode ───
290
+
291
+ /**
292
+ * Run in owner mode: start the server + Vite in the foreground.
293
+ * This is the original behavior — the process owns the server lifecycle.
294
+ */
295
+ async function runOwnerMode(worktreeName, targetCwd, domain, serverPort) {
296
+ getPort(worktreeName) // allocate port for this worktree
297
+ const isMain = worktreeName === 'main'
298
+ const basePath = isMain ? '/' : `/branch--${worktreeName}/`
299
+ const proxyUrl = `http://${domain}${basePath}`
300
+
301
+ p.log.info('Starting storyboard server...')
302
+
303
+ const { startServer, spawnViteForBranch } = await import('../server/index.js')
304
+
305
+ let serverInstance
306
+ try {
307
+ serverInstance = await startServer()
308
+ } catch (err) {
309
+ if (err.code === 'EADDRINUSE') {
310
+ // Race condition: server started between our health check and bind attempt.
311
+ // Fall back to client mode.
312
+ p.log.info('Server started by another process — switching to client mode...')
313
+ await runClientMode(serverPort, worktreeName, domain)
314
+ return
315
+ }
316
+ throw err
317
+ }
318
+
319
+ // Compact bloated canvas JSONL files before starting Vite
320
+ const compacted = compactAll(targetCwd)
321
+ if (compacted.length > 0) {
322
+ for (const r of compacted) {
323
+ p.log.info(`[compact] ${r.name}: ${(r.before / 1024).toFixed(0)}KB → ${(r.after / 1024).toFixed(0)}KB`)
324
+ }
325
+ }
326
+
327
+ const entry = spawnViteForBranch(worktreeName)
328
+ const renameWatcher = startRenameWatcher(targetCwd)
329
+
330
+ const compactInterval = setInterval(() => {
331
+ try {
332
+ const results = compactAll(targetCwd)
333
+ for (const r of results) {
334
+ p.log.info(`[compact] ${r.name}: ${(r.before / 1024).toFixed(0)}KB → ${(r.after / 1024).toFixed(0)}KB`)
335
+ }
336
+ } catch { /* non-critical */ }
337
+ }, 15 * 60 * 1000)
338
+
339
+ function cleanup() {
340
+ renameWatcher.close()
341
+ clearInterval(compactInterval)
342
+ try { entry.child.kill('SIGTERM') } catch { /* already dead */ }
343
+ try { serverInstance.close() } catch { /* best effort */ }
344
+ releasePort(worktreeName)
345
+ process.exit(0)
346
+ }
347
+ process.on('SIGINT', cleanup)
348
+ process.on('SIGTERM', cleanup)
349
+
350
+ const ready = await (async () => {
351
+ const start = Date.now()
352
+ while (Date.now() - start < 60_000) {
353
+ if (entry.status === 'ready') return true
354
+ await new Promise(r => setTimeout(r, 300))
355
+ }
356
+ return false
357
+ })()
358
+
359
+ if (ready) {
360
+ const actualDirectUrl = `http://localhost:${entry.port}${basePath}`
361
+
362
+ if (isCaddyRunning()) {
363
+ const routeConfig = generateRouteConfig({ [worktreeName]: entry.port })
364
+ if (upsertCaddyRoute(routeConfig)) {
365
+ generateCaddyfile({ [worktreeName]: entry.port })
366
+ }
367
+ p.log.success(`${proxyUrl}`)
368
+ p.log.info(`direct: ${actualDirectUrl}`)
369
+ } else {
370
+ p.log.success(actualDirectUrl)
371
+ p.log.warning('Proxy not running — run `npx storyboard setup` for clean URLs')
372
+ }
373
+ p.outro('Ready')
374
+
375
+ entry.child.stdout.pipe(process.stdout)
376
+ entry.child.stderr.pipe(process.stderr)
377
+ } else {
378
+ p.log.warning(`Vite may still be starting — check ${proxyUrl}`)
379
+ p.outro('Server running')
380
+ }
381
+
382
+ entry.child.on('exit', (code) => {
383
+ renameWatcher.close()
384
+ clearInterval(compactInterval)
385
+ try { serverInstance.close() } catch { /* best effort */ }
386
+ if (code && code !== 0) {
387
+ p.log.error(`Vite exited with code ${code}`)
388
+ }
389
+ process.exit(code ?? 0)
390
+ })
391
+ }
392
+
393
+ // ─── Main ───
394
+
395
+ async function main() {
396
+ const { flags, positional } = parseFlags(process.argv.slice(3), flagSchema)
397
+
398
+ const branchArg = positional[0] || undefined
399
+ const allowCreate = flags.create
400
+
401
+ p.intro('storyboard dev')
402
+
403
+ const { worktreeName, targetCwd, created } = await resolveDevTarget(branchArg, { allowCreate })
404
+
405
+ if (created) {
406
+ p.log.success(`Worktree ready: worktrees/${worktreeName}`)
407
+ } else if (branchArg) {
408
+ p.log.info(`Using ${worktreeName === 'main' ? 'main repo' : `worktrees/${worktreeName}`}`)
409
+ }
410
+
411
+ const domain = readDevDomain(targetCwd)
412
+
413
+ // Check if a storyboard server is already running for this repo
414
+ const { SERVER_PORT } = await import('../server/index.js')
415
+ const existingServer = await checkExistingServer(SERVER_PORT)
416
+
417
+ if (existingServer?.ok) {
418
+ // Validate the running server belongs to the same repo (devDomain match)
419
+ if (existingServer.devDomain && existingServer.devDomain !== domain) {
420
+ p.log.error(`Port ${SERVER_PORT} is in use by a different project (domain: ${existingServer.devDomain}).`)
421
+ p.log.info('Stop it with `npx storyboard exit`, or use a different devDomain in storyboard.config.json.')
422
+ process.exit(1)
423
+ }
424
+ await runClientMode(SERVER_PORT, worktreeName, domain)
425
+ } else {
426
+ await runOwnerMode(worktreeName, targetCwd, domain, SERVER_PORT)
427
+ }
428
+ }
429
+
430
+ main()
@@ -0,0 +1,38 @@
1
+ /**
2
+ * storyboard exit — Stop all running dev servers and the Caddy proxy.
3
+ */
4
+
5
+ import * as p from '@clack/prompts'
6
+ import { list, unregister } from '../worktree/serverRegistry.js'
7
+ import { isCaddyRunning, stopCaddy } from './proxy.js'
8
+
9
+ p.intro('storyboard exit')
10
+
11
+ // 1. Stop all registered dev servers
12
+ const servers = list()
13
+ if (servers.length > 0) {
14
+ let stopped = 0
15
+ for (const s of servers) {
16
+ try {
17
+ process.kill(s.pid, 'SIGTERM')
18
+ stopped++
19
+ } catch { /* already dead */ }
20
+ unregister(s.id)
21
+ }
22
+ p.log.success(`Stopped ${stopped} dev server${stopped !== 1 ? 's' : ''}`)
23
+ } else {
24
+ p.log.info('No dev servers running')
25
+ }
26
+
27
+ // 2. Stop Caddy proxy via admin API (no sudo needed)
28
+ if (isCaddyRunning()) {
29
+ if (stopCaddy()) {
30
+ p.log.success('Proxy stopped')
31
+ } else {
32
+ p.log.warning('Failed to stop proxy via admin API')
33
+ }
34
+ } else {
35
+ p.log.info('Proxy was not running')
36
+ }
37
+
38
+ p.outro('All stopped')