@dfosco/storyboard 0.7.1 → 0.9.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 (323) hide show
  1. package/dist/storyboard-ui.css +1 -1
  2. package/dist/storyboard-ui.js +14128 -10953
  3. package/dist/storyboard-ui.js.map +1 -1
  4. package/dist/tailwind.css +1 -1
  5. package/dist/tiny-canvas.css +1 -1
  6. package/package.json +138 -128
  7. package/scaffold/AGENTS.md +9 -7
  8. package/scaffold/README.md +18 -0
  9. package/scaffold/deploy.yml +21 -0
  10. package/scaffold/gitignore +8 -14
  11. package/scaffold/preview.yml +40 -0
  12. package/scaffold/{manifest.json → scaffold.config.json} +73 -3
  13. package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
  14. package/scaffold/skills/canvas/SKILL.md +1 -1
  15. package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
  16. package/scaffold/skills/ship/SKILL.md +2 -2
  17. package/scaffold/skills/worktree/SKILL.md +1 -1
  18. package/scaffold/src/_prototype.jsx +17 -0
  19. package/scaffold/src/_story.jsx +13 -0
  20. package/scaffold/src/library/README.md +34 -0
  21. package/scaffold/src/library/_app.jsx +41 -0
  22. package/scaffold/src/library/_app.module.css +2 -0
  23. package/scaffold/src/library/create.jsx +10 -0
  24. package/scaffold/src/library/index.jsx +14 -0
  25. package/scaffold/src/library/mount.jsx +51 -0
  26. package/scaffold/src/library/prototypes-entry.jsx +57 -0
  27. package/scaffold/src/library/routes.jsx +205 -0
  28. package/scaffold/src/library/viewfinder.jsx +13 -0
  29. package/scaffold/src/library/workspace.jsx +17 -0
  30. package/scripts/aggregate-artifacts.mjs +257 -0
  31. package/src/canvas/style.css +16 -16
  32. package/src/core/artifact/operations.js +54 -15
  33. package/src/core/artifact/schemas/canvas.schema.json +7 -1
  34. package/src/core/autosync/server.test.js +1 -1
  35. package/src/core/canvas/hot-pool.js +21 -33
  36. package/src/core/canvas/materializer.js +39 -13
  37. package/src/core/canvas/selectedWidgets.js +1 -1
  38. package/src/core/canvas/server.js +376 -100
  39. package/src/core/canvas/server.test.js +472 -2
  40. package/src/core/canvas/terminal-server.js +144 -57
  41. package/src/core/canvas/undoRedo.js +174 -0
  42. package/src/core/canvas/undoRedo.test.js +250 -0
  43. package/src/core/cli/artifact.js +2 -0
  44. package/src/core/cli/buildManifest.js +34 -0
  45. package/src/core/cli/canvasRedo.js +44 -0
  46. package/src/core/cli/canvasUndo.js +40 -0
  47. package/src/core/cli/create.js +151 -23
  48. package/src/core/cli/dev.js +23 -35
  49. package/src/core/cli/index.js +21 -0
  50. package/src/core/cli/schemas.js +8 -4
  51. package/src/core/cli/serverUrl.js +24 -0
  52. package/src/core/cli/setup.js +14 -25
  53. package/src/core/cli/terminal-welcome.js +12 -6
  54. package/src/core/cli/updateVersion.js +42 -2
  55. package/src/core/cli/workleaf.js +140 -0
  56. package/src/core/comments/api.js +1 -1
  57. package/src/core/comments/queries.js +3 -0
  58. package/src/core/comments/ui/CommentWindow.jsx +3 -2
  59. package/src/core/comments/ui/comment-layout.css +14 -14
  60. package/src/core/data/artifactIndex.js +235 -0
  61. package/src/core/data/artifactIndex.test.js +203 -0
  62. package/src/core/data/artifactManifest.js +290 -0
  63. package/src/core/data/artifactManifest.test.js +207 -0
  64. package/src/core/data/viewfinder.js +47 -7
  65. package/src/core/index.js +24 -1
  66. package/src/core/inspector/highlighter.js +1 -1
  67. package/src/core/inspector/mouseMode.js +2 -2
  68. package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
  69. package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
  70. package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
  71. package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
  72. package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
  73. package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
  74. package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
  75. package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
  76. package/src/core/lib/components/ui/dialog/index.js +4 -34
  77. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
  78. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
  79. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
  80. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
  81. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
  82. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
  83. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
  84. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
  85. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
  86. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
  87. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
  88. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
  89. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
  90. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
  91. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
  92. package/src/core/lib/components/ui/label/label.jsx +1 -2
  93. package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
  94. package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
  95. package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
  96. package/src/core/lib/components/ui/panel/panel.jsx +11 -2
  97. package/src/core/lib/components/ui/separator/separator.jsx +6 -4
  98. package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
  99. package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
  100. package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
  101. package/src/core/modes/modes.css +4 -4
  102. package/src/core/mountStoryboardCore.js +287 -83
  103. package/src/core/mountStoryboardCore.test.js +198 -0
  104. package/src/core/rename-watcher/config.json +1 -1
  105. package/src/core/rename-watcher/watcher.js +330 -37
  106. package/src/core/rename-watcher/watcher.test.js +253 -0
  107. package/src/core/scaffold/fragments.js +287 -0
  108. package/src/core/scaffold/fragments.test.js +401 -0
  109. package/src/core/scaffold/index.js +291 -0
  110. package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
  111. package/src/core/scaffold/scaffold-integration.test.js +247 -0
  112. package/src/core/scaffold.js +4 -97
  113. package/src/core/stores/canvasInteractionStore.js +120 -0
  114. package/src/core/stores/canvasInteractionStore.test.js +126 -0
  115. package/src/core/stores/configSchema.js +64 -8
  116. package/src/core/stores/configSchema.test.js +3 -0
  117. package/src/core/stores/customerModeConfig.js +24 -3
  118. package/src/core/stores/customerModeConfig.test.js +46 -0
  119. package/src/core/stores/presentationStore.js +118 -0
  120. package/src/core/stores/presentationStore.test.js +114 -0
  121. package/src/core/stores/recentArtifacts.js +49 -2
  122. package/src/core/stores/recentArtifacts.test.js +59 -6
  123. package/src/core/stores/themeBootstrap.js +264 -0
  124. package/src/core/stores/themeBootstrap.test.js +211 -0
  125. package/src/core/stores/themeStore.test.js +154 -0
  126. package/src/core/stores/themeStore.ts +235 -220
  127. package/src/core/stores/uiConfig.js +10 -2
  128. package/src/core/styles/generateThemesCss.js +63 -0
  129. package/src/core/styles/tailwind.css +150 -94
  130. package/src/core/styles/workspace-chrome.css +13 -0
  131. package/src/core/tools/handlers/paletteTheme.js +11 -8
  132. package/src/core/ui/ActionMenuButton.jsx +2 -1
  133. package/src/core/ui/AutosyncMenuButton.css +7 -7
  134. package/src/core/ui/BranchSelect.module.css +5 -5
  135. package/src/core/ui/CanvasCreateMenu.jsx +1 -19
  136. package/src/core/ui/ChromeSlot.jsx +92 -0
  137. package/src/core/ui/ChromeSlot.test.jsx +140 -0
  138. package/src/core/ui/CoreUIBar.css +5 -5
  139. package/src/core/ui/CoreUIBar.jsx +170 -142
  140. package/src/core/ui/InspectorPanel.css +5 -5
  141. package/src/core/ui/InspectorPanel.jsx +9 -9
  142. package/src/core/ui/PwaInstallBanner.css +5 -5
  143. package/src/core/ui/PwaInstallBanner.jsx +5 -5
  144. package/src/core/ui/ThemeMenuButton.jsx +45 -30
  145. package/src/core/ui/core-ui-colors.css +28 -66
  146. package/src/core/ui/sidepanel.css +12 -12
  147. package/src/core/vite/server-plugin.js +13 -0
  148. package/src/core/workleaf/cleanup.js +102 -0
  149. package/src/core/workleaf/create.js +199 -0
  150. package/src/core/workleaf/delete.js +101 -0
  151. package/src/core/workleaf/list.js +57 -0
  152. package/src/core/workleaf/merge.js +238 -0
  153. package/src/core/workleaf/paths.js +146 -0
  154. package/src/core/workleaf/paths.test.js +230 -0
  155. package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +130 -34
  156. package/src/core/workshop/features/createFlow/server.js +5 -1
  157. package/src/core/workshop/features/createPrototype/server.js +5 -1
  158. package/src/core/workshop/features/templateIndex.js +28 -1
  159. package/src/core/worktree/port.js +48 -5
  160. package/src/core/worktree/port.test.js +42 -0
  161. package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
  162. package/src/internals/ArtifactForm/ArtifactForm.module.css +51 -69
  163. package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
  164. package/src/internals/AuthModal/AuthModal.module.css +34 -34
  165. package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
  166. package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
  167. package/src/internals/BranchBar/BranchBar.module.css +4 -4
  168. package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
  169. package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
  170. package/src/internals/CommandPalette/CommandPalette.jsx +41 -29
  171. package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
  172. package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
  173. package/src/internals/CommandPalette/WidgetArtifactDialog.css +16 -16
  174. package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
  175. package/src/internals/CommandPalette/command-palette.css +14 -14
  176. package/src/internals/CommandPalette/createHelpers.js +30 -0
  177. package/src/internals/CreatePage/CreatePage.jsx +57 -0
  178. package/src/internals/CreatePage/CreatePage.module.css +34 -0
  179. package/src/internals/Dialog.jsx +51 -0
  180. package/src/internals/FlowError.module.css +5 -5
  181. package/src/internals/PrototypeErrorBoundary.module.css +15 -15
  182. package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +534 -0
  183. package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
  184. package/src/internals/Tooltip.jsx +39 -0
  185. package/src/internals/Viewfinder.jsx +229 -64
  186. package/src/internals/Viewfinder.module.css +253 -182
  187. package/src/internals/canvas/CanvasControls.module.css +11 -11
  188. package/src/internals/canvas/CanvasPage.bridge.test.jsx +11 -37
  189. package/src/internals/canvas/CanvasPage.jsx +358 -217
  190. package/src/internals/canvas/CanvasPage.module.css +20 -19
  191. package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
  192. package/src/internals/canvas/CanvasToolbar.module.css +11 -11
  193. package/src/internals/canvas/ConnectorLayer.jsx +39 -2
  194. package/src/internals/canvas/ConnectorLayer.module.css +10 -10
  195. package/src/internals/canvas/PageSelector.jsx +15 -1
  196. package/src/internals/canvas/PageSelector.module.css +41 -27
  197. package/src/internals/canvas/canvasApi.js +40 -5
  198. package/src/internals/canvas/canvasTheme.js +22 -111
  199. package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
  200. package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
  201. package/src/internals/canvas/isolation/prototypeRoutes.jsx +140 -0
  202. package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
  203. package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
  204. package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
  205. package/src/internals/canvas/useUndoRedo.js +75 -60
  206. package/src/internals/canvas/useUndoRedo.test.js +46 -178
  207. package/src/internals/canvas/widgets/CodePenEmbed.jsx +16 -36
  208. package/src/internals/canvas/widgets/CodePenEmbed.module.css +12 -12
  209. package/src/internals/canvas/widgets/CropOverlay.module.css +23 -23
  210. package/src/internals/canvas/widgets/ExpandedPane.module.css +8 -8
  211. package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
  212. package/src/internals/canvas/widgets/FigmaEmbed.jsx +15 -36
  213. package/src/internals/canvas/widgets/FigmaEmbed.module.css +15 -15
  214. package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
  215. package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +5 -5
  216. package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
  217. package/src/internals/canvas/widgets/ImageWidget.module.css +3 -3
  218. package/src/internals/canvas/widgets/LinkPreview.module.css +79 -80
  219. package/src/internals/canvas/widgets/MarkdownBlock.module.css +31 -31
  220. package/src/internals/canvas/widgets/PromptWidget.jsx +2 -16
  221. package/src/internals/canvas/widgets/PromptWidget.module.css +33 -33
  222. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +92 -17
  223. package/src/internals/canvas/widgets/PrototypeEmbed.module.css +58 -56
  224. package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +118 -4
  225. package/src/internals/canvas/widgets/ResizeHandle.module.css +4 -4
  226. package/src/internals/canvas/widgets/StorySetWidget.jsx +53 -25
  227. package/src/internals/canvas/widgets/StoryWidget.jsx +71 -56
  228. package/src/internals/canvas/widgets/StoryWidget.module.css +22 -20
  229. package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
  230. package/src/internals/canvas/widgets/TerminalReadWidget.module.css +4 -4
  231. package/src/internals/canvas/widgets/TerminalWidget.jsx +56 -45
  232. package/src/internals/canvas/widgets/TerminalWidget.module.css +10 -10
  233. package/src/internals/canvas/widgets/TilesWidget.module.css +17 -17
  234. package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
  235. package/src/internals/canvas/widgets/WidgetChrome.module.css +76 -61
  236. package/src/internals/canvas/widgets/WidgetWrapper.module.css +2 -2
  237. package/src/internals/canvas/widgets/embedOverlay.module.css +14 -4
  238. package/src/internals/canvas/widgets/embedTheme.js +12 -104
  239. package/src/internals/canvas/widgets/expandUtils.js +19 -19
  240. package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
  241. package/src/internals/canvas/widgets/widgetConfig.js +5 -5
  242. package/src/internals/context.jsx +22 -1
  243. package/src/internals/context.test.jsx +47 -0
  244. package/src/internals/hooks/useArtifactIndex.js +31 -0
  245. package/src/internals/hooks/useArtifactIndex.test.js +45 -0
  246. package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
  247. package/src/internals/index.js +6 -0
  248. package/src/internals/loadConsumerWrapper.js +60 -0
  249. package/src/internals/story/ComponentSetPage.jsx +75 -34
  250. package/src/internals/story/ComponentSetPage.module.css +14 -14
  251. package/src/internals/story/StoryPage.jsx +16 -25
  252. package/src/internals/story/StoryPage.module.css +2 -2
  253. package/src/internals/vite/data-plugin.js +317 -149
  254. package/src/internals/vite/data-plugin.test.js +11 -11
  255. package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
  256. package/terminal.config.json +1 -1
  257. package/src/core/lib/components/ui/card/card-action.jsx +0 -11
  258. package/src/core/lib/components/ui/card/card-content.jsx +0 -11
  259. package/src/core/lib/components/ui/card/card-description.jsx +0 -11
  260. package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
  261. package/src/core/lib/components/ui/card/card-header.jsx +0 -19
  262. package/src/core/lib/components/ui/card/card-title.jsx +0 -11
  263. package/src/core/lib/components/ui/card/card.jsx +0 -17
  264. package/src/core/lib/components/ui/card/index.js +0 -25
  265. package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
  266. package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
  267. package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
  268. package/src/core/lib/components/ui/collapsible/index.js +0 -13
  269. package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
  270. package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
  271. package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
  272. package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
  273. package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
  274. package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
  275. package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
  276. package/src/core/lib/components/ui/popover/index.js +0 -28
  277. package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
  278. package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
  279. package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
  280. package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
  281. package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
  282. package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
  283. package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
  284. package/src/core/lib/components/ui/popover/popover.jsx +0 -4
  285. package/src/core/lib/components/ui/select/index.js +0 -37
  286. package/src/core/lib/components/ui/select/select-content.jsx +0 -30
  287. package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
  288. package/src/core/lib/components/ui/select/select-group.jsx +0 -15
  289. package/src/core/lib/components/ui/select/select-item.jsx +0 -26
  290. package/src/core/lib/components/ui/select/select-label.jsx +0 -11
  291. package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
  292. package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
  293. package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
  294. package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
  295. package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
  296. package/src/core/lib/components/ui/select/select.jsx +0 -4
  297. package/src/core/lib/components/ui/sheet/index.js +0 -34
  298. package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
  299. package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
  300. package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
  301. package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
  302. package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
  303. package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
  304. package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
  305. package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
  306. package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
  307. package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
  308. package/src/core/lib/components/ui/toggle/index.js +0 -8
  309. package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
  310. package/src/core/lib/components/ui/toggle-group/index.js +0 -10
  311. package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
  312. package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
  313. package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
  314. package/src/internals/canvas/WebGLContextPool.jsx +0 -291
  315. package/src/internals/canvas/WebGLContextPool.test.jsx +0 -165
  316. package/src/internals/canvas/componentIsolate.jsx +0 -144
  317. package/src/internals/canvas/componentSetIsolate.jsx +0 -300
  318. package/src/internals/canvas/widgets/ComponentSetWidget.module.css +0 -89
  319. package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
  320. package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
  321. package/src/internals/canvas/widgets/StorySetWidget.module.css +0 -89
  322. package/src/primer/ThemeSync.jsx +0 -73
  323. package/src/primer/index.js +0 -1
