@dfosco/storyboard-core 4.2.0-beta.3 → 4.2.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 (419) hide show
  1. package/commandpalette.config.json +109 -24
  2. package/dist/storyboard-ui.css +1 -1
  3. package/dist/storyboard-ui.js +16711 -27239
  4. package/dist/storyboard-ui.js.map +1 -1
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +5 -2
  7. package/scaffold/agents/prompt-agent.agent.md +181 -0
  8. package/scaffold/agents/terminal-agent.agent.md +351 -0
  9. package/scaffold/codex/config.toml +246 -0
  10. package/scaffold/manifest.json +5 -10
  11. package/scaffold/skills/canvas/SKILL.md +5 -4
  12. package/scaffold/skills/ship/SKILL.md +1 -1
  13. package/scaffold/storyboard.config.json +11 -1
  14. package/src/ActionMenuButton.jsx +103 -0
  15. package/src/AutosyncMenuButton.css +67 -0
  16. package/src/AutosyncMenuButton.jsx +242 -0
  17. package/src/BranchSelect.jsx +29 -0
  18. package/src/BranchSelect.module.css +30 -0
  19. package/src/CanvasAgentsMenu.jsx +89 -0
  20. package/src/CanvasCreateMenu.jsx +611 -0
  21. package/src/CanvasSnap.css +27 -0
  22. package/src/CanvasSnap.jsx +51 -0
  23. package/src/CanvasUndoRedo.css +36 -0
  24. package/src/CanvasUndoRedo.jsx +62 -0
  25. package/src/CanvasZoomControl.css +53 -0
  26. package/src/CanvasZoomControl.jsx +49 -0
  27. package/src/CanvasZoomToFit.css +18 -0
  28. package/src/CanvasZoomToFit.jsx +26 -0
  29. package/src/CommandMenu.css +8 -0
  30. package/src/CommandMenu.jsx +287 -0
  31. package/src/CommandPalette.jsx +35 -0
  32. package/src/CommandPaletteTrigger.jsx +25 -0
  33. package/src/CommentsMenuButton.jsx +40 -0
  34. package/src/CoreUIBar.css +47 -0
  35. package/src/CoreUIBar.jsx +858 -0
  36. package/src/CreateMenuButton.jsx +117 -0
  37. package/src/HideChromeTrigger.jsx +40 -0
  38. package/src/InspectorPanel.css +109 -0
  39. package/src/InspectorPanel.jsx +632 -0
  40. package/src/PwaInstallBanner.css +42 -0
  41. package/src/PwaInstallBanner.jsx +124 -0
  42. package/src/SidePanel.jsx +261 -0
  43. package/src/ThemeMenuButton.jsx +139 -0
  44. package/src/autosync/server.js +202 -5
  45. package/src/autosync/server.test.js +111 -0
  46. package/src/canvas/__tests__/agent-integration.test.js +596 -0
  47. package/src/canvas/__tests__/helpers/browser.js +95 -0
  48. package/src/canvas/__tests__/helpers/canvas-api.js +129 -0
  49. package/src/canvas/__tests__/helpers/perf.js +118 -0
  50. package/src/canvas/__tests__/helpers/setup.js +176 -0
  51. package/src/canvas/__tests__/helpers/tmux.js +130 -0
  52. package/src/canvas/__tests__/helpers/transcript.js +132 -0
  53. package/src/canvas/__tests__/terminal-integration.test.js +177 -0
  54. package/src/canvas/hot-pool.js +756 -0
  55. package/src/canvas/materializer.js +31 -0
  56. package/src/canvas/materializer.test.js +56 -0
  57. package/src/canvas/selectedWidgets.js +65 -7
  58. package/src/canvas/server.js +1802 -22
  59. package/src/canvas/server.test.js +239 -0
  60. package/src/canvas/terminal-config.js +330 -0
  61. package/src/canvas/terminal-registry.js +41 -3
  62. package/src/canvas/terminal-server.js +1098 -37
  63. package/src/canvas/writeGuard.js +51 -3
  64. package/src/canvasConfig.js +67 -1
  65. package/src/canvasConfig.test.js +79 -1
  66. package/src/cli/agent.js +85 -0
  67. package/src/cli/branch.js +232 -0
  68. package/src/cli/canvasAdd.js +61 -14
  69. package/src/cli/canvasBatch.js +98 -0
  70. package/src/cli/canvasBounds.js +1 -1
  71. package/src/cli/canvasRead.js +1 -1
  72. package/src/cli/canvasUpdate.js +179 -0
  73. package/src/cli/compact.js +0 -2
  74. package/src/cli/create.js +40 -16
  75. package/src/cli/dev.js +158 -84
  76. package/src/cli/exit.js +23 -24
  77. package/src/cli/index.js +55 -2
  78. package/src/cli/proxy.js +96 -37
  79. package/src/cli/schemas.js +22 -4
  80. package/src/cli/server.js +149 -26
  81. package/src/cli/serverUrl.js +8 -3
  82. package/src/cli/sessions.js +133 -7
  83. package/src/cli/setup.js +138 -12
  84. package/src/cli/terminal-commands.js +20 -9
  85. package/src/cli/terminal-messaging.js +231 -0
  86. package/src/cli/terminal-welcome.js +449 -34
  87. package/src/cli/updateVersion.js +1 -1
  88. package/src/commandActions.js +1 -0
  89. package/src/commandPaletteConfig.js +9 -0
  90. package/src/comments/auth.js +2 -1
  91. package/src/comments/ui/AuthModal.jsx +114 -0
  92. package/src/comments/ui/CommentWindow.jsx +329 -0
  93. package/src/comments/ui/CommentsDrawer.jsx +102 -0
  94. package/src/comments/ui/Composer.jsx +64 -0
  95. package/src/comments/ui/authModal.test.js +1 -1
  96. package/src/comments/ui/commentWindow.js +16 -17
  97. package/src/comments/ui/commentsDrawer.js +25 -26
  98. package/src/comments/ui/composer.js +23 -24
  99. package/src/comments/ui/index.js +2 -3
  100. package/src/configSchema.js +59 -1
  101. package/src/configStore.js +161 -0
  102. package/src/core-ui-colors.css +12 -0
  103. package/src/devtools.js +17 -19
  104. package/src/devtools.test.js +18 -9
  105. package/src/featureFlags.js +12 -5
  106. package/src/fuzzySearch.test.js +10 -0
  107. package/src/index.js +15 -3
  108. package/src/lib/components/ui/alert/alert-action.jsx +11 -0
  109. package/src/lib/components/ui/alert/alert-description.jsx +11 -0
  110. package/src/lib/components/ui/alert/alert-title.jsx +11 -0
  111. package/src/lib/components/ui/alert/alert.jsx +25 -0
  112. package/src/lib/components/ui/alert/index.js +15 -15
  113. package/src/lib/components/ui/avatar/avatar-badge.jsx +22 -0
  114. package/src/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
  115. package/src/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
  116. package/src/lib/components/ui/avatar/avatar-group.jsx +19 -0
  117. package/src/lib/components/ui/avatar/avatar-image.jsx +15 -0
  118. package/src/lib/components/ui/avatar/avatar.jsx +19 -0
  119. package/src/lib/components/ui/avatar/index.js +20 -20
  120. package/src/lib/components/ui/badge/badge.jsx +31 -0
  121. package/src/lib/components/ui/badge/index.js +2 -2
  122. package/src/lib/components/ui/button/button.jsx +100 -0
  123. package/src/lib/components/ui/button/index.js +9 -9
  124. package/src/lib/components/ui/card/card-action.jsx +11 -0
  125. package/src/lib/components/ui/card/card-content.jsx +11 -0
  126. package/src/lib/components/ui/card/card-description.jsx +11 -0
  127. package/src/lib/components/ui/card/card-footer.jsx +11 -0
  128. package/src/lib/components/ui/card/card-header.jsx +19 -0
  129. package/src/lib/components/ui/card/card-title.jsx +11 -0
  130. package/src/lib/components/ui/card/card.jsx +17 -0
  131. package/src/lib/components/ui/card/index.js +23 -23
  132. package/src/lib/components/ui/checkbox/checkbox.jsx +29 -0
  133. package/src/lib/components/ui/checkbox/index.js +5 -5
  134. package/src/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
  135. package/src/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
  136. package/src/lib/components/ui/collapsible/collapsible.jsx +7 -0
  137. package/src/lib/components/ui/collapsible/index.js +11 -11
  138. package/src/lib/components/ui/dialog/dialog-close.jsx +7 -0
  139. package/src/lib/components/ui/dialog/dialog-content.jsx +34 -0
  140. package/src/lib/components/ui/dialog/dialog-description.jsx +15 -0
  141. package/src/lib/components/ui/dialog/dialog-footer.jsx +23 -0
  142. package/src/lib/components/ui/dialog/dialog-header.jsx +11 -0
  143. package/src/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
  144. package/src/lib/components/ui/dialog/dialog-portal.jsx +4 -0
  145. package/src/lib/components/ui/dialog/dialog-title.jsx +15 -0
  146. package/src/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
  147. package/src/lib/components/ui/dialog/dialog.jsx +4 -0
  148. package/src/lib/components/ui/dialog/index.js +32 -32
  149. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
  150. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
  151. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
  152. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
  153. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
  154. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
  155. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
  156. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
  157. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
  158. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
  159. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
  160. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
  161. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
  162. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
  163. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
  164. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
  165. package/src/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
  166. package/src/lib/components/ui/dropdown-menu/index.js +52 -52
  167. package/src/lib/components/ui/input/index.js +5 -5
  168. package/src/lib/components/ui/input/input.jsx +19 -0
  169. package/src/lib/components/ui/label/index.js +5 -5
  170. package/src/lib/components/ui/label/label.jsx +19 -0
  171. package/src/lib/components/ui/panel/index.js +21 -21
  172. package/src/lib/components/ui/panel/panel-body.jsx +11 -0
  173. package/src/lib/components/ui/panel/panel-close.jsx +16 -0
  174. package/src/lib/components/ui/panel/panel-content.jsx +29 -0
  175. package/src/lib/components/ui/panel/panel-footer.jsx +11 -0
  176. package/src/lib/components/ui/panel/panel-header.jsx +11 -0
  177. package/src/lib/components/ui/panel/panel-title.jsx +12 -0
  178. package/src/lib/components/ui/panel/panel.jsx +4 -0
  179. package/src/lib/components/ui/popover/index.js +26 -26
  180. package/src/lib/components/ui/popover/popover-close.jsx +7 -0
  181. package/src/lib/components/ui/popover/popover-content.jsx +22 -0
  182. package/src/lib/components/ui/popover/popover-description.jsx +11 -0
  183. package/src/lib/components/ui/popover/popover-header.jsx +11 -0
  184. package/src/lib/components/ui/popover/popover-portal.jsx +4 -0
  185. package/src/lib/components/ui/popover/popover-title.jsx +11 -0
  186. package/src/lib/components/ui/popover/popover-trigger.jsx +8 -0
  187. package/src/lib/components/ui/popover/popover.jsx +4 -0
  188. package/src/lib/components/ui/searchable-list.jsx +160 -0
  189. package/src/lib/components/ui/select/index.js +35 -35
  190. package/src/lib/components/ui/select/select-content.jsx +30 -0
  191. package/src/lib/components/ui/select/select-group-heading.jsx +17 -0
  192. package/src/lib/components/ui/select/select-group.jsx +15 -0
  193. package/src/lib/components/ui/select/select-item.jsx +26 -0
  194. package/src/lib/components/ui/select/select-label.jsx +11 -0
  195. package/src/lib/components/ui/select/select-portal.jsx +4 -0
  196. package/src/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
  197. package/src/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
  198. package/src/lib/components/ui/select/select-separator.jsx +15 -0
  199. package/src/lib/components/ui/select/select-trigger.jsx +25 -0
  200. package/src/lib/components/ui/select/select.jsx +4 -0
  201. package/src/lib/components/ui/separator/index.js +5 -5
  202. package/src/lib/components/ui/separator/separator.jsx +22 -0
  203. package/src/lib/components/ui/sheet/index.js +32 -32
  204. package/src/lib/components/ui/sheet/sheet-close.jsx +7 -0
  205. package/src/lib/components/ui/sheet/sheet-content.jsx +35 -0
  206. package/src/lib/components/ui/sheet/sheet-description.jsx +15 -0
  207. package/src/lib/components/ui/sheet/sheet-footer.jsx +11 -0
  208. package/src/lib/components/ui/sheet/sheet-header.jsx +11 -0
  209. package/src/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
  210. package/src/lib/components/ui/sheet/sheet-portal.jsx +4 -0
  211. package/src/lib/components/ui/sheet/sheet-title.jsx +15 -0
  212. package/src/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
  213. package/src/lib/components/ui/sheet/sheet.jsx +4 -0
  214. package/src/lib/components/ui/textarea/index.js +5 -5
  215. package/src/lib/components/ui/textarea/textarea.jsx +18 -0
  216. package/src/lib/components/ui/toggle/index.js +6 -9
  217. package/src/lib/components/ui/toggle/toggle.jsx +36 -0
  218. package/src/lib/components/ui/toggle-group/index.js +8 -8
  219. package/src/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
  220. package/src/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
  221. package/src/lib/components/ui/tooltip/index.js +3 -3
  222. package/src/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
  223. package/src/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
  224. package/src/lib/components/ui/tooltip/tooltip.jsx +11 -0
  225. package/src/lib/components/ui/trigger-button/index.js +3 -3
  226. package/src/lib/components/ui/trigger-button/trigger-button.css +38 -0
  227. package/src/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
  228. package/src/logger/devLogger.js +238 -0
  229. package/src/logger/devLogger.test.js +193 -0
  230. package/src/modes.test.js +4 -4
  231. package/src/mountStoryboardCore.js +133 -35
  232. package/src/paletteProviders.js +3 -0
  233. package/src/paletteProviders.test.js +2 -2
  234. package/src/server/index.js +104 -40
  235. package/src/sidepanel.css +214 -0
  236. package/src/smoothCorners.js +1 -1
  237. package/src/styles/tailwind.css +1 -1
  238. package/src/svelte-plugin-ui/__tests__/ModeSwitch.test.ts +8 -8
  239. package/src/svelte-plugin-ui/__tests__/ToolbarShell.test.ts +11 -10
  240. package/src/svelte-plugin-ui/components/Icon.css +11 -0
  241. package/src/svelte-plugin-ui/components/Icon.jsx +281 -0
  242. package/src/svelte-plugin-ui/components/ModeSwitch.css +90 -0
  243. package/src/svelte-plugin-ui/components/ModeSwitch.jsx +47 -0
  244. package/src/svelte-plugin-ui/components/ToolbarShell.css +80 -0
  245. package/src/svelte-plugin-ui/components/ToolbarShell.jsx +84 -0
  246. package/src/svelte-plugin-ui/components/Viewfinder.css +412 -0
  247. package/src/svelte-plugin-ui/components/Viewfinder.jsx +513 -0
  248. package/src/svelte-plugin-ui/mount.ts +12 -16
  249. package/src/toolRegistry.js +4 -4
  250. package/src/toolbarConfigStore.js +30 -0
  251. package/src/tools/handlers/autosync.js +1 -1
  252. package/src/tools/handlers/canvasAddWidget.js +1 -1
  253. package/src/tools/handlers/canvasAgents.js +20 -0
  254. package/src/tools/handlers/canvasToolbar.js +8 -8
  255. package/src/tools/handlers/commandPalette.js +9 -0
  256. package/src/tools/handlers/comments.js +2 -2
  257. package/src/tools/handlers/create.js +1 -1
  258. package/src/tools/handlers/devtools.js +19 -3
  259. package/src/tools/handlers/devtools.test.js +38 -0
  260. package/src/tools/handlers/featureFlags.js +1 -1
  261. package/src/tools/handlers/flows.js +3 -3
  262. package/src/tools/handlers/hideChrome.js +9 -0
  263. package/src/tools/handlers/paletteTheme.js +35 -0
  264. package/src/tools/handlers/theme.js +1 -1
  265. package/src/tools/registry.js +4 -1
  266. package/src/tools/surfaces/commandList.js +3 -3
  267. package/src/tools/surfaces/mainToolbar.js +3 -3
  268. package/src/tools/surfaces/registry.js +4 -4
  269. package/src/ui/design-modes.ts +2 -2
  270. package/src/ui/viewfinder.ts +1 -1
  271. package/src/vite/server-plugin.js +243 -61
  272. package/src/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
  273. package/src/workshop/features/createCanvas/index.js +1 -1
  274. package/src/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
  275. package/src/workshop/features/createFlow/index.js +1 -1
  276. package/src/workshop/features/createPage/CreatePageForm.jsx +304 -0
  277. package/src/workshop/features/createPage/index.js +1 -1
  278. package/src/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
  279. package/src/workshop/features/createPrototype/index.js +1 -1
  280. package/src/workshop/features/createPrototype/server.js +98 -0
  281. package/src/workshop/features/createStory/CreateStoryForm.jsx +208 -0
  282. package/src/workshop/features/createStory/index.js +1 -1
  283. package/src/workshop/ui/WorkshopPanel.jsx +98 -0
  284. package/src/workshop/ui/mount.ts +1 -1
  285. package/src/worktree/port.js +48 -0
  286. package/src/worktree/serverRegistry.js +120 -0
  287. package/toolbar.config.json +93 -42
  288. package/widgets.config.json +580 -12
  289. package/scaffold/commandpalette.config.json +0 -4
  290. package/scaffold/toolbar.config.json +0 -4
  291. package/src/ActionMenuButton.svelte +0 -119
  292. package/src/AutosyncMenuButton.svelte +0 -397
  293. package/src/CanvasCreateMenu.svelte +0 -295
  294. package/src/CanvasSnap.svelte +0 -87
  295. package/src/CanvasUndoRedo.svelte +0 -108
  296. package/src/CanvasZoomControl.svelte +0 -111
  297. package/src/CanvasZoomToFit.svelte +0 -52
  298. package/src/CommandMenu.svelte +0 -249
  299. package/src/CommandPalette.svelte +0 -33
  300. package/src/CommentsMenuButton.svelte +0 -53
  301. package/src/CoreUIBar.svelte +0 -847
  302. package/src/CreateMenuButton.svelte +0 -133
  303. package/src/DocPanel.svelte +0 -299
  304. package/src/InspectorPanel.svelte +0 -745
  305. package/src/PwaInstallBanner.svelte +0 -124
  306. package/src/SidePanel.svelte +0 -480
  307. package/src/ThemeMenuButton.svelte +0 -132
  308. package/src/comments/ui/AuthModal.svelte +0 -108
  309. package/src/comments/ui/CommentWindow.svelte +0 -333
  310. package/src/comments/ui/CommentsDrawer.svelte +0 -96
  311. package/src/comments/ui/Composer.svelte +0 -65
  312. package/src/lib/components/ui/alert/alert-action.svelte +0 -19
  313. package/src/lib/components/ui/alert/alert-description.svelte +0 -22
  314. package/src/lib/components/ui/alert/alert-title.svelte +0 -22
  315. package/src/lib/components/ui/alert/alert.svelte +0 -38
  316. package/src/lib/components/ui/avatar/avatar-badge.svelte +0 -25
  317. package/src/lib/components/ui/avatar/avatar-fallback.svelte +0 -20
  318. package/src/lib/components/ui/avatar/avatar-group-count.svelte +0 -22
  319. package/src/lib/components/ui/avatar/avatar-group.svelte +0 -22
  320. package/src/lib/components/ui/avatar/avatar-image.svelte +0 -17
  321. package/src/lib/components/ui/avatar/avatar.svelte +0 -24
  322. package/src/lib/components/ui/badge/badge.svelte +0 -44
  323. package/src/lib/components/ui/button/button.svelte +0 -108
  324. package/src/lib/components/ui/card/card-action.svelte +0 -21
  325. package/src/lib/components/ui/card/card-content.svelte +0 -19
  326. package/src/lib/components/ui/card/card-description.svelte +0 -19
  327. package/src/lib/components/ui/card/card-footer.svelte +0 -18
  328. package/src/lib/components/ui/card/card-header.svelte +0 -21
  329. package/src/lib/components/ui/card/card-title.svelte +0 -14
  330. package/src/lib/components/ui/card/card.svelte +0 -21
  331. package/src/lib/components/ui/checkbox/checkbox.svelte +0 -39
  332. package/src/lib/components/ui/collapsible/collapsible-content.svelte +0 -7
  333. package/src/lib/components/ui/collapsible/collapsible-trigger.svelte +0 -7
  334. package/src/lib/components/ui/collapsible/collapsible.svelte +0 -11
  335. package/src/lib/components/ui/dialog/dialog-close.svelte +0 -11
  336. package/src/lib/components/ui/dialog/dialog-content.svelte +0 -42
  337. package/src/lib/components/ui/dialog/dialog-description.svelte +0 -17
  338. package/src/lib/components/ui/dialog/dialog-footer.svelte +0 -29
  339. package/src/lib/components/ui/dialog/dialog-header.svelte +0 -19
  340. package/src/lib/components/ui/dialog/dialog-overlay.svelte +0 -17
  341. package/src/lib/components/ui/dialog/dialog-portal.svelte +0 -7
  342. package/src/lib/components/ui/dialog/dialog-title.svelte +0 -17
  343. package/src/lib/components/ui/dialog/dialog-trigger.svelte +0 -11
  344. package/src/lib/components/ui/dialog/dialog.svelte +0 -7
  345. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +0 -16
  346. package/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -40
  347. package/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -27
  348. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +0 -18
  349. package/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +0 -7
  350. package/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -24
  351. package/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +0 -20
  352. package/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte +0 -7
  353. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +0 -16
  354. package/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +0 -34
  355. package/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -17
  356. package/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +0 -19
  357. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +0 -17
  358. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +0 -27
  359. package/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte +0 -7
  360. package/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -7
  361. package/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte +0 -7
  362. package/src/lib/components/ui/input/input.svelte +0 -40
  363. package/src/lib/components/ui/label/label.svelte +0 -20
  364. package/src/lib/components/ui/panel/panel-body.svelte +0 -13
  365. package/src/lib/components/ui/panel/panel-close.svelte +0 -16
  366. package/src/lib/components/ui/panel/panel-content.svelte +0 -33
  367. package/src/lib/components/ui/panel/panel-footer.svelte +0 -13
  368. package/src/lib/components/ui/panel/panel-header.svelte +0 -16
  369. package/src/lib/components/ui/panel/panel-title.svelte +0 -14
  370. package/src/lib/components/ui/panel/panel.svelte +0 -15
  371. package/src/lib/components/ui/popover/popover-close.svelte +0 -7
  372. package/src/lib/components/ui/popover/popover-content.svelte +0 -27
  373. package/src/lib/components/ui/popover/popover-description.svelte +0 -19
  374. package/src/lib/components/ui/popover/popover-header.svelte +0 -19
  375. package/src/lib/components/ui/popover/popover-portal.svelte +0 -7
  376. package/src/lib/components/ui/popover/popover-title.svelte +0 -19
  377. package/src/lib/components/ui/popover/popover-trigger.svelte +0 -17
  378. package/src/lib/components/ui/popover/popover.svelte +0 -7
  379. package/src/lib/components/ui/select/select-content.svelte +0 -40
  380. package/src/lib/components/ui/select/select-group-heading.svelte +0 -19
  381. package/src/lib/components/ui/select/select-group.svelte +0 -17
  382. package/src/lib/components/ui/select/select-item.svelte +0 -38
  383. package/src/lib/components/ui/select/select-label.svelte +0 -18
  384. package/src/lib/components/ui/select/select-portal.svelte +0 -7
  385. package/src/lib/components/ui/select/select-scroll-down-button.svelte +0 -20
  386. package/src/lib/components/ui/select/select-scroll-up-button.svelte +0 -20
  387. package/src/lib/components/ui/select/select-separator.svelte +0 -17
  388. package/src/lib/components/ui/select/select-trigger.svelte +0 -27
  389. package/src/lib/components/ui/select/select.svelte +0 -11
  390. package/src/lib/components/ui/separator/separator.svelte +0 -23
  391. package/src/lib/components/ui/sheet/sheet-close.svelte +0 -7
  392. package/src/lib/components/ui/sheet/sheet-content.svelte +0 -43
  393. package/src/lib/components/ui/sheet/sheet-description.svelte +0 -17
  394. package/src/lib/components/ui/sheet/sheet-footer.svelte +0 -18
  395. package/src/lib/components/ui/sheet/sheet-header.svelte +0 -19
  396. package/src/lib/components/ui/sheet/sheet-overlay.svelte +0 -17
  397. package/src/lib/components/ui/sheet/sheet-portal.svelte +0 -7
  398. package/src/lib/components/ui/sheet/sheet-title.svelte +0 -17
  399. package/src/lib/components/ui/sheet/sheet-trigger.svelte +0 -7
  400. package/src/lib/components/ui/sheet/sheet.svelte +0 -7
  401. package/src/lib/components/ui/textarea/textarea.svelte +0 -21
  402. package/src/lib/components/ui/toggle/toggle.svelte +0 -45
  403. package/src/lib/components/ui/toggle-group/toggle-group-item.svelte +0 -35
  404. package/src/lib/components/ui/toggle-group/toggle-group.svelte +0 -63
  405. package/src/lib/components/ui/tooltip/tooltip-content.svelte +0 -24
  406. package/src/lib/components/ui/tooltip/tooltip-trigger.svelte +0 -27
  407. package/src/lib/components/ui/tooltip/tooltip.svelte +0 -9
  408. package/src/lib/components/ui/trigger-button/trigger-button.svelte +0 -106
  409. package/src/svelte-plugin-ui/components/Icon.svelte +0 -181
  410. package/src/svelte-plugin-ui/components/ModeSwitch.svelte +0 -121
  411. package/src/svelte-plugin-ui/components/ToolbarShell.svelte +0 -150
  412. package/src/svelte-plugin-ui/components/Viewfinder.svelte +0 -1001
  413. package/src/tools/handlers/docs.js +0 -11
  414. package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +0 -139
  415. package/src/workshop/features/createFlow/CreateFlowForm.svelte +0 -314
  416. package/src/workshop/features/createPage/CreatePageForm.svelte +0 -249
  417. package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +0 -287
  418. package/src/workshop/features/createStory/CreateStoryForm.svelte +0 -161
  419. package/src/workshop/ui/WorkshopPanel.svelte +0 -97
