@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
@@ -1,8 +1,8 @@
1
1
  .embed {
2
2
  position: relative;
3
3
  overflow: hidden;
4
- background: var(--bgColor-default, #ffffff);
5
- border: 3px solid var(--borderColor-default, #d0d7de);
4
+ background: var(--color-background, #ffffff);
5
+ border: 3px solid var(--color-border, #d0d7de);
6
6
  border-radius: 12px;
7
7
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
8
8
  }
@@ -14,9 +14,9 @@
14
14
  padding: 10px 10px;
15
15
  font-size: 12px;
16
16
  font-weight: 500;
17
- color: var(--fgColor-muted, #656d76);
18
- background: var(--bgColor-muted, #f6f8fa);
19
- border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
17
+ color: var(--color-foreground-muted, #656d76);
18
+ background: var(--color-background-muted, #f6f8fa);
19
+ border-bottom: 1px solid var(--color-border-muted, #d8dee4);
20
20
  white-space: nowrap;
21
21
  overflow: hidden;
22
22
  text-overflow: ellipsis;
@@ -48,8 +48,10 @@
48
48
  align-items: center;
49
49
  justify-content: center;
50
50
  gap: 8px;
51
- color: var(--fgColor-muted, #656d76);
51
+ background: var(--color-background-muted, #f6f8fa);
52
+ color: var(--color-foreground-muted, #656d76);
52
53
  text-align: center;
54
+ z-index: 2;
53
55
  }
54
56
 
55
57
  .placeholderIcon {
@@ -63,10 +65,10 @@
63
65
  }
64
66
 
65
67
  .spinner {
66
- width: 24px;
67
- height: 24px;
68
- border: 3px solid var(--borderColor-muted, #d0d7de);
69
- border-top-color: var(--fgColor-accent, #2f81f7);
68
+ width: 40px;
69
+ height: 40px;
70
+ border: 3px solid var(--color-border-muted, #d0d7de);
71
+ border-top-color: var(--color-foreground-accent, #2f81f7);
70
72
  border-radius: 50%;
71
73
  animation: spin 0.8s linear infinite;
72
74
  }
@@ -100,7 +102,7 @@
100
102
  align-items: center;
101
103
  justify-content: center;
102
104
  height: 100%;
103
- color: var(--fgColor-muted, #656d76);
105
+ color: var(--color-foreground-muted, #656d76);
104
106
  font-size: 14px;
105
107
  font-style: italic;
106
108
  cursor: pointer;
@@ -122,9 +124,9 @@
122
124
  align-items: center;
123
125
  justify-content: center;
124
126
  border-radius: 6px;
125
- background: var(--bgColor-default, rgba(255, 255, 255, 0.92));
126
- border: 1px solid var(--borderColor-default, rgba(0, 0, 0, 0.12));
127
- color: var(--fgColor-default, #1f2328);
127
+ background: var(--color-background, rgba(255, 255, 255, 0.92));
128
+ border: 1px solid var(--color-border, rgba(0, 0, 0, 0.12));
129
+ color: var(--color-foreground, #1f2328);
128
130
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
129
131
  font-size: 14px;
130
132
  opacity: 0;
@@ -137,7 +139,7 @@
137
139
  }
138
140
 
139
141
  .editBtn:hover {
140
- background: var(--bgColor-muted, rgba(255, 255, 255, 0.98));
142
+ background: var(--color-background-muted, rgba(255, 255, 255, 0.98));
141
143
  }
142
144
 
143
145
  .urlForm {
@@ -170,14 +172,14 @@
170
172
  margin: 8px 16px;
171
173
  padding: 6px 10px;
172
174
  font-size: 13px;
173
- border: 1px solid var(--borderColor-default, #d0d7de);
175
+ border: 1px solid var(--color-border, #d0d7de);
174
176
  border-radius: 6px;
175
- background: var(--bgColor-default, #ffffff);
176
- color: var(--fgColor-default, #1f2328);
177
+ background: var(--color-background, #ffffff);
178
+ color: var(--color-foreground, #1f2328);
177
179
  }
178
180
 
179
181
  .filterInput:focus {
180
- border-color: var(--bgColor-accent-emphasis, #2f81f7);
182
+ border-color: var(--color-background-accent-emphasis, #2f81f7);
181
183
  box-shadow: 0 0 0 2px rgba(47, 129, 247, 0.3);
182
184
  }
183
185
 
@@ -195,7 +197,7 @@
195
197
  padding: 6px 8px 2px;
196
198
  font-size: 11px;
197
199
  font-weight: 600;
198
- color: var(--fgColor-muted, #656d76);
200
+ color: var(--color-foreground-muted, #656d76);
199
201
  text-transform: uppercase;
200
202
  letter-spacing: 0.5px;
201
203
  }
@@ -207,7 +209,7 @@
207
209
  box-sizing: border-box;
208
210
  padding: 6px 12px;
209
211
  font-size: 13px;
210
- color: var(--fgColor-default, #1f2328);
212
+ color: var(--color-foreground, #1f2328);
211
213
  border-radius: 6px;
212
214
  cursor: pointer;
213
215
  white-space: nowrap;
@@ -216,18 +218,18 @@
216
218
  }
217
219
 
218
220
  .pickerItem:hover {
219
- background: var(--bgColor-neutral-muted, #eaeef2);
221
+ background: var(--color-background-neutral-muted, #eaeef2);
220
222
  }
221
223
 
222
224
  .pickerItem:focus-visible {
223
- outline: 4px solid var(--bgColor-accent-emphasis, #2f81f7);
225
+ outline: 4px solid var(--color-background-accent-emphasis, #2f81f7);
224
226
  outline-offset: -2px;
225
227
  }
226
228
 
227
229
  .pickerEmpty {
228
230
  padding: 12px;
229
231
  font-size: 13px;
230
- color: var(--fgColor-muted, #656d76);
232
+ color: var(--color-foreground-muted, #656d76);
231
233
  font-style: italic;
232
234
  text-align: center;
233
235
  }
@@ -235,7 +237,7 @@
235
237
  .pickerDivider {
236
238
  height: 1px;
237
239
  margin: 4px 16px;
238
- background: var(--borderColor-muted, #d8dee4);
240
+ background: var(--color-border-muted, #d8dee4);
239
241
  }
240
242
 
241
243
  .customUrlSection {
@@ -248,7 +250,7 @@
248
250
  .urlLabel {
249
251
  font-size: 12px;
250
252
  font-weight: 600;
251
- color: var(--fgColor-muted, #656d76);
253
+ color: var(--color-foreground-muted, #656d76);
252
254
  text-transform: uppercase;
253
255
  letter-spacing: 0.5px;
254
256
  }
@@ -258,14 +260,14 @@
258
260
  padding: 8px 10px;
259
261
  font-size: 14px;
260
262
  font-family: ui-monospace, SFMono-Regular, monospace;
261
- border: 1px solid var(--borderColor-default, #d0d7de);
263
+ border: 1px solid var(--color-border, #d0d7de);
262
264
  border-radius: 6px;
263
- background: var(--bgColor-default, #ffffff);
264
- color: var(--fgColor-default, #1f2328);
265
+ background: var(--color-background, #ffffff);
266
+ color: var(--color-foreground, #1f2328);
265
267
  }
266
268
 
267
269
  .urlInput:focus {
268
- border-color: var(--bgColor-accent-emphasis, #2f81f7);
270
+ border-color: var(--color-background-accent-emphasis, #2f81f7);
269
271
  box-shadow: 0 0 0 2px rgba(47, 129, 247, 0.3);
270
272
  }
271
273
 
@@ -286,21 +288,21 @@
286
288
  }
287
289
 
288
290
  .urlSave {
289
- background: var(--bgColor-accent-emphasis, #2f81f7);
290
- color: var(--fgColor-onEmphasis, #ffffff);
291
+ background: var(--color-background-accent-emphasis, #2f81f7);
292
+ color: var(--color-foreground-on-emphasis, #ffffff);
291
293
  }
292
294
 
293
295
  .urlSave:hover {
294
- background: var(--bgColor-accent-emphasis, #388bfd);
296
+ background: var(--color-background-accent-emphasis, #388bfd);
295
297
  }
296
298
 
297
299
  .urlCancel {
298
- color: var(--fgColor-muted, #656d76);
300
+ color: var(--color-foreground-muted, #656d76);
299
301
  }
300
302
 
301
303
  .urlCancel:hover {
302
- color: var(--fgColor-default, #1f2328);
303
- background: var(--bgColor-muted, #f6f8fa);
304
+ color: var(--color-foreground, #1f2328);
305
+ background: var(--color-background-muted, #f6f8fa);
304
306
  }
305
307
 
306
308
  .resizeHandle {
@@ -313,12 +315,12 @@
313
315
  background: linear-gradient(
314
316
  135deg,
315
317
  transparent 40%,
316
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 40%,
317
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 50%,
318
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 40%,
319
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 50%,
318
320
  transparent 50%,
319
321
  transparent 65%,
320
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 65%,
321
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 75%,
322
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 65%,
323
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 75%,
322
324
  transparent 75%
323
325
  );
324
326
  opacity: 0;
@@ -339,8 +341,8 @@
339
341
  display: flex;
340
342
  align-items: center;
341
343
  border-radius: 10px;
342
- border: 1.5px solid var(--trigger-border, var(--borderColor-muted, #d0d7de));
343
- background: var(--trigger-bg, var(--bgColor-muted, #f6f8fa));
344
+ border: 1.5px solid var(--trigger-border, var(--color-border-muted, #d0d7de));
345
+ background: var(--trigger-bg, var(--color-background-muted, #f6f8fa));
344
346
  opacity: 0;
345
347
  transition: opacity 150ms;
346
348
  z-index: 3;
@@ -360,7 +362,7 @@
360
362
  justify-content: center;
361
363
  font-size: 16px;
362
364
  font-weight: 600;
363
- color: var(--trigger-text, var(--fgColor-default, #1f2328));
365
+ color: var(--trigger-text, var(--color-foreground, #1f2328));
364
366
  transition: background 120ms;
365
367
  }
366
368
 
@@ -373,7 +375,7 @@
373
375
  }
374
376
 
375
377
  .zoomBtn:hover:not(:disabled) {
376
- background: var(--trigger-bg-hover, var(--bgColor-neutral-muted, #eaeef2));
378
+ background: var(--trigger-bg-hover, var(--color-background-neutral-muted, #eaeef2));
377
379
  }
378
380
 
379
381
  .zoomBtn:disabled {
@@ -391,9 +393,9 @@
391
393
  font-size: 11px;
392
394
  font-weight: 600;
393
395
  font-variant-numeric: tabular-nums;
394
- color: var(--trigger-text, var(--fgColor-default, #1f2328));
395
- border-left: 1.5px solid var(--trigger-border, var(--borderColor-muted, #d0d7de));
396
- border-right: 1.5px solid var(--trigger-border, var(--borderColor-muted, #d0d7de));
396
+ color: var(--trigger-text, var(--color-foreground, #1f2328));
397
+ border-left: 1.5px solid var(--trigger-border, var(--color-border-muted, #d0d7de));
398
+ border-right: 1.5px solid var(--trigger-border, var(--color-border-muted, #d0d7de));
397
399
  user-select: none;
398
400
  }
399
401
 
@@ -420,7 +422,7 @@
420
422
  position: relative;
421
423
  border-radius: 12px;
422
424
  overflow: hidden;
423
- background: var(--bgColor-default, #ffffff);
425
+ background: var(--color-background, #ffffff);
424
426
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.4);
425
427
  animation: expandScaleIn 0.2s ease;
426
428
  }
@@ -469,7 +471,7 @@
469
471
  height: 100%;
470
472
  position: relative;
471
473
  overflow: hidden;
472
- background: var(--bgColor-default, #ffffff);
474
+ background: var(--color-background, #ffffff);
473
475
  }
474
476
 
475
477
  .expandContainerSplit .expandIframe {
@@ -499,7 +501,7 @@
499
501
  min-width: 0;
500
502
  height: 100%;
501
503
  overflow: hidden;
502
- border-right: 1px solid var(--borderColor-muted, #d8dee4);
504
+ border-right: 1px solid var(--color-border-muted, #d8dee4);
503
505
  }
504
506
 
505
507
  .expandSplitRight {
@@ -513,7 +515,7 @@
513
515
  width: 100%;
514
516
  height: 100%;
515
517
  overflow: auto;
516
- background: var(--bgColor-default, #ffffff);
518
+ background: var(--color-background, #ffffff);
517
519
  }
518
520
 
519
521
  .expandSecondary iframe {
@@ -533,13 +535,13 @@
533
535
  padding: 32px 40px;
534
536
  font-size: 15px;
535
537
  line-height: 1.7;
536
- color: var(--fgColor-default, #1f2328);
538
+ color: var(--color-foreground, #1f2328);
537
539
  max-width: 800px;
538
540
  margin: 0 auto;
539
541
  }
540
542
 
541
543
  .expandMarkdownContent * { pointer-events: auto; }
542
- .expandMarkdownContent a { color: var(--fgColor-accent, #0969da); text-decoration: none; }
544
+ .expandMarkdownContent a { color: var(--color-foreground-accent, #0969da); text-decoration: none; }
543
545
  .expandMarkdownContent a:hover { text-decoration: underline; }
544
546
  .expandMarkdownContent img { max-width: 100%; height: auto; border-radius: 6px; margin: 8px 0; display: block; }
545
547
 
@@ -547,14 +549,14 @@
547
549
  height: 100%;
548
550
  display: flex;
549
551
  flex-direction: column;
550
- background: var(--bgColor-default, #ffffff);
552
+ background: var(--color-background, #ffffff);
551
553
  }
552
554
 
553
555
  .expandGithubHeader {
554
556
  padding: 16px 24px;
555
557
  font-size: 18px;
556
558
  font-weight: 600;
557
- border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
559
+ border-bottom: 1px solid var(--color-border-muted, #d8dee4);
558
560
  }
559
561
 
560
562
  .expandGithubBody {
@@ -566,7 +568,7 @@
566
568
  }
567
569
 
568
570
  .expandGithubBody * { pointer-events: auto; }
569
- .expandGithubBody a { color: var(--fgColor-accent, #0969da); }
571
+ .expandGithubBody a { color: var(--color-foreground-accent, #0969da); }
570
572
 
571
573
  .expandLinkCard {
572
574
  padding: 32px 40px;
@@ -1,10 +1,124 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { getEmbedChromeVars } from './embedTheme.js'
3
+ import { normalizeLegacyEmbedSrc } from './normalizeLegacyEmbedSrc.js'
3
4
 
4
5
  describe('getEmbedChromeVars', () => {
5
- it('follows toolbar theme variants for embed edit chrome', () => {
6
- expect(getEmbedChromeVars('light')['--bgColor-default']).toBe('#ffffff')
7
- expect(getEmbedChromeVars('dark')['--bgColor-default']).toBe('#0d1117')
8
- expect(getEmbedChromeVars('dark_dimmed')['--bgColor-default']).toBe('#212830')
6
+ it('returns an empty object color tokens now cascade from data-sb-canvas-theme via tailwind.css', () => {
7
+ expect(getEmbedChromeVars('light')).toEqual({})
8
+ expect(getEmbedChromeVars('dark')).toEqual({})
9
+ expect(getEmbedChromeVars('dark_dimmed')).toEqual({})
10
+ })
11
+ })
12
+
13
+ describe('normalizeLegacyEmbedSrc', () => {
14
+ it('passes through clean canvas-app routes unchanged', () => {
15
+ expect(normalizeLegacyEmbedSrc('/cq-org-enablement')).toBe('/cq-org-enablement')
16
+ expect(normalizeLegacyEmbedSrc('/MyProto/SignupForm')).toBe('/MyProto/SignupForm')
17
+ expect(normalizeLegacyEmbedSrc('/cq-org-enablement#x=1')).toBe('/cq-org-enablement#x=1')
18
+ })
19
+
20
+ it('passes through external http(s) URLs unchanged', () => {
21
+ expect(normalizeLegacyEmbedSrc('https://example.com/foo')).toBe('https://example.com/foo')
22
+ })
23
+
24
+ it('passes through empty/falsy values', () => {
25
+ expect(normalizeLegacyEmbedSrc('')).toBe('')
26
+ expect(normalizeLegacyEmbedSrc(null)).toBe(null)
27
+ expect(normalizeLegacyEmbedSrc(undefined)).toBe(undefined)
28
+ })
29
+
30
+ describe('legacy hash-form: /prototypes.html#/<route>', () => {
31
+ it('strips loader prefix and keeps inner route', () => {
32
+ expect(normalizeLegacyEmbedSrc('/prototypes.html#/cq-org-enablement')).toBe('/cq-org-enablement')
33
+ })
34
+
35
+ it('keeps query strings inside inner', () => {
36
+ expect(normalizeLegacyEmbedSrc('/prototypes.html#/cq-org-enablement?cqConfirmOpen=true'))
37
+ .toBe('/cq-org-enablement?cqConfirmOpen=true')
38
+ })
39
+
40
+ it('handles non-root basePath', () => {
41
+ expect(normalizeLegacyEmbedSrc('/storyboard/prototypes.html#/MyProto')).toBe('/MyProto')
42
+ })
43
+
44
+ it('returns "/" for empty inner', () => {
45
+ expect(normalizeLegacyEmbedSrc('/prototypes.html#')).toBe('/')
46
+ })
47
+
48
+ it('prefixes "/" if inner missing leading slash', () => {
49
+ expect(normalizeLegacyEmbedSrc('/prototypes.html#MyProto')).toBe('/MyProto')
50
+ })
51
+ })
52
+
53
+ describe('legacy URLSearchParams-clobbered form (real-world 0.6.13/0.6.14 bug)', () => {
54
+ // Real bug: storyboard's session.writeHash did URLSearchParams.toString()
55
+ // on the entire hash, which URL-encoded the route slash to %2F and turned
56
+ // /MyProto into a key with an empty value. Real persisted srcs from the
57
+ // wild look like /prototypes.html#%2Fcq-org-enablement=&cqConfirmOpen=true
58
+ it('recovers route from URLSearchParams-mangled hash', () => {
59
+ expect(
60
+ normalizeLegacyEmbedSrc('/prototypes.html#%2Fcq-org-enablement=&cqConfirmOpen=true'),
61
+ ).toBe('/cq-org-enablement#cqConfirmOpen=true')
62
+ })
63
+
64
+ it('recovers route with no surviving storyboard state', () => {
65
+ expect(normalizeLegacyEmbedSrc('/prototypes.html#%2Fcq-org-enablement=')).toBe('/cq-org-enablement')
66
+ })
67
+
68
+ it('recovers route with multiple storyboard params', () => {
69
+ expect(
70
+ normalizeLegacyEmbedSrc(
71
+ '/prototypes.html#%2Fcq-org-enablement=&cqEnabled=null&cqFlashDismissed=null&cqConfirmOpen=true',
72
+ ),
73
+ ).toBe('/cq-org-enablement#cqEnabled=null&cqFlashDismissed=null&cqConfirmOpen=true')
74
+ })
75
+
76
+ it('recovers nested route', () => {
77
+ expect(
78
+ normalizeLegacyEmbedSrc('/prototypes.html#%2FMyProto%2FSignupForm=&x=1'),
79
+ ).toBe('/MyProto/SignupForm#x=1')
80
+ })
81
+
82
+ it('handles non-root basePath', () => {
83
+ expect(
84
+ normalizeLegacyEmbedSrc('/storyboard/prototypes.html#%2FMyProto=&x=1'),
85
+ ).toBe('/MyProto#x=1')
86
+ })
87
+ })
88
+
89
+ describe('defensive path-form: /prototypes.html/<route>', () => {
90
+ it('strips loader prefix', () => {
91
+ expect(normalizeLegacyEmbedSrc('/prototypes.html/MyProto')).toBe('/MyProto')
92
+ })
93
+
94
+ it('handles nested routes', () => {
95
+ expect(normalizeLegacyEmbedSrc('/prototypes.html/MyProto/SignupForm')).toBe('/MyProto/SignupForm')
96
+ })
97
+
98
+ it('handles non-root basePath', () => {
99
+ expect(normalizeLegacyEmbedSrc('/storyboard/prototypes.html/MyProto')).toBe('/MyProto')
100
+ })
101
+ })
102
+
103
+ describe('all-encompassing regression — no legacy shape can produce a /prototypes.html src', () => {
104
+ const cases = [
105
+ '/prototypes.html#/foo',
106
+ '/prototypes.html#/foo?bar=1',
107
+ '/prototypes.html#',
108
+ '/prototypes.html#%2Ffoo=&bar=1',
109
+ '/prototypes.html#%2Ffoo%2Fbar=&x=1',
110
+ '/prototypes.html/foo',
111
+ '/prototypes.html/foo/bar',
112
+ '/storyboard/prototypes.html#/foo',
113
+ '/storyboard/prototypes.html#%2Ffoo=&x=1',
114
+ '/storyboard/prototypes.html/foo',
115
+ ]
116
+ for (const input of cases) {
117
+ it(`recovers ${input}`, () => {
118
+ const result = normalizeLegacyEmbedSrc(input)
119
+ expect(result).not.toMatch(/prototypes\.html/)
120
+ expect(result.startsWith('/')).toBe(true)
121
+ })
122
+ }
9
123
  })
10
124
  })
@@ -8,12 +8,12 @@
8
8
  background: linear-gradient(
9
9
  135deg,
10
10
  transparent 40%,
11
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 40%,
12
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 50%,
11
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 40%,
12
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 50%,
13
13
  transparent 50%,
14
14
  transparent 65%,
15
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 65%,
16
- var(--borderColor-muted, rgba(0, 0, 0, 0.15)) 75%,
15
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 65%,
16
+ var(--color-border-muted, rgba(0, 0, 0, 0.15)) 75%,
17
17
  transparent 75%
18
18
  );
19
19
  opacity: 0;
@@ -20,7 +20,6 @@ import ExpandedPane from './ExpandedPane.jsx'
20
20
  import { buildSecondaryIframeUrl, getSplitPaneLabel } from './expandUtils.js'
21
21
  import { useExpandOverride } from './useExpandOverride.js'
22
22
  import { useIframeDevLogs } from './iframeDevLogs.js'
23
- import styles from './StorySetWidget.module.css'
24
23
  import overlayStyles from './embedOverlay.module.css'
25
24
 
26
25
  function GridIcon({ size = 16 }) {
@@ -31,18 +30,28 @@ function resolveStorySetUrl(storyId, layout, selected, density, theme) {
31
30
  const story = getStoryData(storyId)
32
31
  if (!story?._storyModule) return ''
33
32
  const base = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
33
+
34
+ // Route through the shared iframe-isolation entry in dev so a broken
35
+ // .story.jsx can't poison the canvas SPA's module graph. The real story
36
+ // route handles it in prod (no isolation middleware in deployed builds).
34
37
  const params = new URLSearchParams()
35
- // Route via the real story page (works in dev AND prod). The dev-only
36
- // `_storyboard/canvas/isolate-set` middleware doesn't exist in deployed
37
- // builds, so we mount ComponentSetPage at the story's route with
38
- // `_sb_component_set` instead. `_sb_embed` keeps the canvas chrome off.
39
38
  params.set('_sb_embed', '')
40
39
  params.set('_sb_component_set', '')
40
+ // Force the prototype surface inside the iframe to follow the global
41
+ // theme. themeBootstrap only writes a theme's companion attrs
42
+ // (data-color-mode, data-light-theme, data-dark-theme) to <html> for
43
+ // the prototype surface — so any other target would leave Primer
44
+ // components rendering in light mode regardless of the canvas theme.
45
+ params.set('_sb_theme_target', 'prototype')
41
46
  if (layout) params.set('layout', layout)
42
47
  if (selected) params.set('selected', selected)
43
48
  if (density) params.set('density', density)
44
49
  if (theme) params.set('theme', theme)
50
+
45
51
  const route = story._route || `/components/${storyId}`
52
+ if (import.meta.env.DEV) {
53
+ return `${base}/stories.html${route}?${params}`
54
+ }
46
55
  return `${base}${route}?${params}`
47
56
  }
48
57
 
@@ -61,6 +70,10 @@ export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdat
61
70
  const contentSizeRef = useRef({ width: 0, height: 0 })
62
71
  const [snapping, setSnapping] = useState(false)
63
72
  const [interactive, setInteractive] = useState(false)
73
+ // Track which src URL has fired its onLoad callback so we can overlay a
74
+ // spinner while a new iframe is fetching. Stored as the URL itself so
75
+ // changing src naturally invalidates the loaded state without an effect.
76
+ const [loadedSrc, setLoadedSrc] = useState(null)
64
77
  const [storyIndexKey, setStoryIndexKey] = useState(0)
65
78
  const [expandedMode, setExpandedMode] = useExpandOverride('storyset', widgetId)
66
79
  const expanded = expandedMode === '1' || expandedMode === 'immersive'
@@ -193,10 +206,13 @@ export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdat
193
206
  if (!storyId) {
194
207
  return (
195
208
  <WidgetWrapper>
196
- <div className={styles.container} ref={containerRef}>
197
- <div className={styles.error}>
198
- <span className={styles.errorIcon}><GridIcon size={20} /></span>
199
- <span className={styles.errorText}>Missing story ID</span>
209
+ <div
210
+ ref={containerRef}
211
+ className="relative overflow-hidden min-w-[200px] min-h-[120px] w-full h-full rounded-lg bg-background shadow-[0_4px_16px_rgba(0,0,0,0.08)]"
212
+ >
213
+ <div className="flex items-center gap-2 p-4 text-sm leading-snug font-[system-ui,-apple-system,sans-serif] text-foreground-danger">
214
+ <span className="text-xl flex-shrink-0"><GridIcon size={20} /></span>
215
+ <span className="break-words">Missing story ID</span>
200
216
  </div>
201
217
  </div>
202
218
  </WidgetWrapper>
@@ -206,10 +222,13 @@ export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdat
206
222
  if (!iframeSrc) {
207
223
  return (
208
224
  <WidgetWrapper>
209
- <div className={styles.container} ref={containerRef}>
210
- <div className={styles.error}>
211
- <span className={styles.errorIcon}><GridIcon size={20} /></span>
212
- <span className={styles.errorText}>Story &ldquo;{storyId}&rdquo; not found or has no route</span>
225
+ <div
226
+ ref={containerRef}
227
+ className="relative overflow-hidden min-w-[200px] min-h-[120px] w-full h-full rounded-lg bg-background shadow-[0_4px_16px_rgba(0,0,0,0.08)]"
228
+ >
229
+ <div className="flex items-center gap-2 p-4 text-sm leading-snug font-[system-ui,-apple-system,sans-serif] text-foreground-danger">
230
+ <span className="text-xl flex-shrink-0"><GridIcon size={20} /></span>
231
+ <span className="break-words">Story &ldquo;{storyId}&rdquo; not found or has no route</span>
213
232
  </div>
214
233
  </div>
215
234
  </WidgetWrapper>
@@ -224,25 +243,34 @@ export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdat
224
243
  return (
225
244
  <>
226
245
  <WidgetWrapper>
227
- <div ref={containerRef} className={styles.container} style={sizeStyle}>
228
- <div className={styles.header}>
229
- <span className={styles.headerIcon}><GridIcon size={16} /></span>
230
- <span className={styles.headerTitle}>{displayName}</span>
246
+ <div
247
+ ref={containerRef}
248
+ className="relative overflow-hidden min-w-[200px] min-h-[120px] w-full h-full rounded-lg bg-background shadow-[0_4px_16px_rgba(0,0,0,0.08)]"
249
+ style={sizeStyle}
250
+ >
251
+ <div className="flex items-center gap-1.5 px-2.5 py-2.5 text-xs font-medium whitespace-nowrap overflow-hidden text-ellipsis select-none border-b border-border-muted bg-background-muted text-foreground-muted">
252
+ <span className="inline-flex flex-shrink-0"><GridIcon size={16} /></span>
253
+ <span className="overflow-hidden text-ellipsis">{displayName}</span>
231
254
  {selected && (
232
- <span className={styles.headerSelected}>· {selected}</span>
255
+ <span className="font-semibold flex-shrink-0 text-foreground-accent">· {selected}</span>
233
256
  )}
234
- <span className={styles.headerLayout} title={`Layout: ${layout}`}>
235
- {layout === 'wide' ? '▭' : layout === 'tall' ? '▯' : '▦'}
236
- </span>
237
257
  </div>
238
- <div className={styles.content}>
258
+ <div className="relative w-full h-[calc(100%-37px)]">
239
259
  <iframe
240
260
  ref={iframeRef}
241
261
  src={iframeSrc}
242
- className={styles.iframe}
262
+ className="absolute inset-0 block w-full h-full border-0 z-[1]"
243
263
  title={`${displayName} component set`}
244
- onLoad={(e) => e.target.blur()}
264
+ onLoad={(e) => { e.target.blur(); setLoadedSrc(iframeSrc) }}
245
265
  />
266
+ {loadedSrc !== iframeSrc && (
267
+ <div
268
+ className="absolute inset-0 z-[2] flex flex-col items-center justify-center gap-2 text-center bg-background-muted text-foreground-muted"
269
+ aria-hidden="true"
270
+ >
271
+ <div className="w-10 h-10 rounded-full animate-spin border-t-foreground-accent" />
272
+ </div>
273
+ )}
246
274
  </div>
247
275
  {!interactive && (
248
276
  <div
@@ -299,7 +327,7 @@ function ComponentSetExpandPane({ widgetId, storyId, layout, selected, immersive
299
327
  kind: 'react',
300
328
  render: () => url
301
329
  ? <iframe src={url} style={{ border: 'none', width: '100%', height: '100%', display: 'block' }} title={storyId} onLoad={(e) => e.target.blur()} />
302
- : <div style={{ padding: 32, color: 'var(--fgColor-muted)' }}>Story &quot;{storyId}&quot; not found</div>,
330
+ : <div style={{ padding: 32, color: 'var(--color-foreground-muted)' }}>Story &quot;{storyId}&quot; not found</div>,
303
331
  }), [widgetId, label, url, storyId])
304
332
 
305
333
  return (