@@ -5,12 +5,27 @@
5
5
  * Formerly known as Viewfinder — renamed to match the /workspace route.
6
6
  */
7
7
  import { useState, useEffect, useRef, useMemo, useCallback, useSyncExternalStore } from 'react'
8
- import { buildPrototypeIndex, listStories, getStoryData, BranchSelect, getCustomerModeConfig } from '../core/index.js'
9
- import { MarkGithubIcon, GitBranchIcon, ChevronDownIcon, ChevronRightIcon, PlusIcon, StarIcon, StarFillIcon, ThreeBarsIcon, XIcon, StackIcon, TrashIcon, ShieldLockIcon, KebabHorizontalIcon, PencilIcon } from '@primer/octicons-react'
8
+ import {
9
+ buildPrototypeIndex,
10
+ listStories,
11
+ getStoryData,
12
+ BranchSelect,
13
+ getCustomerModeConfig,
14
+ getConfig,
15
+ isMenuHidden,
16
+ trackRecent,
17
+ subscribeToRecent,
18
+ getRecentSnapshot,
19
+ } from '../core/index.js'
20
+ import { buildBranchArtifactHref, mergeArtifactIndexIntoPrototypeIndex } from '../core/data/artifactIndex.js'
21
+ import { MarkGithubIcon, GitBranchIcon, ChevronDownIcon, ChevronRightIcon, PlusIcon, StarIcon, StarFillIcon, ThreeBarsIcon, XIcon, StackIcon, TrashIcon, ShieldLockIcon, KebabHorizontalIcon, PencilIcon, EyeClosedIcon } from '@primer/octicons-react'
10
22
  import { Menu } from '@base-ui/react/menu'
