@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,10 +1,10 @@
1
1
  .block {
2
2
  position: relative;
3
3
  min-height: 80px;
4
- --sb--markdown-bg: var(--bgColor-default, #ffffff);
5
- --sb--markdown-fg: var(--fgColor-default, #1f2328);
6
- --sb--markdown-muted: var(--fgColor-muted, #656d76);
7
- --sb--markdown-accent: var(--bgColor-accent-emphasis, #2f81f7);
4
+ --sb--markdown-bg: var(--color-background, #ffffff);
5
+ --sb--markdown-fg: var(--color-foreground, #1f2328);
6
+ --sb--markdown-muted: var(--color-foreground-muted, #656d76);
7
+ --sb--markdown-accent: var(--color-background-accent-emphasis, #2f81f7);
8
8
  background: var(--sb--markdown-bg);
9
9
  color: var(--sb--markdown-fg);
10
10
  font-family: var(--tc-font-stack, system-ui, -apple-system, sans-serif);
@@ -49,7 +49,7 @@
49
49
  max-width: 100%;
50
50
  height: auto;
51
51
  border-radius: 6px;
52
- border: 1px solid var(--borderColor-default, #d0d7de);
52
+ border: 1px solid var(--color-border, #d0d7de);
53
53
  margin: 8px 0;
54
54
  display: block;
55
55
  pointer-events: auto;
@@ -59,11 +59,11 @@
59
59
  max-width: 100%;
60
60
  height: auto;
61
61
  border-radius: 6px;
62
- border: 1px solid var(--borderColor-default, #d0d7de);
62
+ border: 1px solid var(--color-border, #d0d7de);
63
63
  margin: 8px 0;
64
64
  display: block;
65
65
  pointer-events: auto;
66
- background: var(--bgColor-muted, #f6f8fa);
66
+ background: var(--color-background-muted, #f6f8fa);
67
67
  }
68
68
 
69
69
  .preview h1 {
@@ -92,7 +92,7 @@
92
92
  }
93
93
 
94
94
  .preview code {
95
- background: var(--bgColor-neutral-muted, #afb8c133);
95
+ background: var(--color-background-neutral-muted, #afb8c133);
96
96
  padding: 2px 5px;
97
97
  border-radius: 4px;
98
98
  font-size: 12px;
@@ -145,13 +145,13 @@
145
145
 
146
146
  .preview th,
147
147
  .preview td {
148
- border: 1px solid var(--borderColor-default, #d0d7de);
148
+ border: 1px solid var(--color-border, #d0d7de);
149
149
  padding: 6px 12px;
150
150
  text-align: left;
151
151
  }
152
152
 
153
153
  .preview th {
154
- background: var(--bgColor-muted, #f6f8fa);
154
+ background: var(--color-background-muted, #f6f8fa);
155
155
  font-weight: 600;
156
156
  }
157
157
 
@@ -159,17 +159,17 @@
159
159
  .preview pre {
160
160
  padding: 12px 16px;
161
161
  border-radius: 6px;
162
- border: 1px solid var(--borderColor-muted, #d8dee4);
162
+ border: 1px solid var(--color-border-muted, #d8dee4);
163
163
  overflow-x: auto;
164
164
  margin: 8px 0;
165
- background: var(--bgColor-neutral-muted, #afb8c133);
165
+ background: var(--color-background-neutral-muted, #afb8c133);
166
166
  line-height: 1.4;
167
167
  }
168
168
 
169
169
  /* When inspector highlighter sets inline background, let it through */
170
170
  .preview pre[style] {
171
171
  border-radius: 6px;
172
- border: 1px solid var(--borderColor-muted, #d8dee4);
172
+ border: 1px solid var(--color-border-muted, #d8dee4);
173
173
  overflow-x: auto;
174
174
  margin: 8px 0;
175
175
  }
@@ -193,7 +193,7 @@
193
193
 
194
194
  /* Blockquotes */
195
195
  .preview blockquote {
196
- border-left: 4px solid var(--borderColor-default, #d0d7de);
196
+ border-left: 4px solid var(--color-border, #d0d7de);
197
197
  margin: 8px 0;
198
198
  padding: 4px 16px;
199
199
  color: var(--sb--markdown-muted);
@@ -202,7 +202,7 @@
202
202
  /* Horizontal rules */
203
203
  .preview hr {
204
204
  border: none;
205
- border-top: 1px solid var(--borderColor-default, #d0d7de);
205
+ border-top: 1px solid var(--color-border, #d0d7de);
206
206
  margin: 16px 0;
207
207
  }
208
208
 
@@ -231,10 +231,10 @@
231
231
  /* ── Expanded preview in modal ──────────────────────────────────── */
232
232
 
233
233
  .expandedPreview {
234
- --sb--markdown-bg: var(--bgColor-default, #ffffff);
235
- --sb--markdown-fg: var(--fgColor-default, #1f2328);
236
- --sb--markdown-muted: var(--fgColor-muted, #656d76);
237
- --sb--markdown-accent: var(--bgColor-accent-emphasis, #2f81f7);
234
+ --sb--markdown-bg: var(--color-background, #ffffff);
235
+ --sb--markdown-fg: var(--color-foreground, #1f2328);
236
+ --sb--markdown-muted: var(--color-foreground-muted, #656d76);
237
+ --sb--markdown-accent: var(--color-background-accent-emphasis, #2f81f7);
238
238
  padding: 32px 40px;
239
239
  font-size: 15px;
240
240
  line-height: 1.7;
@@ -264,7 +264,7 @@
264
264
  max-width: 100%;
265
265
  height: auto;
266
266
  border-radius: 6px;
267
- border: 1px solid var(--borderColor-default, #d0d7de);
267
+ border: 1px solid var(--color-border, #d0d7de);
268
268
  margin: 8px 0;
269
269
  display: block;
270
270
  }
@@ -273,7 +273,7 @@
273
273
  max-width: 100%;
274
274
  height: auto;
275
275
  border-radius: 6px;
276
- border: 1px solid var(--borderColor-default, #d0d7de);
276
+ border: 1px solid var(--color-border, #d0d7de);
277
277
  margin: 8px 0;
278
278
  display: block;
279
279
  }
@@ -284,7 +284,7 @@
284
284
  .expandedPreview p { margin: 0 0 12px; }
285
285
 
286
286
  .expandedPreview code {
287
- background: var(--bgColor-neutral-muted, #afb8c133);
287
+ background: var(--color-background-neutral-muted, #afb8c133);
288
288
  padding: 2px 5px;
289
289
  border-radius: 4px;
290
290
  font-size: 13px;
@@ -294,10 +294,10 @@
294
294
  .expandedPreview pre {
295
295
  padding: 12px 16px;
296
296
  border-radius: 6px;
297
- border: 1px solid var(--borderColor-muted, #d8dee4);
297
+ border: 1px solid var(--color-border-muted, #d8dee4);
298
298
  overflow-x: auto;
299
299
  margin: 8px 0;
300
- background: var(--bgColor-neutral-muted, #afb8c133);
300
+ background: var(--color-background-neutral-muted, #afb8c133);
301
301
  line-height: 1.4;
302
302
  }
303
303
 
@@ -314,7 +314,7 @@
314
314
  .expandedPreview li { margin: 0 0 4px; display: list-item; }
315
315
 
316
316
  .expandedPreview blockquote {
317
- border-left: 4px solid var(--borderColor-default, #d0d7de);
317
+ border-left: 4px solid var(--color-border, #d0d7de);
318
318
  margin: 12px 0;
319
319
  padding: 4px 16px;
320
320
  color: var(--sb--markdown-muted);
@@ -329,19 +329,19 @@
329
329
 
330
330
  .expandedPreview th,
331
331
  .expandedPreview td {
332
- border: 1px solid var(--borderColor-default, #d0d7de);
332
+ border: 1px solid var(--color-border, #d0d7de);
333
333
  padding: 6px 12px;
334
334
  text-align: left;
335
335
  }
336
336
 
337
337
  .expandedPreview th {
338
- background: var(--bgColor-muted, #f6f8fa);
338
+ background: var(--color-background-muted, #f6f8fa);
339
339
  font-weight: 600;
340
340
  }
341
341
 
342
342
  .expandedPreview hr {
343
343
  border: none;
344
- border-top: 1px solid var(--borderColor-default, #d0d7de);
344
+ border-top: 1px solid var(--color-border, #d0d7de);
345
345
  margin: 16px 0;
346
346
  }
347
347
 
@@ -365,7 +365,7 @@
365
365
  height: 100%;
366
366
  min-height: 0;
367
367
  overflow: hidden;
368
- background: var(--bgColor-default, #ffffff);
368
+ background: var(--color-background, #ffffff);
369
369
  }
370
370
 
371
371
  .expandedEditor {
@@ -380,10 +380,10 @@
380
380
  border: none;
381
381
  outline: none;
382
382
  resize: none;
383
- background: var(--bgColor-default, #ffffff);
383
+ background: var(--color-background, #ffffff);
384
384
  font-family: ui-monospace, SFMono-Regular, monospace;
385
385
  font-size: 14px;
386
386
  line-height: 1.6;
387
- color: var(--fgColor-default, #1f2328);
387
+ color: var(--color-foreground, #1f2328);
388
388
  overflow: auto;
389
389
  }
@@ -2,7 +2,6 @@ import { useState, useCallback, useEffect, useRef, forwardRef, useImperativeHand
2
2
  import { readProp, promptSchema } from './widgetProps.js'
3
3
  import { CopilotIcon, SquareFillIcon, CheckCircleIcon, XIcon } from '@primer/octicons-react'
4
4
  import ResizeHandle from './ResizeHandle.jsx'
5
- import { useWebGLSlot, Priority } from '../WebGLContextPool.jsx'
6
5
  import styles from './PromptWidget.module.css'
7
6
 
8
7
  function getBase() {
@@ -122,18 +121,6 @@ const PromptWidget = forwardRef(function PromptWidget({ id, props, onUpdate, res
122
121
  const termDisposedRef = useRef(false)
123
122
  const textareaRef = useRef(null)
124
123
 
125
- // ── WebGL context pool integration ──
126
- // Only request a live slot when the output terminal is actually shown
127
- const { isLive, setPriority } = useWebGLSlot(id)
128
-
129
- useEffect(() => {
130
- if (showOutput && execStatus !== 'idle') {
131
- setPriority(Priority.VISIBLE)
132
- } else {
133
- setPriority(Priority.OFFSCREEN)
134
- }
135
- }, [showOutput, execStatus, setPriority])
136
-
137
124
  const onUpdateRef = useRef(onUpdate)
138
125
  useEffect(() => { onUpdateRef.current = onUpdate }, [onUpdate])
139
126
 
@@ -304,9 +291,8 @@ const PromptWidget = forwardRef(function PromptWidget({ id, props, onUpdate, res
304
291
  }
305
292
  }, [onUpdate])
306
293
 
307
- // Embedded read-only terminal (only created when pool grants a live slot)
294
+ // Embedded read-only terminal created when the prompt has an active session
308
295
  useEffect(() => {
309
- if (!isLive) return
310
296
  if (!showOutput || execStatus === 'idle') return
311
297
  if (!termContainerRef.current) return
312
298
 
@@ -376,7 +362,7 @@ const PromptWidget = forwardRef(function PromptWidget({ id, props, onUpdate, res
376
362
  termRef.current = null
377
363
  wsRef.current = null
378
364
  }
379
- }, [isLive, showOutput, execStatus, id, width, height])
365
+ }, [showOutput, execStatus, id, width, height])
380
366
 
381
367
  const isPending = execStatus === 'pending'
382
368
  const isDone = execStatus === 'done'
@@ -1,12 +1,12 @@
1
1
  /* ── Loom-style prompt widget ── */
2
2
 
3
3
  .wrapper {
4
- background: var(--bgColor-default, #fff);
5
- border: 1px solid var(--borderColor-default, #d0d7de);
4
+ background: var(--color-background, #fff);
5
+ border: 1px solid var(--color-border, #d0d7de);
6
6
  border-radius: 16px;
7
7
  padding: 20px;
8
8
  min-width: 200px;
9
- font-family: var(--fontStack-system, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif);
9
+ font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif);
10
10
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.06);
11
11
  }
12
12
 
@@ -23,24 +23,24 @@
23
23
  width: 32px;
24
24
  height: 32px;
25
25
  border-radius: 50%;
26
- background: var(--bgColor-neutral-muted, #eaeef2);
26
+ background: var(--color-background-neutral-muted, #eaeef2);
27
27
  display: flex;
28
28
  align-items: center;
29
29
  justify-content: center;
30
30
  flex-shrink: 0;
31
- color: var(--fgColor-muted, #636c76);
31
+ color: var(--color-foreground-muted, #636c76);
32
32
  }
33
33
 
34
34
  .avatarDone {
35
- background: var(--bgColor-success-muted, #dafbe1);
36
- color: var(--fgColor-success, #1a7f37);
35
+ background: var(--color-background-success-muted, #dafbe1);
36
+ color: var(--color-foreground-success, #1a7f37);
37
37
  }
38
38
 
39
39
  .textarea {
40
40
  flex: 1;
41
41
  border: none;
42
42
  background: transparent;
43
- color: var(--fgColor-default, #1f2328);
43
+ color: var(--color-foreground, #1f2328);
44
44
  font-size: 14px;
45
45
  font-family: inherit;
46
46
  line-height: 1.5;
@@ -55,7 +55,7 @@
55
55
  }
56
56
 
57
57
  .textarea::placeholder {
58
- color: var(--fgColor-muted, #636c76);
58
+ color: var(--color-foreground-muted, #636c76);
59
59
  }
60
60
 
61
61
  /* ── Toolbar: icons + submit ── */
@@ -72,9 +72,9 @@
72
72
  font-size: 12px;
73
73
  padding: 4px 14px;
74
74
  border-radius: 6px;
75
- border: 1px solid var(--borderColor-default, #d0d7de);
76
- background: var(--bgColor-default, #fff);
77
- color: var(--fgColor-muted, #636c76);
75
+ border: 1px solid var(--color-border, #d0d7de);
76
+ background: var(--color-background, #fff);
77
+ color: var(--color-foreground-muted, #636c76);
78
78
  cursor: pointer;
79
79
  white-space: nowrap;
80
80
  font-weight: 500;
@@ -82,8 +82,8 @@
82
82
  }
83
83
 
84
84
  .submitBtn:hover:not(:disabled) {
85
- background: var(--bgColor-neutral-muted, rgba(175, 184, 193, 0.2));
86
- color: var(--fgColor-default, #1f2328);
85
+ background: var(--color-background-neutral-muted, rgba(175, 184, 193, 0.2));
86
+ color: var(--color-foreground, #1f2328);
87
87
  }
88
88
 
89
89
  .submitBtn:disabled {
@@ -96,9 +96,9 @@
96
96
  .errorText {
97
97
  margin: 0 0 12px;
98
98
  padding: 6px 10px;
99
- background: var(--bgColor-danger-muted, #ffebe9);
99
+ background: var(--color-background-danger-muted, #ffebe9);
100
100
  border-radius: 8px;
101
- color: var(--fgColor-danger, #cf222e);
101
+ color: var(--color-foreground-danger, #cf222e);
102
102
  font-size: 12px;
103
103
  line-height: 1.4;
104
104
  overflow: hidden;
@@ -122,7 +122,7 @@
122
122
 
123
123
  .pendingText {
124
124
  font-size: 14px;
125
- color: var(--fgColor-default, #1f2328);
125
+ color: var(--color-foreground, #1f2328);
126
126
  line-height: 1.5;
127
127
  margin: 0;
128
128
  padding-top: 4px;
@@ -136,7 +136,7 @@
136
136
 
137
137
  .pendingHint {
138
138
  font-size: 12px;
139
- color: var(--fgColor-muted, #636c76);
139
+ color: var(--color-foreground-muted, #636c76);
140
140
  margin: 0;
141
141
  }
142
142
 
@@ -149,7 +149,7 @@
149
149
  border: none;
150
150
  border-radius: 6px;
151
151
  cursor: pointer;
152
- color: var(--fgColor-accent, #0969da);
152
+ color: var(--color-foreground-accent, #0969da);
153
153
  transition: background 0.15s;
154
154
  position: relative;
155
155
  width: 24px;
@@ -157,18 +157,18 @@
157
157
  }
158
158
 
159
159
  .cancelBtn:hover {
160
- background: var(--bgColor-neutral-muted, rgba(175, 184, 193, 0.2));
160
+ background: var(--color-background-neutral-muted, rgba(175, 184, 193, 0.2));
161
161
  }
162
162
 
163
163
  .spinnerSvg {
164
164
  animation: spin 1s linear infinite;
165
- color: var(--fgColor-accent, #0969da);
165
+ color: var(--color-foreground-accent, #0969da);
166
166
  }
167
167
 
168
168
  .stopIcon {
169
169
  display: none;
170
170
  position: absolute;
171
- color: var(--fgColor-danger, #cf222e);
171
+ color: var(--color-foreground-danger, #cf222e);
172
172
  }
173
173
 
174
174
  .cancelBtn:hover .spinnerSvg {
@@ -200,7 +200,7 @@
200
200
 
201
201
  .doneText {
202
202
  font-size: 14px;
203
- color: var(--fgColor-default, #1f2328);
203
+ color: var(--color-foreground, #1f2328);
204
204
  line-height: 1.5;
205
205
  margin: 0;
206
206
  padding-top: 4px;
@@ -210,8 +210,8 @@
210
210
  align-self: flex-end;
211
211
  padding: 4px 14px;
212
212
  background: transparent;
213
- color: var(--fgColor-accent, #0969da);
214
- border: 1px solid var(--borderColor-default, #d0d7de);
213
+ color: var(--color-foreground-accent, #0969da);
214
+ border: 1px solid var(--color-border, #d0d7de);
215
215
  border-radius: 6px;
216
216
  font-size: 12px;
217
217
  cursor: pointer;
@@ -219,14 +219,14 @@
219
219
  }
220
220
 
221
221
  .resetBtn:hover {
222
- background: var(--bgColor-muted, #f6f8fa);
222
+ background: var(--color-background-muted, #f6f8fa);
223
223
  }
224
224
 
225
225
  /* ── Inline terminal output (flush to bottom edge) ── */
226
226
 
227
227
  .terminalArea {
228
228
  margin: 16px -20px -20px;
229
- border-top: 1px solid var(--borderColor-muted, #d8dee4);
229
+ border-top: 1px solid var(--color-border-muted, #d8dee4);
230
230
  overflow: hidden;
231
231
  border-radius: 0 0 16px 16px;
232
232
  }
@@ -236,14 +236,14 @@
236
236
  align-items: center;
237
237
  justify-content: space-between;
238
238
  padding: 4px 10px;
239
- background: var(--bgColor-muted, #f6f8fa);
240
- border-bottom: 1px solid var(--borderColor-muted, #d8dee4);
239
+ background: var(--color-background-muted, #f6f8fa);
240
+ border-bottom: 1px solid var(--color-border-muted, #d8dee4);
241
241
  }
242
242
 
243
243
  .terminalLabel {
244
244
  font-size: 11px;
245
245
  font-weight: 600;
246
- color: var(--fgColor-muted, #636c76);
246
+ color: var(--color-foreground-muted, #636c76);
247
247
  text-transform: uppercase;
248
248
  letter-spacing: 0.5px;
249
249
  }
@@ -257,13 +257,13 @@
257
257
  border: none;
258
258
  border-radius: 4px;
259
259
  cursor: pointer;
260
- color: var(--fgColor-muted, #636c76);
260
+ color: var(--color-foreground-muted, #636c76);
261
261
  transition: background 0.15s;
262
262
  }
263
263
 
264
264
  .terminalClose:hover {
265
- background: var(--bgColor-neutral-muted, rgba(175, 184, 193, 0.2));
266
- color: var(--fgColor-default, #1f2328);
265
+ background: var(--color-background-neutral-muted, rgba(175, 184, 193, 0.2));
266
+ color: var(--color-foreground, #1f2328);
267
267
  }
268
268
 
269
269
  .terminalContainer {
@@ -7,6 +7,7 @@ import { readProp, prototypeEmbedSchema } from './widgetProps.js'
7
7
  import { getEmbedChromeVars } from './embedTheme.js'
8
8
  import { useIframeDevLogs } from './iframeDevLogs.js'
9
9
  import { findAllConnectedSplitTargets, getSplitPaneLabel, buildPaneForWidget, buildSplitLayout } from './expandUtils.js'
10
+ import { normalizeLegacyEmbedSrc } from './normalizeLegacyEmbedSrc.js'
10
11
  import ExpandedPane from './ExpandedPane.jsx'
11
12
  import styles from './PrototypeEmbed.module.css'
12
13
  import overlayStyles from './embedOverlay.module.css'
@@ -46,27 +47,87 @@ function resolveCanvasThemeFromStorage() {
46
47
  const HEADER_HEIGHT = 37
47
48
 
48
49
  export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdate, resizable }, ref) {
49
- const src = readProp(props, 'src', prototypeEmbedSchema)
50
+ const rawStoredSrc = readProp(props, 'src', prototypeEmbedSchema)
51
+ const src = normalizeLegacyEmbedSrc(rawStoredSrc)
50
52
  const width = readProp(props, 'width', prototypeEmbedSchema) || 800
51
53
  const height = readProp(props, 'height', prototypeEmbedSchema) || 600
52
54
  const zoom = readProp(props, 'zoom', prototypeEmbedSchema) || 100
53
55
  const label = readProp(props, 'label', prototypeEmbedSchema) || src
54
56
 
57
+ // Self-healing write-back: if normalization changed the src, persist the
58
+ // clean form so the canvas JSONL stops carrying the broken URL. Without
59
+ // this, every render keeps recovering at runtime but the file stays
60
+ // poisoned and any consumer on an older library still sees the bug.
61
+ useEffect(() => {
62
+ if (!onUpdate) return
63
+ if (typeof rawStoredSrc !== 'string' || rawStoredSrc === src) return
64
+ onUpdate({ src })
65
+ }, [rawStoredSrc, src, onUpdate])
66
+
55
67
  const basePath = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
56
68
  const baseSegment = basePath.replace(/^\//, '')
57
- const rawSrc = useMemo(() => {
58
- if (!src) return ''
59
- if (/^https?:\/\//.test(src)) return src
69
+ // Two URLs are derived from `src`:
70
+ // - rawSrc the iframe URL. In DEV this routes through the isolated
71
+ // prototypes.html entry so a broken prototype's transform/HMR errors
72
+ // stay inside the iframe (see .agents/plans/vite-isolation.md):
73
+ // /MyProto/SignupForm becomes prototypes.html/MyProto/SignupForm.
74
+ // Routing happens in the *pathname* (createBrowserRouter), not in the
75
+ // hash, so the hash and search stay free for storyboard URL state
76
+ // (`?flow=...` and `#key=value` overrides). In PROD it loads the
77
+ // prototype path directly through the canvas SPA (prototypes.html is
78
+ // a build-time isolation artifact that must not leak into deployed URLs).
79
+ // - externalSrc — the URL used by "Open in new tab". Always direct
80
+ // (`${basePath}/<protoPath>`), never prototypes.html, even in dev —
81
+ // opening prototypes.html/... in a fresh tab is a leaky surprise
82
+ // for users navigating from the canvas.
83
+ // External http(s) URLs are left alone in both cases. basePath already
84
+ // carries the /branch--xxx/ prefix on branch deploys, so both work for
85
+ // main and branch deploys alike.
86
+ const { rawSrc, externalSrc } = useMemo(() => {
87
+ if (!src) return { rawSrc: '', externalSrc: '' }
88
+ if (/^https?:\/\//.test(src)) return { rawSrc: src, externalSrc: src }
60
89
  const cleaned = src.replace(/^\/branch--[^/]+/, '')
61
- if (baseSegment && cleaned.startsWith(basePath)) return cleaned
62
- if (baseSegment && cleaned.startsWith(baseSegment)) return `/${cleaned}`
63
- return `${basePath}${cleaned}`
90
+ let normalized
91
+ if (baseSegment && cleaned.startsWith(basePath)) normalized = cleaned
92
+ else if (baseSegment && cleaned.startsWith(baseSegment)) normalized = `/${cleaned}`
93
+ else normalized = `${basePath}${cleaned}`
94
+ // Strip basePath so we can split path/query/hash cleanly and
95
+ // re-anchor for whichever mode we're in. Any pre-existing #hash on
96
+ // the original src is preserved.
97
+ const withoutBase = baseSegment && normalized.startsWith(basePath)
98
+ ? normalized.slice(basePath.length) || '/'
99
+ : normalized
100
+ const hashIdx = withoutBase.indexOf('#')
101
+ const innerHash = hashIdx >= 0 ? withoutBase.slice(hashIdx + 1) : ''
102
+ const pathAndQuery = hashIdx >= 0 ? withoutBase.slice(0, hashIdx) : withoutBase
103
+ const routePath = pathAndQuery.startsWith('/') ? pathAndQuery : `/${pathAndQuery}`
104
+ const suffix = innerHash ? `#${innerHash}` : ''
105
+ // Direct path through the canvas SPA — used in prod for the iframe and
106
+ // always for "Open in new tab".
107
+ const directUrl = `${basePath}${routePath}${suffix}`
108
+ if (import.meta.env.PROD) {
109
+ return { rawSrc: directUrl, externalSrc: directUrl }
110
+ }
111
+ // Dev iframe: prototypes.html with the route as a sub-path. The library
112
+ // middleware (`data-plugin.js`) serves the same HTML shell for
113
+ // `/prototypes.html/*` so a hard reload / open-in-new-tab works. The
114
+ // prototype name (first path segment) narrows getRoutesForProto so a
115
+ // broken sibling prototype can't poison the lazy() chain. Hash and
116
+ // search stay attached so storyboard URL state (`#key=value` overrides,
117
+ // `?flow=name`) survives the redirect through the isolation entry.
118
+ const iframeUrl = `${basePath}/prototypes.html${routePath}${suffix}`
119
+ return { rawSrc: iframeUrl, externalSrc: directUrl }
64
120
  }, [src, basePath, baseSegment])
65
121
 
66
122
  const scale = zoom / 100
67
123
 
68
124
  const [editing, setEditing] = useState(false)
69
125
  const [interactive, setInteractive] = useState(false)
126
+ // Track which src URL has fired its onLoad callback so we can overlay a
127
+ // spinner while a new iframe is fetching. Stored as the URL itself (not
128
+ // a boolean) so changing src naturally invalidates the loaded state
129
+ // without an effect.
130
+ const [loadedSrc, setLoadedSrc] = useState(null)
70
131
  const [expandOverride, setExpandOverride, clearExpandOverride] = useOverride(`_prototype_expanded_${widgetId}`)
71
132
  const expandMode = expandOverride === 'immersive' ? 'immersive' : expandOverride === 'split' ? 'split' : expandOverride === 'single' ? 'single' : null
72
133
  const setExpandMode = useCallback((mode) => {
@@ -268,7 +329,10 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
268
329
  function handleMessage(e) {
269
330
  if (e.source !== iframeRef.current?.contentWindow) return
270
331
  if (e.data?.type !== 'storyboard:embed:navigate') return
271
- const newSrc = e.data.src
332
+ // Defensive: older consumer embeds reported `/prototypes.html#/route`
333
+ // here (loader path + hash). Normalize to the inner route so we never
334
+ // persist the broken form.
335
+ const newSrc = normalizeLegacyEmbedSrc(e.data.src)
272
336
  if (newSrc && newSrc !== src) {
273
337
  const originalSrc = readProp(props, 'originalSrc', prototypeEmbedSchema)
274
338
  onUpdate?.({ src: newSrc, originalSrc: originalSrc || src })
@@ -295,7 +359,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
295
359
  } else if (actionId === 'split-screen') {
296
360
  setExpandMode('split')
297
361
  } else if (actionId === 'open-external') {
298
- if (rawSrc) window.open(rawSrc, '_blank', 'noopener')
362
+ if (externalSrc) window.open(externalSrc, '_blank', 'noopener')
299
363
  } else if (actionId === 'refresh-frame') {
300
364
  const iframe = iframeRef.current
301
365
  if (iframe) {
@@ -310,7 +374,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
310
374
  onUpdate?.({ zoom: Math.max(25, zoom - step) })
311
375
  }
312
376
  },
313
- }), [rawSrc, zoom, onUpdate])
377
+ }), [externalSrc, zoom, onUpdate])
314
378
 
315
379
  function handlePickRoute(route) {
316
380
  onUpdate?.({ src: route })
@@ -340,12 +404,18 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
340
404
  <WidgetWrapper>
341
405
  <div
342
406
  ref={embedRef}
343
- className={styles.embed}
344
- style={{ width, height, ...chromeVars }}
407
+ className="relative overflow-hidden rounded-lg bg-background shadow-[0_4px_16px_rgba(0,0,0,0.08)]"
408
+ style={{
409
+ width,
410
+ height,
411
+ ...chromeVars,
412
+ }}
345
413
  >
346
- <div className={styles.header}>
347
- <span className={styles.headerIcon}><CollageFrameIcon size={16} /></span>
348
- <span className={styles.headerTitle}>{prototypeTitle}</span>
414
+ <div
415
+ 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"
416
+ >
417
+ <span className="inline-flex flex-shrink-0"><CollageFrameIcon size={16} /></span>
418
+ <span className="overflow-hidden text-ellipsis">{prototypeTitle}</span>
349
419
  </div>
350
420
  {editing ? (
351
421
  <div
@@ -420,9 +490,14 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
420
490
  }}
421
491
  title={`${prototypeTitle} prototype`}
422
492
  sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
423
- onLoad={(e) => e.target.blur()}
493
+ onLoad={(e) => { e.target.blur(); setLoadedSrc(effectiveSrc) }}
424
494
  />
425
- </div>
495
+ {loadedSrc !== effectiveSrc && (
496
+ <div className={styles.placeholder} aria-hidden="true">
497
+ <div className={styles.spinner} />
498
+ </div>
499
+ )}
500
+ </div>
426
501
  {!interactive && !expanded && (
427
502
  <div
428
503
  className={overlayStyles.interactOverlay}