@@ -0,0 +1,29 @@
1
+ import { forwardRef } from "react";
2
+ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
3
+ import { useToggleGroupCtx } from "./toggle-group.jsx";
4
+ import { cn } from "../../../utils/index.js";
5
+ import { toggleVariants } from "../../../components/ui/toggle/index.js";
6
+
7
+ const ToggleGroupItem = forwardRef(function ToggleGroupItem({ className, variant, size, ...props }, ref) {
8
+ const ctx = useToggleGroupCtx();
9
+
10
+ return (
11
+ <ToggleGroupPrimitive.Item
12
+ ref={ref}
13
+ data-slot="toggle-group-item"
14
+ data-variant={ctx.variant || variant}
15
+ data-size={ctx.size || size}
16
+ data-spacing={ctx.spacing}
17
+ className={cn(
18
+ "group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
19
+ toggleVariants({
20
+ variant: ctx.variant || variant,
21
+ size: ctx.size || size,
22
+ }),
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ );
28
+ });
29
+ export default ToggleGroupItem;
@@ -0,0 +1,43 @@
1
+ import { forwardRef, createContext, useContext, useMemo } from "react";
2
+ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
3
+ import { cn } from "../../../utils/index.js";
4
+
5
+ export const ToggleGroupContext = createContext({
6
+ variant: "default",
7
+ size: "default",
8
+ spacing: 0,
9
+ orientation: "horizontal",
10
+ });
11
+
12
+ export function useToggleGroupCtx() {
13
+ return useContext(ToggleGroupContext);
14
+ }
15
+
16
+ const ToggleGroup = forwardRef(function ToggleGroup(
17
+ { className, variant = "default", size = "default", spacing = 0, orientation = "horizontal", children, ...props },
18
+ ref
19
+ ) {
20
+ const ctx = useMemo(() => ({ variant, size, spacing, orientation }), [variant, size, spacing, orientation]);
21
+
22
+ return (
23
+ <ToggleGroupContext.Provider value={ctx}>
24
+ <ToggleGroupPrimitive.Root
25
+ ref={ref}
26
+ orientation={orientation}
27
+ data-slot="toggle-group"
28
+ data-variant={variant}
29
+ data-size={size}
30
+ data-spacing={spacing}
31
+ style={{ '--sb--gap': spacing }}
32
+ className={cn(
33
+ "rounded-lg data-[size=sm]:rounded-[min(var(--sb--radius-md),10px)] group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--sb--gap))] data-vertical:flex-col data-vertical:items-stretch",
34
+ className
35
+ )}
36
+ {...props}
37
+ >
38
+ {children}
39
+ </ToggleGroupPrimitive.Root>
40
+ </ToggleGroupContext.Provider>
41
+ );
42
+ });
43
+ export default ToggleGroup;
@@ -1,3 +1,3 @@
1
- export { default as Root } from "./tooltip.svelte";
2
- export { default as Trigger } from "./tooltip-trigger.svelte";
3
- export { default as Content } from "./tooltip-content.svelte";
1
+ export { default as Root } from "./tooltip.jsx";
2
+ export { default as Trigger } from "./tooltip-trigger.jsx";
3
+ export { default as Content } from "./tooltip-content.jsx";
@@ -0,0 +1,21 @@
1
+ import { forwardRef } from "react";
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
+ import { cn } from "../../../utils/index.js";
4
+
5
+ const TooltipContent = forwardRef(function TooltipContent({ className, sideOffset = 8, ...props }, ref) {
6
+ return (
7
+ <TooltipPrimitive.Portal>
8
+ <TooltipPrimitive.Content
9
+ ref={ref}
10
+ data-slot="tooltip-content"
11
+ sideOffset={sideOffset}
12
+ className={cn(
13
+ "font-sans bg-slate-700 text-white text-xs px-2.5 py-1.5 rounded-lg shadow-lg z-[10001] animate-in fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ </TooltipPrimitive.Portal>
19
+ );
20
+ });
21
+ export default TooltipContent;
@@ -0,0 +1,23 @@
1
+ import { forwardRef } from "react";
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
+
4
+ const TooltipTrigger = forwardRef(function TooltipTrigger({ asChild, children, ...props }, ref) {
5
+ if (asChild) {
6
+ return (
7
+ <TooltipPrimitive.Trigger ref={ref} asChild {...props}>
8
+ {children}
9
+ </TooltipPrimitive.Trigger>
10
+ );
11
+ }
12
+ return (
13
+ <TooltipPrimitive.Trigger ref={ref} asChild {...props}>
14
+ <span
15
+ tabIndex={-1}
16
+ style={{ display: "inline-flex" }}
17
+ >
18
+ {children}
19
+ </span>
20
+ </TooltipPrimitive.Trigger>
21
+ );
22
+ });
23
+ export default TooltipTrigger;
@@ -0,0 +1,11 @@
1
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2
+
3
+ export default function Tooltip({ children, ...props }) {
4
+ return (
5
+ <TooltipPrimitive.Provider delayDuration={50}>
6
+ <TooltipPrimitive.Root {...props}>
7
+ {children}
8
+ </TooltipPrimitive.Root>
9
+ </TooltipPrimitive.Provider>
10
+ );
11
+ }
@@ -1,6 +1,6 @@
1
- import Root from "./trigger-button.svelte";
1
+ import Root from "./trigger-button.jsx";
2
2
 
3
3
  export {
4
- Root,
5
- Root as TriggerButton,
4
+ Root,
5
+ Root as TriggerButton,
6
6
  };
@@ -0,0 +1,38 @@
1
+ [data-trigger-button] {
2
+ display: inline-flex;
3
+ position: relative;
4
+ }
5
+ [data-trigger-button] [data-slot="button-wrapper"] {
6
+ --sb--sc-border-color: var(--sb--trigger-border, var(--color-slate-400));
7
+ --sb--sc-border-width: var(--sb--trigger-border-width, 3px);
8
+ }
9
+ /* Accent-colored border/gap on focus — follows the superellipse shape */
10
+ [data-trigger-button] [data-slot="button-wrapper"]:has([data-slot="button"]:focus-visible) {
11
+ --sb--sc-border-color: hsl(212 92% 45%);
12
+ }
13
+ [data-trigger-button] [data-slot="button"] {
14
+ background-color: var(--sb--trigger-bg, var(--color-slate-100));
15
+ color: var(--sb--trigger-text, var(--color-slate-600));
16
+ }
17
+ [data-trigger-button] [data-slot="button"]:hover,
18
+ [data-trigger-button] [data-slot="button"]:focus-visible,
19
+ [data-trigger-button] [data-slot="button"][aria-expanded="true"],
20
+ [data-trigger-button][data-active] [data-slot="button"] {
21
+ background-color: var(--sb--trigger-bg-hover, var(--color-slate-300));
22
+ }
23
+
24
+ /* Inactive: disabled-looking, no interaction */
25
+ [data-trigger-button][data-inactive] {
26
+ opacity: 0.45;
27
+ pointer-events: none;
28
+ }
29
+
30
+ /* Dimmed: reduced visibility, interactive on hover/focus */
31
+ [data-trigger-button][data-dimmed] {
32
+ opacity: 0.3;
33
+ transition: opacity 200ms;
34
+ }
35
+ [data-trigger-button][data-dimmed]:hover,
36
+ [data-trigger-button][data-dimmed]:focus-within {
37
+ opacity: 1;
38
+ }
@@ -0,0 +1,63 @@
1
+ import './trigger-button.css';
2
+ import { useMemo, forwardRef } from "react";
3
+ import { cn } from "../../../utils/index.js";
4
+ import { registerSmoothCorners } from '../../../../smoothCorners.js';
5
+ import { Button } from '../../../components/ui/button/index.js';
6
+
7
+ registerSmoothCorners();
8
+
9
+ const TriggerButton = forwardRef(function TriggerButton({
10
+ className,
11
+ wrapperClass = "",
12
+ active = false,
13
+ inactive = false,
14
+ dimmed = false,
15
+ localOnly = false,
16
+ size = "icon-2xl",
17
+ children,
18
+ // Separate Radix trigger props from button props
19
+ 'aria-expanded': ariaExpanded,
20
+ 'data-state': dataState,
21
+ 'aria-haspopup': ariaHaspopup,
22
+ ...props
23
+ }, ref) {
24
+ const borderWidth = useMemo(
25
+ () => ['icon-2xl', 'icon-xl', '2xl', 'xl'].includes(size) ? '3px' : '2px',
26
+ [size]
27
+ );
28
+
29
+ return (
30
+ <span
31
+ ref={ref}
32
+ data-trigger-button=""
33
+ data-active={active || undefined}
34
+ data-inactive={inactive || undefined}
35
+ data-dimmed={dimmed || undefined}
36
+ data-local-only={localOnly || undefined}
37
+ style={{ '--sb--trigger-border-width': borderWidth }}
38
+ aria-expanded={ariaExpanded}
39
+ data-state={dataState}
40
+ aria-haspopup={ariaHaspopup}
41
+ >
42
+ <Button
43
+ variant="trigger"
44
+ size={size}
45
+ disabled={inactive}
46
+ wrapperClass={cn(
47
+ "smooth-corners [--sb--smooth-corners:4] hover:rotate-2 focus-visible:rotate-2 transition-transform",
48
+ active && !inactive && "rotate-2",
49
+ wrapperClass
50
+ )}
51
+ className={cn(
52
+ "smooth-corners leading-none font-semibold",
53
+ className
54
+ )}
55
+ {...props}
56
+ >
57
+ {children}
58
+ </Button>
59
+ </span>
60
+ );
61
+ });
62
+
63
+ export default TriggerButton;
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Dev Logger — structured JSONL logging for storyboard dev server errors.
3
+ *
4
+ * Writes one JSONL file per day to `.storyboard/logs/{YYYY-MM-DD}.jsonl`.
5
+ * Automatically enriches log entries with context extracted from request URLs
6
+ * (canvas name, prototype, page, etc.).
7
+ *
8
+ * Usage:
9
+ * import { createDevLogger } from './logger/devLogger.js'
10
+ * const logger = createDevLogger({ root, devDomain, branch })
11
+ * logger.logResponse({ status: 404, method: 'GET', url: '/...', route: 'canvas', error: '...' })
12
+ * logger.logEvent('warn', 'Something happened', { extra: 'context' })
13
+ */
14
+
15
+ import fs from 'node:fs'
16
+ import path from 'node:path'
17
+
18
+ const LOGS_DIR_NAME = 'logs'
19
+ const STORYBOARD_DIR = '.storyboard'
20
+ const MAX_AGE_DAYS = 7
21
+
22
+ /**
23
+ * Format a Date as YYYY-MM-DD.
24
+ */
25
+ function dateStamp(date = new Date()) {
26
+ return date.toISOString().slice(0, 10)
27
+ }
28
+
29
+ /**
30
+ * Extract contextual metadata from a request URL.
31
+ * Parses query params and path segments to find canvas, prototype, and page.
32
+ */
33
+ function extractContext(url) {
34
+ const ctx = { canvas: null, prototype: null, page: null }
35
+ if (!url) return ctx
36
+
37
+ try {
38
+ const parsed = new URL(url, 'http://localhost')
39
+ const params = parsed.searchParams
40
+
41
+ // Canvas name from ?name= query param (used by canvas CRUD routes)
42
+ if (params.has('name')) ctx.canvas = params.get('name')
43
+
44
+ // Prototype from path: /PrototypeName/... or ?prototype=
45
+ if (params.has('prototype')) ctx.prototype = params.get('prototype')
46
+
47
+ // Page from path segments or ?page=
48
+ if (params.has('page')) ctx.page = params.get('page')
49
+
50
+ // Infer from path: /_storyboard/canvas/... → canvas route
51
+ const pathname = parsed.pathname
52
+ if (!ctx.canvas && pathname.includes('/canvas/')) {
53
+ const segments = pathname.split('/').filter(Boolean)
54
+ const canvasIdx = segments.indexOf('canvas')
55
+ if (canvasIdx >= 0 && segments[canvasIdx + 1]) {
56
+ const sub = segments[canvasIdx + 1]
57
+ // Skip known sub-routes
58
+ if (!['read', 'list', 'update', 'widget', 'create', 'folders',
59
+ 'connector', 'stories', 'create-story', 'github', 'image',
60
+ 'images', 'terminal-buffer', 'terminal-snapshot',
61
+ 'rename-page', 'reorder-pages', 'page-order',
62
+ 'update-folder-meta', 'duplicate', 'image-proxy'].includes(sub)) {
63
+ ctx.canvas = sub
64
+ }
65
+ }
66
+ }
67
+ } catch {
68
+ // URL parsing failed — return empty context
69
+ }
70
+
71
+ return ctx
72
+ }
73
+
74
+ /**
75
+ * Prune log files older than MAX_AGE_DAYS.
76
+ */
77
+ function pruneOldLogs(logsDir) {
78
+ try {
79
+ const files = fs.readdirSync(logsDir)
80
+ const cutoff = Date.now() - MAX_AGE_DAYS * 24 * 60 * 60 * 1000
81
+
82
+ for (const file of files) {
83
+ if (!file.endsWith('.jsonl')) continue
84
+ const dateStr = file.replace('.jsonl', '')
85
+ const fileDate = new Date(dateStr + 'T00:00:00Z')
86
+ if (isNaN(fileDate.getTime())) continue
87
+ if (fileDate.getTime() < cutoff) {
88
+ try {
89
+ fs.unlinkSync(path.join(logsDir, file))
90
+ } catch { /* best-effort */ }
91
+ }
92
+ }
93
+ } catch {
94
+ // Directory might not exist yet — that's fine
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Create a dev logger instance.
100
+ *
101
+ * @param {object} opts
102
+ * @param {string} opts.root — project root directory
103
+ * @param {string} [opts.devDomain] — dev domain from config (e.g. "storyboard-core")
104
+ * @param {string} [opts.branch] — current git branch
105
+ * @param {boolean} [opts.verbose] — also log to console (controlled by dev-logs flag)
106
+ * @returns {{ logResponse: Function, logEvent: Function, logsDir: string }}
107
+ */
108
+ export function createDevLogger({ root, devDomain = null, branch = null, verbose = false } = {}) {
109
+ const logsDir = path.join(root, STORYBOARD_DIR, LOGS_DIR_NAME)
110
+ let dirEnsured = false
111
+
112
+ function ensureDir() {
113
+ if (dirEnsured) return true
114
+ try {
115
+ fs.mkdirSync(logsDir, { recursive: true })
116
+ dirEnsured = true
117
+ return true
118
+ } catch {
119
+ return false
120
+ }
121
+ }
122
+
123
+ // Prune old logs on startup (non-blocking, best-effort)
124
+ try { pruneOldLogs(logsDir) } catch { /* ignore */ }
125
+
126
+ function writeEntry(entry) {
127
+ if (!ensureDir()) return
128
+ const filename = `${dateStamp()}.jsonl`
129
+ const filepath = path.join(logsDir, filename)
130
+ const line = JSON.stringify(entry) + '\n'
131
+
132
+ try {
133
+ fs.appendFileSync(filepath, line, 'utf-8')
134
+ } catch {
135
+ // If we can't write logs, fail silently — never crash the server
136
+ }
137
+
138
+ if (verbose) {
139
+ const tag = entry.status ? `[${entry.status}]` : `[${entry.level}]`
140
+ const msg = entry.error || entry.message || ''
141
+ console.warn(`[storyboard-logs] ${tag} ${entry.method || ''} ${entry.url || ''} — ${msg}`)
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Log an HTTP error response (4xx/5xx).
147
+ *
148
+ * @param {object} opts
149
+ * @param {number} opts.status — HTTP status code
150
+ * @param {string} opts.method — HTTP method (GET, POST, etc.)
151
+ * @param {string} opts.url — full request URL
152
+ * @param {string} [opts.route] — top-level route prefix (e.g. "canvas", "workshop")
153
+ * @param {string} [opts.subRoute] — sub-route path after prefix
154
+ * @param {string} [opts.error] — error message from the response body
155
+ * @param {object} [opts.context] — additional context (overrides auto-extracted)
156
+ */
157
+ function logResponse({ status, method, url, route = null, subRoute = null, error = null, context = {} }) {
158
+ if (status < 400) return
159
+
160
+ const autoContext = extractContext(url)
161
+ const mergedContext = { ...autoContext, ...context }
162
+
163
+ // Strip null values from context
164
+ for (const key of Object.keys(mergedContext)) {
165
+ if (mergedContext[key] == null) delete mergedContext[key]
166
+ }
167
+
168
+ const entry = {
169
+ ts: new Date().toISOString(),
170
+ level: status >= 500 ? 'error' : 'warn',
171
+ status,
172
+ method: method || 'UNKNOWN',
173
+ url: url || '',
174
+ route: route || null,
175
+ subRoute: subRoute || null,
176
+ error: error || null,
177
+ devDomain: devDomain || null,
178
+ branch: branch || null,
179
+ }
180
+
181
+ if (Object.keys(mergedContext).length > 0) {
182
+ entry.context = mergedContext
183
+ }
184
+
185
+ writeEntry(entry)
186
+ }
187
+
188
+ /**
189
+ * Log a general event (not tied to an HTTP response).
190
+ *
191
+ * @param {'info'|'warn'|'error'} level
192
+ * @param {string} message
193
+ * @param {object} [context] — arbitrary context data
194
+ */
195
+ function logEvent(level, message, context = {}) {
196
+ const entry = {
197
+ ts: new Date().toISOString(),
198
+ level,
199
+ message,
200
+ devDomain: devDomain || null,
201
+ branch: branch || null,
202
+ }
203
+ if (Object.keys(context).length > 0) {
204
+ entry.context = context
205
+ }
206
+ writeEntry(entry)
207
+ }
208
+
209
+ return { logResponse, logEvent, logsDir, _extractContext: extractContext }
210
+ }
211
+
212
+ // ─── Module-level singleton ───
213
+ // Set once by server-plugin.js, then any server-side module can call devLog().
214
+
215
+ let _instance = null
216
+
217
+ /**
218
+ * Register the global dev logger instance.
219
+ * Called once during server startup (server-plugin.js configureServer).
220
+ */
221
+ export function setDevLogger(logger) {
222
+ _instance = logger
223
+ }
224
+
225
+ /**
226
+ * Get the global dev logger. Returns a no-op logger if not yet initialized.
227
+ * Safe to call from any server-side module without constructor plumbing.
228
+ */
229
+ export function devLog() {
230
+ return _instance || _noop
231
+ }
232
+
233
+ const _noop = {
234
+ logResponse() {},
235
+ logEvent() {},
236
+ }
237
+
238
+ export { extractContext, pruneOldLogs, dateStamp }
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import os from 'node:os'
5
+ import { createDevLogger, extractContext, pruneOldLogs, dateStamp } from './devLogger.js'
6
+
7
+ function makeTmpDir() {
8
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'devlogger-test-'))
9
+ }
10
+
11
+ describe('devLogger', () => {
12
+ let tmpRoot
13
+
14
+ beforeEach(() => {
15
+ tmpRoot = makeTmpDir()
16
+ })
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(tmpRoot, { recursive: true, force: true })
20
+ })
21
+
22
+ describe('createDevLogger', () => {
23
+ it('creates the logs directory on first write', () => {
24
+ const logger = createDevLogger({ root: tmpRoot, devDomain: 'test', branch: 'main' })
25
+ const logsDir = path.join(tmpRoot, '.storyboard', 'logs')
26
+
27
+ // Not created yet (lazy)
28
+ expect(fs.existsSync(logsDir)).toBe(false)
29
+
30
+ logger.logResponse({ status: 404, method: 'GET', url: '/_storyboard/canvas/read?name=missing', error: 'Not found' })
31
+
32
+ expect(fs.existsSync(logsDir)).toBe(true)
33
+ })
34
+
35
+ it('writes JSONL entries with correct format', () => {
36
+ const logger = createDevLogger({ root: tmpRoot, devDomain: 'test-domain', branch: 'feat-x' })
37
+
38
+ logger.logResponse({
39
+ status: 404,
40
+ method: 'GET',
41
+ url: '/_storyboard/canvas/read?name=my-canvas',
42
+ route: 'canvas',
43
+ subRoute: '/read',
44
+ error: 'Canvas "my-canvas" not found',
45
+ })
46
+
47
+ const logsDir = path.join(tmpRoot, '.storyboard', 'logs')
48
+ const files = fs.readdirSync(logsDir)
49
+ expect(files).toHaveLength(1)
50
+ expect(files[0]).toMatch(/^\d{4}-\d{2}-\d{2}\.jsonl$/)
51
+
52
+ const content = fs.readFileSync(path.join(logsDir, files[0]), 'utf-8')
53
+ const lines = content.trim().split('\n')
54
+ expect(lines).toHaveLength(1)
55
+
56
+ const entry = JSON.parse(lines[0])
57
+ expect(entry.ts).toMatch(/^\d{4}-\d{2}-\d{2}T/)
58
+ expect(entry.level).toBe('warn')
59
+ expect(entry.status).toBe(404)
60
+ expect(entry.method).toBe('GET')
61
+ expect(entry.route).toBe('canvas')
62
+ expect(entry.subRoute).toBe('/read')
63
+ expect(entry.error).toBe('Canvas "my-canvas" not found')
64
+ expect(entry.devDomain).toBe('test-domain')
65
+ expect(entry.branch).toBe('feat-x')
66
+ expect(entry.context.canvas).toBe('my-canvas')
67
+ })
68
+
69
+ it('uses level "error" for 5xx status codes', () => {
70
+ const logger = createDevLogger({ root: tmpRoot })
71
+ logger.logResponse({ status: 500, method: 'POST', url: '/test', error: 'Internal error' })
72
+
73
+ const logsDir = path.join(tmpRoot, '.storyboard', 'logs')
74
+ const files = fs.readdirSync(logsDir)
75
+ const entry = JSON.parse(fs.readFileSync(path.join(logsDir, files[0]), 'utf-8').trim())
76
+ expect(entry.level).toBe('error')
77
+ })
78
+
79
+ it('does not log 2xx responses', () => {
80
+ const logger = createDevLogger({ root: tmpRoot })
81
+ logger.logResponse({ status: 200, method: 'GET', url: '/ok' })
82
+ logger.logResponse({ status: 201, method: 'POST', url: '/created' })
83
+
84
+ const logsDir = path.join(tmpRoot, '.storyboard', 'logs')
85
+ expect(fs.existsSync(logsDir)).toBe(false) // never written, dir never created
86
+ })
87
+
88
+ it('appends multiple entries to the same day file', () => {
89
+ const logger = createDevLogger({ root: tmpRoot })
90
+ logger.logResponse({ status: 404, method: 'GET', url: '/a', error: 'A' })
91
+ logger.logResponse({ status: 400, method: 'POST', url: '/b', error: 'B' })
92
+ logger.logResponse({ status: 502, method: 'GET', url: '/c', error: 'C' })
93
+
94
+ const logsDir = path.join(tmpRoot, '.storyboard', 'logs')
95
+ const files = fs.readdirSync(logsDir)
96
+ expect(files).toHaveLength(1)
97
+
98
+ const lines = fs.readFileSync(path.join(logsDir, files[0]), 'utf-8').trim().split('\n')
99
+ expect(lines).toHaveLength(3)
100
+ expect(JSON.parse(lines[0]).error).toBe('A')
101
+ expect(JSON.parse(lines[1]).error).toBe('B')
102
+ expect(JSON.parse(lines[2]).error).toBe('C')
103
+ })
104
+ })
105
+
106
+ describe('logEvent', () => {
107
+ it('logs general events with level and message', () => {
108
+ const logger = createDevLogger({ root: tmpRoot, devDomain: 'test' })
109
+ logger.logEvent('warn', 'Hot pool failed to start', { poolId: 'copilot' })
110
+
111
+ const logsDir = path.join(tmpRoot, '.storyboard', 'logs')
112
+ const files = fs.readdirSync(logsDir)
113
+ const entry = JSON.parse(fs.readFileSync(path.join(logsDir, files[0]), 'utf-8').trim())
114
+
115
+ expect(entry.level).toBe('warn')
116
+ expect(entry.message).toBe('Hot pool failed to start')
117
+ expect(entry.context.poolId).toBe('copilot')
118
+ expect(entry.devDomain).toBe('test')
119
+ })
120
+ })
121
+
122
+ describe('extractContext', () => {
123
+ it('extracts canvas name from ?name= query param', () => {
124
+ const ctx = extractContext('/_storyboard/canvas/read?name=design-system')
125
+ expect(ctx.canvas).toBe('design-system')
126
+ })
127
+
128
+ it('extracts prototype from ?prototype= query param', () => {
129
+ const ctx = extractContext('/_storyboard/workshop/create?prototype=Dashboard')
130
+ expect(ctx.prototype).toBe('Dashboard')
131
+ })
132
+
133
+ it('extracts page from ?page= query param', () => {
134
+ const ctx = extractContext('/_storyboard/something?page=settings')
135
+ expect(ctx.page).toBe('settings')
136
+ })
137
+
138
+ it('returns empty context for non-parseable URLs', () => {
139
+ const ctx = extractContext('')
140
+ expect(ctx.canvas).toBeNull()
141
+ expect(ctx.prototype).toBeNull()
142
+ expect(ctx.page).toBeNull()
143
+ })
144
+
145
+ it('returns empty context for null input', () => {
146
+ const ctx = extractContext(null)
147
+ expect(ctx.canvas).toBeNull()
148
+ })
149
+ })
150
+
151
+ describe('dateStamp', () => {
152
+ it('returns YYYY-MM-DD format', () => {
153
+ const stamp = dateStamp(new Date('2026-04-25T12:00:00Z'))
154
+ expect(stamp).toBe('2026-04-25')
155
+ })
156
+ })
157
+
158
+ describe('pruneOldLogs', () => {
159
+ it('removes log files older than 7 days', () => {
160
+ const logsDir = path.join(tmpRoot, 'logs')
161
+ fs.mkdirSync(logsDir, { recursive: true })
162
+
163
+ // Create old file (10 days ago)
164
+ const old = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000)
165
+ const oldFile = `${dateStamp(old)}.jsonl`
166
+ fs.writeFileSync(path.join(logsDir, oldFile), '{"test":true}\n')
167
+
168
+ // Create recent file (today)
169
+ const todayFile = `${dateStamp()}.jsonl`
170
+ fs.writeFileSync(path.join(logsDir, todayFile), '{"test":true}\n')
171
+
172
+ pruneOldLogs(logsDir)
173
+
174
+ const remaining = fs.readdirSync(logsDir)
175
+ expect(remaining).toContain(todayFile)
176
+ expect(remaining).not.toContain(oldFile)
177
+ })
178
+
179
+ it('ignores non-jsonl files', () => {
180
+ const logsDir = path.join(tmpRoot, 'logs')
181
+ fs.mkdirSync(logsDir, { recursive: true })
182
+ fs.writeFileSync(path.join(logsDir, 'readme.txt'), 'keep me')
183
+
184
+ pruneOldLogs(logsDir)
185
+
186
+ expect(fs.existsSync(path.join(logsDir, 'readme.txt'))).toBe(true)
187
+ })
188
+
189
+ it('handles non-existent directory gracefully', () => {
190
+ expect(() => pruneOldLogs(path.join(tmpRoot, 'nonexistent'))).not.toThrow()
191
+ })
192
+ })
193
+ })