11
23
  import { Dialog } from '@base-ui/react/dialog'
24
+ import Tooltip from './Tooltip.jsx'
12
25
  import Icon from './Icon.jsx'
13
26
  import { useBranches } from './BranchBar/useBranches.js'
27
+ import { useArtifactIndex } from './hooks/useArtifactIndex.js'
28
+ import BranchesDropdown from './BranchesDropdown/BranchesDropdown.jsx'
14
29
  import css from './Viewfinder.module.css'
15
30
 
16
31
  /* ─── Theme sync: read toolbar theme from DOM and apply to Primer/BaseUI ─── */
@@ -96,8 +111,6 @@ function useGitHubUser() {
96
111
  /* ─── localStorage helpers ─── */
97
112
 
98
113
  const STARRED_KEY = 'sb-workspace-starred'
99
- const RECENT_KEY = 'sb-workspace-recent'
100
- const MAX_RECENT = 30
101
114
  const GROUP_BY_FOLDERS_KEY = 'sb-workspace-group-folders'
102
115
  const COLLAPSED_FOLDERS_KEY = 'sb-workspace-collapsed-folders'
103
116
 
@@ -122,7 +135,6 @@ function createLocalStorageStore(key, fallback) {
122
135
  }
123
136
 
124
137
  const starredStore = createLocalStorageStore(STARRED_KEY, [])
125
- const recentStore = createLocalStorageStore(RECENT_KEY, [])
126
138
 
127
139
  function useStarred() {
128
140
  const raw = useSyncExternalStore(starredStore.subscribe, starredStore.getSnapshot)
@@ -135,15 +147,61 @@ function useStarred() {
135
147
  return { starred: new Set(ids), toggle }
136
148
  }
137
149
 
150
+ /**
151
+ * Map a recent store entry `{type, key}` to the composite id used by the
152
+ * workspace `itemMap` (`proto:`, `canvas:`, `component:`).
153
+ *
154
+ * The unified recent store uses `'story'` as the canonical type for
155
+ * component/story artifacts (matches `core/stores/paletteProviders.js`),
156
+ * but the workspace's items use `'component'` for the same things.
157
+ */
158
+ function recentEntryToItemId(entry) {
159
+ if (!entry || typeof entry.type !== 'string' || typeof entry.key !== 'string') return null
160
+ switch (entry.type) {
161
+ case 'prototype': return `proto:${entry.key}`
162
+ case 'canvas': return `canvas:${entry.key}`
163
+ case 'story':
164
+ case 'component': return `component:${entry.key}`
165
+ default: return `${entry.type}:${entry.key}`
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Map a workspace `item.type` value to the canonical type stored in the
171
+ * recent artifacts store. The workspace uses `'component'` for stories
172
+ * but the recent store (shared with the command palette) uses `'story'`.
173
+ */
174
+ function itemTypeToRecentType(itemType) {
175
+ if (itemType === 'component') return 'story'
176
+ return itemType
177
+ }
178
+
138
179
  function useRecent() {
139
- const raw = useSyncExternalStore(recentStore.subscribe, recentStore.getSnapshot)
140
- return JSON.parse(raw)
180
+ const raw = useSyncExternalStore(subscribeToRecent, getRecentSnapshot)
181
+ return useMemo(() => {
182
+ try {
183
+ const entries = JSON.parse(raw)
184
+ if (!Array.isArray(entries)) return []
185
+ const ids = []
186
+ for (const entry of entries) {
187
+ const id = recentEntryToItemId(entry)
188
+ if (id) ids.push(id)
189
+ }
190
+ return ids
191
+ } catch {
192
+ return []
193
+ }
194
+ }, [raw])
141
195
  }
142
196
 
143
- function trackRecent(id) {
144
- const current = readJSON(RECENT_KEY, [])
145
- const next = [id, ...current.filter(x => x !== id)].slice(0, MAX_RECENT)
146
- writeJSON(RECENT_KEY, next)
197
+ function trackRecentItem(item) {
198
+ if (!item || !item.id) return
199
+ const colonIdx = item.id.indexOf(':')
200
+ if (colonIdx <= 0) return
201
+ const key = item.id.slice(colonIdx + 1)
202
+ const type = itemTypeToRecentType(item.type)
203
+ if (!type) return
204
+ trackRecent(type, key, item.name || key)
147
205
  }
148
206
 
149
207
  /* ─── URL helpers ─── */
@@ -440,18 +498,26 @@ function DeleteArtifactModal({ item, dirName, basePath, typeLabel, onClose, onDe
440
498
 
441
499
  /* ─── Artifact Card ─── */
442
500
 
443
- function ArtifactCard({ item, basePath, starred, onToggleStar, onItemDeleted }) {
444
- const href = item.route ? withBase(basePath, item.route) : '#'
501
+ function ArtifactCard({ item, basePath, branchBasePath, currentBranch, starred, onToggleStar, onItemDeleted }) {
502
+ const isBranchOnly = !!item.isBranchOnly
445
503
  const isExternal = item.isExternal
504
+ const branchOnlyHref = isBranchOnly
505
+ ? isExternal
506
+ ? item.externalUrl
507
+ : buildBranchArtifactHref(branchBasePath || basePath, item.homeBranch?.folder, item.homeBranch?.route || item.route)
508
+ : null
509
+ const href = item.route ? withBase(basePath, item.route) : '#'
446
510
 
447
511
  const handleClick = () => {
448
- trackRecent(item.id)
512
+ if (!isBranchOnly) trackRecentItem(item)
449
513
  }
450
514
 
451
- const Tag = isExternal ? 'a' : 'a'
452
- const linkProps = isExternal
453
- ? { href: item.externalUrl, target: '_blank', rel: 'noopener noreferrer' }
454
- : { href }
515
+ const Tag = 'a'
516
+ const linkProps = isBranchOnly
517
+ ? { href: branchOnlyHref || '#', target: '_blank', rel: 'noopener noreferrer' }
518
+ : isExternal
519
+ ? { href: item.externalUrl, target: '_blank', rel: 'noopener noreferrer' }
520
+ : { href }
455
521
 
456
522
  const authorList = item.author
457
523
  ? (Array.isArray(item.author) ? item.author : [item.author])
@@ -463,17 +529,40 @@ function ArtifactCard({ item, basePath, starred, onToggleStar, onItemDeleted })
463
529
  // Extract dirName from item.id (format: "type:dirName")
464
530
  const dirName = item.id.split(':').slice(1).join(':')
465
531
  const typeLabel = item.type === 'canvas' ? 'Canvas' : item.type === 'prototype' ? 'Prototype' : 'Component'
466
- const canEditDelete = item.type === 'canvas' || item.type === 'prototype'
532
+ const canEditDelete = !isBranchOnly && (item.type === 'canvas' || item.type === 'prototype')
533
+ const cardClass = `${css.card}${item.isPrivate ? ' ' + css.cardPrivate : ''}${isBranchOnly ? ' ' + css.branchOnlyCard : ''}`
467
534
 
468
535
  return (
469
536
  <>
470
- <Tag className={css.card} {...linkProps} onClick={handleClick}>
537
+ <Tag className={cardClass} {...linkProps} onClick={handleClick}>
471
538
  <div className={css.cardHeader}>
472
- <span className={css.cardBadge}>{getTypeLabel(item.type)}</span>
539
+ <div className={css.cardBadgeGroup}>
540
+ <span className={css.cardBadge}>{getTypeLabel(item.type)}</span>
541
+ {isBranchOnly && <span className={css.branchOnlyBadge}>Branch-only</span>}
542
+ {item.isPrivate && !isBranchOnly && (
543
+ <Tooltip text={`${typeLabel} not published, only visible to you`} direction="n">
544
+ <button
545
+ type="button"
546
+ className={css.cardPrivateBadge}
547
+ aria-label={`Private ${typeLabel.toLowerCase()}`}
548
+ onClick={(e) => { e.preventDefault(); e.stopPropagation() }}
549
+ >
550
+ <EyeClosedIcon size={12} />
551
+ </button>
552
+ </Tooltip>
553
+ )}
554
+ </div>
473
555
  <div className={css.cardActions}>
474
- <StarBtn active={starred} onClick={() => onToggleStar(item.id)} inline />
475
- {item.flows?.length > 1 && <FlowsDropdown flows={item.flows} basePath={basePath} />}
476
- {item.pages?.length > 1 && <PagesDropdown pages={item.pages} basePath={basePath} />}
556
+ {!isBranchOnly && <StarBtn active={starred} onClick={() => onToggleStar(item.id)} inline />}
557
+ {!isBranchOnly && item.flows?.length > 1 && <FlowsDropdown flows={item.flows} basePath={basePath} />}
558
+ {!isBranchOnly && item.pages?.length > 1 && <PagesDropdown pages={item.pages} basePath={basePath} />}
559
+ <BranchesDropdown
560
+ branches={item.branches || []}
561
+ branchBasePath={branchBasePath}
562
+ currentBranch={currentBranch}
563
+ homeTitle={item.name}
564
+ homeFolder={item.folder}
565
+ />
477
566
  {canEditDelete && (
478
567
  <CardActionsMenu
479
568
  typeLabel={typeLabel}
@@ -505,7 +594,7 @@ function ArtifactCard({ item, basePath, starred, onToggleStar, onItemDeleted })
505
594
  </div>
506
595
  </div>
507
596
  </Tag>
508
- {showEdit && (
597
+ {showEdit && !isBranchOnly && (
509
598
  <EditArtifactModal
510
599
  item={item}
511
600
  dirName={dirName}
@@ -513,7 +602,7 @@ function ArtifactCard({ item, basePath, starred, onToggleStar, onItemDeleted })
513
602
  onClose={() => setShowEdit(false)}
514
603
  />
515
604
  )}
516
- {showDelete && (
605
+ {showDelete && !isBranchOnly && (
517
606
  <DeleteArtifactModal
518
607
  item={item}
519
608
  dirName={dirName}
@@ -609,8 +698,12 @@ function PagesDropdown({ pages, basePath }) {
609
698
  <a
610
699
  href={withBase(basePath, page.route)}
611
700
  className={css.flowsItemLink}
701
+ title={page.isPrivate ? 'Local-only page — gitignored, dev-only' : undefined}
612
702
  >
613
703
  {page.name}
704
+ {page.isPrivate && (
705
+ <EyeClosedIcon size={12} className={css.flowsItemPrivateIcon} />
706
+ )}
614
707
  </a>
615
708
  </Menu.Item>
616
709
  ))}
@@ -623,18 +716,37 @@ function PagesDropdown({ pages, basePath }) {
623
716
 
624
717
  /* ─── Folder Section ─── */
625
718
 
626
- function FolderSection({ folder, collapsed, onToggle, basePath, starred, onToggleStar, onItemDeleted }) {
719
+ function FolderSection({ folder, collapsed, onToggle, basePath, branchBasePath, currentBranch, starred, onToggleStar, onItemDeleted }) {
720
+ const sectionClass = collapsed ? css.folderSectionCollapsed : css.folderSection
627
721
  return (
628
- <section className={collapsed ? css.folderSectionCollapsed : css.folderSection}>
629
- <button className={css.folderHeader} onClick={onToggle}>
722
+ <section className={`${sectionClass}${folder.isPrivate ? ' ' + css.folderSectionPrivate : ''}`}>
723
+ <div
724
+ className={css.folderHeader}
725
+ role="button"
726
+ tabIndex={0}
727
+ onClick={onToggle}
728
+ onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggle() } }}
729
+ >
630
730
  <Icon name={collapsed ? 'folder' : 'folder-open'} size={16} className={css.folderIcon} />
631
731
  <span className={css.folderName}>{folder.name}</span>
732
+ {folder.isPrivate && (
733
+ <Tooltip text="Folder not published, only visible to you" direction="n">
734
+ <button
735
+ type="button"
736
+ className={css.folderPrivateBadge}
737
+ aria-label="Private folder"
738
+ onClick={(e) => { e.preventDefault(); e.stopPropagation() }}
739
+ >
740
+ <EyeClosedIcon size={12} />
741
+ </button>
742
+ </Tooltip>
743
+ )}
632
744
  <span className={css.folderCount}>{folder.items.length}</span>
633
745
  <ChevronRightIcon
634
746
  size={14}
635
747
  className={collapsed ? css.folderChevron : css.folderChevronExpanded}
636
748
  />
637
- </button>
749
+ </div>
638
750
  {!collapsed && (
639
751
  <div className={css.grid}>
640
752
  {folder.items.map(item => (
@@ -642,6 +754,8 @@ function FolderSection({ folder, collapsed, onToggle, basePath, starred, onToggl
642
754
  key={item.id}
643
755
  item={item}
644
756
  basePath={basePath}
757
+ branchBasePath={branchBasePath}
758
+ currentBranch={currentBranch}
645
759
  starred={starred.has(item.id)}
646
760
  onToggleStar={onToggleStar}
647
761
  onItemDeleted={onItemDeleted}
@@ -878,22 +992,30 @@ function CreateForm({ type, onClose, basePath }) {
878
992
 
879
993
  /* ─── Create Menu (Dropdown) ─── */
880
994
 
881
- function CreateMenu({ onClose, basePath }) {
995
+ export function CreateMenu({ onClose, basePath }) {
882
996
  const [activeForm, setActiveForm] = useState(null)
883
997
  const [showMore, setShowMore] = useState(false)
884
998
 
999
+ // Each menu item is enabled by default; a workshop.features.create<X> flag
1000
+ // can opt it out per consumer. Preserves current behavior when no flags are
1001
+ // set, and lets a consumer hide individual artifact types by adding e.g.
1002
+ // { "workshop": { "features": { "createComponent": false } } }
1003
+ // to their storyboard.config.json.
1004
+ const features = getConfig('workshop')?.features || {}
1005
+ const isEnabled = (flag) => features[flag] !== false
1006
+
885
1007
  const items = [
886
- { icon: <Icon name="canvas" size={18} />, title: 'Canvas', desc: 'Interactive board for prototypes, components, and documents' },
887
- { icon: <Icon name="prototype" size={18} />, title: 'Prototype', desc: 'Interactive page flow' },
888
- { icon: <Icon name="iconoir/-couple-solid" size={18} />, title: 'Component', desc: 'Reusable component' },
889
- ]
1008
+ { icon: <Icon name="canvas" size={18} />, title: 'Canvas', desc: 'Interactive board for prototypes, components, and documents', flag: 'createCanvas' },
1009
+ { icon: <Icon name="prototype" size={18} />, title: 'Prototype', desc: 'Interactive page flow', flag: 'createPrototype' },
1010
+ { icon: <Icon name="iconoir/-couple-solid" size={18} />, title: 'Component', desc: 'Reusable component', flag: 'createComponent' },
1011
+ ].filter(it => isEnabled(it.flag))
890
1012
 
891
1013
  const moreItems = [
892
- { title: 'Prototype Flow', desc: 'A flow data file for a prototype', type: 'Flow' },
893
- { title: 'Prototype Page', desc: 'A new page inside a prototype', type: 'Page' },
894
- { title: 'Object', desc: 'Reusable JSON data fragment', type: 'Object' },
895
- { title: 'Record', desc: 'Collection of entries with IDs', type: 'Record' },
896
- ]
1014
+ { title: 'Prototype Flow', desc: 'A flow data file for a prototype', type: 'Flow', flag: 'createFlow' },
1015
+ { title: 'Prototype Page', desc: 'A new page inside a prototype', type: 'Page', flag: 'createPage' },
1016
+ { title: 'Object', desc: 'Reusable JSON data fragment', type: 'Object', flag: 'createObject' },
1017
+ { title: 'Record', desc: 'Collection of entries with IDs', type: 'Record', flag: 'createRecord' },
1018
+ ].filter(it => isEnabled(it.flag))
897
1019
 
898
1020
  if (activeForm) {
899
1021
  return (
@@ -923,7 +1045,7 @@ function CreateMenu({ onClose, basePath }) {
923
1045
  ))}
924
1046
  </div>
925
1047
 
926
- {!showMore ? (
1048
+ {moreItems.length > 0 && (!showMore ? (
927
1049
  <button className={css.moreOptionsBtn} onClick={() => setShowMore(true)}>
928
1050
  More options <ChevronDownIcon size={12} />
929
1051
  </button>
@@ -936,7 +1058,7 @@ function CreateMenu({ onClose, basePath }) {
936
1058
  </button>
937
1059
  ))}
938
1060
  </div>
939
- )}
1061
+ ))}
940
1062
 
941
1063
  <CreateTip />
942
1064
  <CreateFooter />
@@ -956,6 +1078,7 @@ const NAV_ITEMS = [
956
1078
  ]
957
1079
 
958
1080
  const TAB_FILTERS = ['All', 'Recent', 'Starred']
1081
+ const TYPE_MAP = { prototypes: 'prototype', canvases: 'canvas', components: 'component' }
959
1082
 
960
1083
  /* ─── Branch Navigation ─── */
961
1084
 
@@ -970,7 +1093,17 @@ function BranchNav({ basePath }) {
970
1093
  if (isLocalDev) return null
971
1094
  if (!branches || branches.length === 0) return null
972
1095
 
973
- const branchNames = branches.map(b => b.branch)
1096
+ // Ensure `main` is always present and sorted first. CI's `branches.json`
1097
+ // historically lists only `branch--*/` folders, so `main` is missing on
1098
+ // prod deploys — without this the <select> falls back to its first option
1099
+ // (often something like `0.5.0-fix-…`) when the user is actually on main.
1100
+ const branchNames = [
1101
+ 'main',
1102
+ ...branches
1103
+ .map(b => b.branch)
1104
+ .filter(b => b && b !== 'main')
1105
+ .sort((a, b) => a.localeCompare(b)),
1106
+ ]
974
1107
 
975
1108
  const navigate = async (branch) => {
976
1109
  if (switching) return
@@ -1093,6 +1226,8 @@ function WorkspaceImpl({
1093
1226
  basePath,
1094
1227
  title = 'Storyboard',
1095
1228
  subtitle,
1229
+ logo,
1230
+ logoIcon = 'iconoir/key-command',
1096
1231
  showAllArtifacts = false,
1097
1232
  showPrototypes = true,
1098
1233
  showCanvases = true,
@@ -1100,6 +1235,8 @@ function WorkspaceImpl({
1100
1235
  }) {
1101
1236
  const themeAttrs = useToolbarTheme()
1102
1237
  const ghUser = useGitHubUser(basePath)
1238
+ const { currentBranch, branchBasePath } = useBranches(basePath)
1239
+ const { artifactIndex } = useArtifactIndex(basePath)
1103
1240
  const [settingsOpen, setSettingsOpen] = useState(false)
1104
1241
 
1105
1242
  const handleRemoveToken = useCallback(() => {
@@ -1129,13 +1266,17 @@ function WorkspaceImpl({
1129
1266
  const knownRoutes = useMemo(() =>
1130
1267
  Object.keys(pageModules)
1131
1268
  .map(p => p.replace('/src/prototypes/', '').replace('.jsx', ''))
1132
- .filter(n => !n.startsWith('_') && n !== 'index' && n !== 'workspace' && n !== 'viewfinder'),
1269
+ .filter(n => !n.startsWith('_') && n !== 'index' && n !== 'workspace' && n !== 'viewfinder' && n !== 'create'),
1133
1270
  [pageModules],
1134
1271
  )
1135
1272
 
1136
1273
  // canvasIndexTick is an intentional re-render trigger from HMR events
1137
1274
  // eslint-disable-next-line react-hooks/exhaustive-deps
1138
- const prototypeIndex = useMemo(() => buildPrototypeIndex(knownRoutes), [knownRoutes, canvasIndexTick])
1275
+ const localPrototypeIndex = useMemo(() => buildPrototypeIndex(knownRoutes), [knownRoutes, canvasIndexTick])
1276
+ const prototypeIndex = useMemo(
1277
+ () => mergeArtifactIndexIntoPrototypeIndex(localPrototypeIndex, artifactIndex, currentBranch),
1278
+ [localPrototypeIndex, artifactIndex, currentBranch],
1279
+ )
1139
1280
 
1140
1281
  // Build unified items list from all sources
1141
1282
  const allItems = useMemo(() => {
@@ -1152,18 +1293,22 @@ function WorkspaceImpl({
1152
1293
  : `/${proto.dirName}`
1153
1294
 
1154
1295
  items.push({
1155
- id: `proto:${proto.dirName}`,
1296
+ id: proto.id || `proto:${proto.dirName}`,
1156
1297
  name: proto.name,
1157
1298
  type: 'prototype',
1158
1299
  author: proto.author,
1159
1300
  gitAuthor: proto.gitAuthor,
1160
1301
  lastModified: proto.lastModified,
1161
- route,
1302
+ route: proto.route || route,
1162
1303
  isExternal: proto.isExternal,
1163
1304
  externalUrl: proto.externalUrl,
1164
1305
  folder: proto.folder,
1165
1306
  description: proto.description,
1166
1307
  flows: proto.flows || [],
1308
+ isPrivate: !!proto.isPrivate,
1309
+ branches: proto.branches || [],
1310
+ isBranchOnly: !!proto.isBranchOnly,
1311
+ homeBranch: proto.homeBranch || null,
1167
1312
  })
1168
1313
  }
1169
1314
 
@@ -1175,18 +1320,22 @@ function WorkspaceImpl({
1175
1320
  // Canvases (ungrouped + from folders)
1176
1321
  const addCanvas = (canvas) => {
1177
1322
  items.push({
1178
- id: `canvas:${canvas.dirName}`,
1323
+ id: canvas.id || `canvas:${canvas.dirName}`,
1179
1324
  name: canvas.name,
1180
1325
  type: 'canvas',
1181
1326
  author: canvas.author,
1182
1327
  gitAuthor: canvas.gitAuthor,
1183
- lastModified: null,
1328
+ lastModified: canvas.lastModified || null,
1184
1329
  route: canvas.route,
1185
1330
  isExternal: false,
1186
1331
  externalUrl: null,
1187
1332
  folder: canvas.folder,
1188
1333
  description: canvas.description,
1189
1334
  pages: canvas.pages || null,
1335
+ isPrivate: !!canvas.isPrivate,
1336
+ branches: canvas.branches || [],
1337
+ isBranchOnly: !!canvas.isBranchOnly,
1338
+ homeBranch: canvas.homeBranch || null,
1190
1339
  })
1191
1340
  }
1192
1341
 
@@ -1200,9 +1349,10 @@ function WorkspaceImpl({
1200
1349
  for (const name of storyNames) {
1201
1350
  const data = getStoryData(name)
1202
1351
  if (!data) continue
1352
+ const displayName = (name.split('/').pop() || name).replace(/^~/, '')
1203
1353
  items.push({
1204
1354
  id: `component:${name}`,
1205
- name: name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
1355
+ name: displayName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
1206
1356
  type: 'component',
1207
1357
  author: null,
1208
1358
  gitAuthor: null,
@@ -1212,6 +1362,7 @@ function WorkspaceImpl({
1212
1362
  externalUrl: null,
1213
1363
  folder: null,
1214
1364
  description: null,
1365
+ isPrivate: !!data._isPrivate || name.split('/').includes('drafts'),
1215
1366
  })
1216
1367
  }
1217
1368
 
@@ -1220,7 +1371,10 @@ function WorkspaceImpl({
1220
1371
 
1221
1372
  const itemMap = useMemo(() => Object.fromEntries(allItems.map(i => [i.id, i])), [allItems])
1222
1373
 
1223
- // Filter nav items based on visibility props
1374
+ // Filter nav items based on visibility props AND `ui.hide` config keys.
1375
+ // `ui.hide` entries take the form `workspace:<navId>` (e.g.
1376
+ // "workspace:components") and let consumers hide individual nav items
1377
+ // without passing component props.
1224
1378
  const visibleNavItems = useMemo(() => {
1225
1379
  const visibility = {
1226
1380
  all: showAllArtifacts,
@@ -1228,7 +1382,7 @@ function WorkspaceImpl({
1228
1382
  canvases: showCanvases,
1229
1383
  components: showComponents,
1230
1384
  }
1231
- return NAV_ITEMS.filter(nav => visibility[nav.id])
1385
+ return NAV_ITEMS.filter(nav => visibility[nav.id] && !isMenuHidden(`workspace:${nav.id}`))
1232
1386
  }, [showAllArtifacts, showPrototypes, showCanvases, showComponents])
1233
1387
 
1234
1388
  // State - default to first visible nav item
@@ -1253,9 +1407,8 @@ function WorkspaceImpl({
1253
1407
  const recentIds = useRecent()
1254
1408
 
1255
1409
  // Filter by nav category
1256
- const typeMap = { prototypes: 'prototype', canvases: 'canvas', components: 'component' }
1257
1410
  const navFiltered = useMemo(() => {
1258
- let filtered = activeNav === 'all' ? allItems : allItems.filter(i => i.type === typeMap[activeNav])
1411
+ let filtered = activeNav === 'all' ? allItems : allItems.filter(i => i.type === TYPE_MAP[activeNav])
1259
1412
  if (hiddenItems.size > 0) filtered = filtered.filter(i => !hiddenItems.has(i.id))
1260
1413
  return filtered
1261
1414
  }, [allItems, activeNav, hiddenItems])
@@ -1263,15 +1416,14 @@ function WorkspaceImpl({
1263
1416
  // Filter by tab
1264
1417
  const items = useMemo(() => {
1265
1418
  if (activeTab === 'Recent') {
1266
- const ordered = recentIds.map(id => itemMap[id]).filter(Boolean)
1419
+ const ordered = recentIds.map(id => itemMap[id]).filter(item => item && !item.isBranchOnly)
1267
1420
  if (activeNav !== 'all') {
1268
- const typeMap = { prototypes: 'prototype', canvases: 'canvas', components: 'component' }
1269
- return ordered.filter(i => i.type === typeMap[activeNav])
1421
+ return ordered.filter(i => i.type === TYPE_MAP[activeNav])
1270
1422
  }
1271
1423
  return ordered
1272
1424
  }
1273
1425
  const base = activeTab === 'Starred'
1274
- ? navFiltered.filter(i => starred.has(i.id))
1426
+ ? navFiltered.filter(i => !i.isBranchOnly && starred.has(i.id))
1275
1427
  : navFiltered
1276
1428
  return [...base].sort((a, b) => {
1277
1429
  const aTime = a.lastModified ? new Date(a.lastModified).getTime() : 0
@@ -1298,6 +1450,7 @@ function WorkspaceImpl({
1298
1450
  const folders = Object.entries(folderItems).map(([dirName, fItems]) => ({
1299
1451
  dirName,
1300
1452
  name: folderMeta[dirName]?.name || dirName,
1453
+ isPrivate: !!folderMeta[dirName]?.isPrivate || dirName === 'drafts',
1301
1454
  items: fItems,
1302
1455
  }))
1303
1456
  folders.sort((a, b) => {
@@ -1343,7 +1496,7 @@ function WorkspaceImpl({
1343
1496
  }), [visibleItems])
1344
1497
 
1345
1498
  // Starred items for sidebar
1346
- const starredItems = useMemo(() => visibleItems.filter(i => starred.has(i.id)), [visibleItems, starred])
1499
+ const starredItems = useMemo(() => visibleItems.filter(i => !i.isBranchOnly && starred.has(i.id)), [visibleItems, starred])
1347
1500
 
1348
1501
  return (
1349
1502
  <div className={css.layout} {...themeAttrs}>
@@ -1357,7 +1510,9 @@ function WorkspaceImpl({
1357
1510
  >
1358
1511
  {sidebarOpen ? <XIcon size={18} /> : <ThreeBarsIcon size={18} />}
1359
1512
  </button>
1360
- <div className={`${css.logo} smooth-corners`}><Icon name="iconoir/key-command" size={22} color="#fff" /></div>
1513
+ {logo
1514
+ ? <div className={`${css.logo} smooth-corners`}>{logo}</div>
1515
+ : <div className={`${css.logo} smooth-corners`}><Icon name={logoIcon} size={22} color="#fff" /></div>}
1361
1516
  <div>
1362
1517
  <div className={css.appName}>{title}</div>
1363
1518
  {subtitle && <div className={css.appSubtitle}>{subtitle}</div>}
@@ -1413,7 +1568,7 @@ function WorkspaceImpl({
1413
1568
  className={css.starredItem}
1414
1569
  href={s.isExternal ? s.externalUrl : withBase(basePath, s.route)}
1415
1570
  {...(s.isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
1416
- onClick={() => trackRecent(s.id)}
1571
+ onClick={() => trackRecentItem(s)}
1417
1572
  >
1418
1573
  <span className={css.starredIcon}>{getTypeIcon(s.type)}</span>
1419
1574
  {s.name}
@@ -1499,6 +1654,8 @@ function WorkspaceImpl({
1499
1654
  collapsed={collapsedFolders.has(folder.dirName)}
1500
1655
  onToggle={() => toggleFolder(folder.dirName)}
1501
1656
  basePath={basePath}
1657
+ branchBasePath={branchBasePath}
1658
+ currentBranch={currentBranch}
1502
1659
  starred={starred}
1503
1660
  onToggleStar={toggleStar}
1504
1661
  onItemDeleted={handleItemDeleted}
@@ -1511,6 +1668,8 @@ function WorkspaceImpl({
1511
1668
  key={item.id}
1512
1669
  item={item}
1513
1670
  basePath={basePath}
1671
+ branchBasePath={branchBasePath}
1672
+ currentBranch={currentBranch}
1514
1673
  starred={starred.has(item.id)}
1515
1674
  onToggleStar={toggleStar}
1516
1675
  onItemDeleted={handleItemDeleted}
@@ -1526,6 +1685,8 @@ function WorkspaceImpl({
1526
1685
  key={item.id}
1527
1686
  item={item}
1528
1687
  basePath={basePath}
1688
+ branchBasePath={branchBasePath}
1689
+ currentBranch={currentBranch}
1529
1690
  starred={starred.has(item.id)}
1530
1691
  onToggleStar={toggleStar}
1531
1692
  onItemDeleted={handleItemDeleted}
@@ -1543,11 +1704,15 @@ function WorkspaceImpl({
1543
1704
  /**
1544
1705
  * Workspace wrapper — short-circuits to an empty page when customer mode
1545
1706
  * has `hideHomepage` enabled, so the workspace dashboard never renders for
1546
- * end-customers. Wrapping (instead of an early return inside WorkspaceImpl)
1547
- * keeps the inner component's hooks call order stable.
1707
+ * end-customers. Also short-circuits when `canvasHomepage` is set, since
1708
+ * that already replaces the homepage via `history.replaceState` in
1709
+ * `mountStoryboardCore.applyCustomerMode` — rendering the workspace would
1710
+ * cause a flash before the redirect resolves. Wrapping (instead of an early
1711
+ * return inside WorkspaceImpl) keeps the inner component's hooks call order
1712
+ * stable.
1548
1713
  */
1549
1714
  export default function Workspace(props) {
1550
1715
  const cm = getCustomerModeConfig()
1551
- if (cm.enabled && cm.hideHomepage) return null
1716
+ if (cm.enabled && (cm.hideHomepage || cm.canvasHomepage)) return null
1552
1717
  return <WorkspaceImpl {...props} />
1553
1718
  }