@dfosco/storyboard 0.7.0 → 0.8.1
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.
- package/assets/favicon.svg +12 -0
- package/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +13907 -10816
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +138 -127
- package/scaffold/AGENTS.md +9 -7
- package/scaffold/README.md +18 -0
- package/scaffold/deploy.yml +21 -0
- package/scaffold/gitignore +8 -14
- package/scaffold/preview.yml +40 -0
- package/scaffold/{manifest.json → scaffold.config.json} +73 -3
- package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
- package/scaffold/skills/canvas/SKILL.md +1 -1
- package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
- package/scaffold/skills/ship/SKILL.md +2 -2
- package/scaffold/skills/worktree/SKILL.md +1 -1
- package/scaffold/src/_prototype.jsx +17 -0
- package/scaffold/src/_story.jsx +13 -0
- package/scaffold/src/library/README.md +34 -0
- package/scaffold/src/library/_app.jsx +53 -0
- package/scaffold/src/library/_app.module.css +8 -0
- package/scaffold/src/library/create.jsx +10 -0
- package/scaffold/src/library/index.jsx +14 -0
- package/scaffold/src/library/mount.jsx +52 -0
- package/scaffold/src/library/prototypes-entry.jsx +57 -0
- package/scaffold/src/library/routes.jsx +205 -0
- package/scaffold/src/library/viewfinder.jsx +13 -0
- package/scaffold/src/library/workspace.jsx +17 -0
- package/scripts/aggregate-artifacts.mjs +257 -0
- package/src/core/artifact/operations.js +19 -13
- package/src/core/autosync/server.test.js +1 -1
- package/src/core/canvas/githubEmbeds.js +12 -0
- package/src/core/canvas/githubEmbeds.test.js +71 -2
- package/src/core/canvas/hot-pool.js +10 -11
- package/src/core/canvas/materializer.js +39 -13
- package/src/core/canvas/server.js +324 -69
- package/src/core/canvas/server.test.js +365 -2
- package/src/core/canvas/terminal-server.js +144 -57
- package/src/core/canvas/undoRedo.js +174 -0
- package/src/core/canvas/undoRedo.test.js +250 -0
- package/src/core/cli/buildManifest.js +34 -0
- package/src/core/cli/canvasRedo.js +44 -0
- package/src/core/cli/canvasUndo.js +40 -0
- package/src/core/cli/create.js +116 -18
- package/src/core/cli/dev.js +23 -35
- package/src/core/cli/index.js +21 -0
- package/src/core/cli/schemas.js +2 -3
- package/src/core/cli/serverUrl.js +24 -0
- package/src/core/cli/setup.js +14 -25
- package/src/core/cli/terminal-welcome.js +12 -6
- package/src/core/cli/updateVersion.js +42 -2
- package/src/core/cli/workleaf.js +140 -0
- package/src/core/comments/api.js +1 -1
- package/src/core/comments/queries.js +3 -0
- package/src/core/comments/ui/CommentWindow.jsx +3 -2
- package/src/core/data/artifactIndex.js +235 -0
- package/src/core/data/artifactIndex.test.js +203 -0
- package/src/core/data/artifactManifest.js +290 -0
- package/src/core/data/artifactManifest.test.js +207 -0
- package/src/core/data/viewfinder.js +47 -7
- package/src/core/index.js +6 -1
- package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
- package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
- package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
- package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
- package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
- package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
- package/src/core/lib/components/ui/dialog/index.js +4 -34
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
- package/src/core/lib/components/ui/label/label.jsx +1 -2
- package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
- package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
- package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
- package/src/core/lib/components/ui/panel/panel.jsx +11 -2
- package/src/core/lib/components/ui/separator/separator.jsx +6 -4
- package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
- package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
- package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
- package/src/core/mountStoryboardCore.js +234 -74
- package/src/core/mountStoryboardCore.test.js +174 -0
- package/src/core/rename-watcher/watcher.js +329 -36
- package/src/core/rename-watcher/watcher.test.js +253 -0
- package/src/core/scaffold/fragments.js +287 -0
- package/src/core/scaffold/fragments.test.js +401 -0
- package/src/core/scaffold/index.js +291 -0
- package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
- package/src/core/scaffold/scaffold-integration.test.js +247 -0
- package/src/core/scaffold.js +4 -97
- package/src/core/stores/configSchema.js +41 -2
- package/src/core/stores/recentArtifacts.js +49 -2
- package/src/core/stores/recentArtifacts.test.js +59 -6
- package/src/core/stores/themeBootstrap.js +264 -0
- package/src/core/stores/themeBootstrap.test.js +211 -0
- package/src/core/stores/themeStore.test.js +154 -0
- package/src/core/stores/themeStore.ts +235 -220
- package/src/core/styles/generateThemesCss.js +63 -0
- package/src/core/styles/tailwind.css +12 -4
- package/src/core/styles/workspace-chrome.css +30 -0
- package/src/core/tools/handlers/paletteTheme.js +11 -8
- package/src/core/ui/ActionMenuButton.jsx +2 -1
- package/src/core/ui/CanvasCreateMenu.jsx +1 -19
- package/src/core/ui/CoreUIBar.jsx +10 -7
- package/src/core/ui/ThemeMenuButton.jsx +45 -30
- package/src/core/vite/server-plugin.js +36 -0
- package/src/core/workleaf/cleanup.js +102 -0
- package/src/core/workleaf/create.js +199 -0
- package/src/core/workleaf/delete.js +101 -0
- package/src/core/workleaf/list.js +57 -0
- package/src/core/workleaf/merge.js +238 -0
- package/src/core/workleaf/paths.js +146 -0
- package/src/core/workleaf/paths.test.js +230 -0
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +28 -21
- package/src/core/workshop/features/createFlow/server.js +5 -1
- package/src/core/workshop/features/createPrototype/server.js +5 -1
- package/src/core/workshop/features/templateIndex.js +28 -1
- package/src/core/worktree/port.js +48 -5
- package/src/core/worktree/port.test.js +42 -0
- package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
- package/src/internals/ArtifactForm/ArtifactForm.module.css +48 -66
- package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
- package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
- package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +25 -19
- package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
- package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
- package/src/internals/CommandPalette/createHelpers.js +30 -0
- package/src/internals/CreatePage/CreatePage.jsx +57 -0
- package/src/internals/CreatePage/CreatePage.module.css +34 -0
- package/src/internals/Dialog.jsx +51 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +533 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
- package/src/internals/Tooltip.jsx +39 -0
- package/src/internals/Viewfinder.jsx +196 -48
- package/src/internals/Viewfinder.module.css +72 -1
- package/src/internals/canvas/CanvasPage.jsx +267 -124
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
- package/src/internals/canvas/PageSelector.jsx +15 -1
- package/src/internals/canvas/PageSelector.module.css +14 -0
- package/src/internals/canvas/canvasApi.js +40 -5
- package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
- package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
- package/src/internals/canvas/isolation/prototypeRoutes.jsx +131 -0
- package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
- package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
- package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
- package/src/internals/canvas/useUndoRedo.js +75 -60
- package/src/internals/canvas/useUndoRedo.test.js +46 -178
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
- package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
- package/src/internals/canvas/widgets/LinkPreview.jsx +45 -1
- package/src/internals/canvas/widgets/LinkPreview.module.css +71 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +69 -10
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +114 -0
- package/src/internals/canvas/widgets/StorySetWidget.jsx +8 -4
- package/src/internals/canvas/widgets/StoryWidget.jsx +21 -38
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
- package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
- package/src/internals/canvas/widgets/WidgetChrome.module.css +15 -0
- package/src/internals/canvas/widgets/embedTheme.js +2 -2
- package/src/internals/canvas/widgets/expandUtils.js +19 -19
- package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
- package/src/internals/context.jsx +22 -1
- package/src/internals/context.test.jsx +47 -0
- package/src/internals/hooks/useArtifactIndex.js +31 -0
- package/src/internals/hooks/useArtifactIndex.test.js +45 -0
- package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
- package/src/internals/index.js +6 -0
- package/src/internals/loadConsumerWrapper.js +60 -0
- package/src/internals/story/ComponentSetPage.jsx +75 -34
- package/src/internals/story/StoryPage.jsx +16 -25
- package/src/internals/vite/data-plugin.js +313 -148
- package/src/internals/vite/data-plugin.test.js +11 -11
- package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
- package/terminal.config.json +1 -1
- package/src/core/lib/components/ui/card/card-action.jsx +0 -11
- package/src/core/lib/components/ui/card/card-content.jsx +0 -11
- package/src/core/lib/components/ui/card/card-description.jsx +0 -11
- package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
- package/src/core/lib/components/ui/card/card-header.jsx +0 -19
- package/src/core/lib/components/ui/card/card-title.jsx +0 -11
- package/src/core/lib/components/ui/card/card.jsx +0 -17
- package/src/core/lib/components/ui/card/index.js +0 -25
- package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/index.js +0 -13
- package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
- package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
- package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
- package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
- package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
- package/src/core/lib/components/ui/popover/index.js +0 -28
- package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
- package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
- package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
- package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
- package/src/core/lib/components/ui/popover/popover.jsx +0 -4
- package/src/core/lib/components/ui/select/index.js +0 -37
- package/src/core/lib/components/ui/select/select-content.jsx +0 -30
- package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
- package/src/core/lib/components/ui/select/select-group.jsx +0 -15
- package/src/core/lib/components/ui/select/select-item.jsx +0 -26
- package/src/core/lib/components/ui/select/select-label.jsx +0 -11
- package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
- package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
- package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
- package/src/core/lib/components/ui/select/select.jsx +0 -4
- package/src/core/lib/components/ui/sheet/index.js +0 -34
- package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
- package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
- package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
- package/src/core/lib/components/ui/toggle/index.js +0 -8
- package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
- package/src/core/lib/components/ui/toggle-group/index.js +0 -10
- package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
- package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
- package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
- package/src/internals/canvas/componentIsolate.jsx +0 -144
- package/src/internals/canvas/componentSetIsolate.jsx +0 -300
- package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
- package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
- package/src/primer/ThemeSync.jsx +0 -73
- package/src/primer/index.js +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-heal canvases that persisted a dev-isolation loader URL as the prototype
|
|
3
|
+
* widget src. Three legacy shapes exist (all caused by older bugs in the embed
|
|
4
|
+
* iframe broadcaster — see commit history of PrototypeEmbed.jsx + mountStoryboardCore.js):
|
|
5
|
+
*
|
|
6
|
+
* 1. `/prototypes.html#/MyProto?...`
|
|
7
|
+
* — 0.6.13 and older, when the iframe broadcast pathname+hash and
|
|
8
|
+
* routing lived in the hash. Inner is a clean path.
|
|
9
|
+
*
|
|
10
|
+
* 2. `/prototypes.html#%2FMyProto=&cqOpen=true&...`
|
|
11
|
+
* — 0.6.13/0.6.14 with storyboard URL state. The iframe's hash was
|
|
12
|
+
* `#/MyProto` (router route), then storyboard's session.writeHash
|
|
13
|
+
* did `URLSearchParams.toString()` on the entire hash, which
|
|
14
|
+
* URL-encoded `/` to `%2F` and turned `/MyProto` into a key with
|
|
15
|
+
* an empty value. This is the most common shape seen in the wild.
|
|
16
|
+
*
|
|
17
|
+
* 3. `/prototypes.html/MyProto...`
|
|
18
|
+
* — defensive: if a future broadcaster ever forwards the loader
|
|
19
|
+
* path verbatim with the path-based router layout (0.6.15+).
|
|
20
|
+
*
|
|
21
|
+
* Without normalization the URL builder in PrototypeEmbed treats
|
|
22
|
+
* "prototypes.html" as the prototype name and produces a blank iframe — and
|
|
23
|
+
* cmd+D duplicates inherit the broken src.
|
|
24
|
+
*
|
|
25
|
+
* Pure, deterministic, safe to call on every render. The PrototypeEmbed
|
|
26
|
+
* component also writes the normalized form back to the canvas via onUpdate
|
|
27
|
+
* so the persisted JSONL stops carrying the broken URL forever.
|
|
28
|
+
*
|
|
29
|
+
* @param {unknown} src
|
|
30
|
+
* @returns {unknown} normalized src, or the original value when not a string
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeLegacyEmbedSrc(src) {
|
|
33
|
+
if (typeof src !== 'string' || !src) return src
|
|
34
|
+
// Hash-form (legacy 0.6.13 router-in-hash). Match both clean and
|
|
35
|
+
// URLSearchParams-mangled inner contents.
|
|
36
|
+
const hashMatch = src.match(/^\/?(?:[^#]*\/)?prototypes\.html#(.*)$/)
|
|
37
|
+
if (hashMatch) {
|
|
38
|
+
const inner = hashMatch[1] || ''
|
|
39
|
+
if (!inner) return '/'
|
|
40
|
+
// Clean form: `#/MyProto?...` — inner already starts with `/`.
|
|
41
|
+
if (inner.startsWith('/')) return inner
|
|
42
|
+
// URLSearchParams-mangled form: parse and find the key whose decoded
|
|
43
|
+
// name starts with `/` (that's the route). Surviving params become
|
|
44
|
+
// the storyboard URL state on the recovered hash.
|
|
45
|
+
try {
|
|
46
|
+
const params = new URLSearchParams(inner)
|
|
47
|
+
let route = ''
|
|
48
|
+
const restPairs = []
|
|
49
|
+
for (const [key, value] of params) {
|
|
50
|
+
if (!route && key.startsWith('/')) {
|
|
51
|
+
// Storyboard never persists state with `/`-prefixed keys, and the
|
|
52
|
+
// mangled form always puts the route first — so first match wins.
|
|
53
|
+
// The route had no `=` in the original URL, so value is empty.
|
|
54
|
+
// If a `?query=...` survived alongside, append it back.
|
|
55
|
+
route = value ? `${key}?${key}=${value}` : key
|
|
56
|
+
} else {
|
|
57
|
+
restPairs.push(`${key}=${value}`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (route) {
|
|
61
|
+
return restPairs.length > 0 ? `${route}#${restPairs.join('&')}` : route
|
|
62
|
+
}
|
|
63
|
+
} catch { /* fall through */ }
|
|
64
|
+
// Last-resort: prefix `/` so we at least produce a routable path.
|
|
65
|
+
return `/${inner}`
|
|
66
|
+
}
|
|
67
|
+
// Path-form: `/prototypes.html/inner...` — defensive.
|
|
68
|
+
const pathMatch = src.match(/^\/?(?:[^#?]*\/)?prototypes\.html(\/[^]*)$/)
|
|
69
|
+
if (pathMatch) {
|
|
70
|
+
const inner = pathMatch[1] || ''
|
|
71
|
+
return inner || '/'
|
|
72
|
+
}
|
|
73
|
+
return src
|
|
74
|
+
}
|
|
@@ -70,6 +70,7 @@ function getCanvasGroupMap() {
|
|
|
70
70
|
name,
|
|
71
71
|
route,
|
|
72
72
|
title: data?.title || name.split('/').pop(),
|
|
73
|
+
isPrivate: !!data?._isPrivate,
|
|
73
74
|
_canvasMeta: data?._canvasMeta || null,
|
|
74
75
|
})
|
|
75
76
|
}
|
|
@@ -131,6 +132,14 @@ function matchStoryRoute(pathname) {
|
|
|
131
132
|
* Strip the app's sub-path prefix (e.g. /storyboard) from the pathname.
|
|
132
133
|
* React Router's basename strips the branch prefix but not the app name prefix
|
|
133
134
|
* when the app runs under a nested base path.
|
|
135
|
+
*
|
|
136
|
+
* Two prefixing scenarios are handled:
|
|
137
|
+
* 1. Branch deploys — BASE_URL is `/branch--xxx/{devDomain}/`, so we strip
|
|
138
|
+
* whatever app sub-path remains after the branch prefix.
|
|
139
|
+
* 2. Local dev via Caddy proxy — BASE_URL is `/` (Caddy strips `/{devDomain}/`
|
|
140
|
+
* before forwarding to the dev server), but window.location.pathname is
|
|
141
|
+
* still `/{devDomain}/...`. React Router's basename `/` doesn't strip it,
|
|
142
|
+
* so we rely on the injected `__SB_DEV_DOMAIN__` global instead.
|
|
134
143
|
*/
|
|
135
144
|
function stripBasePath(pathname) {
|
|
136
145
|
let p = pathname.replace(/\/+$/, '') || '/'
|
|
@@ -145,6 +154,18 @@ function stripBasePath(pathname) {
|
|
|
145
154
|
p = p.slice(subPath.length) || '/'
|
|
146
155
|
}
|
|
147
156
|
}
|
|
157
|
+
// Local dev via Caddy proxy: window.location.pathname includes the
|
|
158
|
+
// /{devDomain}/ prefix that Caddy strips before reaching the dev server.
|
|
159
|
+
// BASE_URL is `/` in this scenario, so the block above is a no-op.
|
|
160
|
+
if (typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ && window.__SB_DEV_DOMAIN__) {
|
|
161
|
+
const devDomain = String(window.__SB_DEV_DOMAIN__).replace(/^\/+|\/+$/g, '')
|
|
162
|
+
if (devDomain) {
|
|
163
|
+
const devPrefix = '/' + devDomain
|
|
164
|
+
if (p === devPrefix || p.startsWith(devPrefix + '/')) {
|
|
165
|
+
p = p.slice(devPrefix.length) || '/'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
148
169
|
return p
|
|
149
170
|
}
|
|
150
171
|
|
|
@@ -428,7 +449,7 @@ function StoryboardProviderInner({ flowName, sceneName, recordName, recordParam,
|
|
|
428
449
|
const _hmrTick = canvasIndexKey
|
|
429
450
|
const siblingPages = group
|
|
430
451
|
? getCanvasGroupMap().get(group) || []
|
|
431
|
-
: [{ name: canvasId, route: canvasData?._route || `/canvas/${canvasId}`, title: canvasData?.title || canvasId.split('/').pop() }]
|
|
452
|
+
: [{ name: canvasId, route: canvasData?._route || `/canvas/${canvasId}`, title: canvasData?.title || canvasId.split('/').pop(), isPrivate: !!canvasData?._isPrivate }]
|
|
432
453
|
const canvasMeta = canvasData?._canvasMeta || null
|
|
433
454
|
const canvasValue = {
|
|
434
455
|
data: null,
|
|
@@ -293,4 +293,51 @@ describe('StoryboardProvider', () => {
|
|
|
293
293
|
expect(screen.getByText('Canvas not found')).toBeInTheDocument()
|
|
294
294
|
expect(screen.getByRole('link', { name: /go to index page/i })).toHaveAttribute('href', '/')
|
|
295
295
|
})
|
|
296
|
+
|
|
297
|
+
it('strips dev-domain prefix in local dev (Caddy proxy) so canvas routes resolve', () => {
|
|
298
|
+
// Local dev via Caddy: window.location.pathname includes the /{devDomain}/
|
|
299
|
+
// prefix even though BASE_URL is `/` (the proxy strips it before forwarding
|
|
300
|
+
// to the dev server). Without stripping, isCanvasPath returns false and the
|
|
301
|
+
// 404 never renders — the canvas page just stays blank.
|
|
302
|
+
const origLocalDev = window.__SB_LOCAL_DEV__
|
|
303
|
+
const origDomain = window.__SB_DEV_DOMAIN__
|
|
304
|
+
window.__SB_LOCAL_DEV__ = true
|
|
305
|
+
window.__SB_DEV_DOMAIN__ = 'storyboard'
|
|
306
|
+
try {
|
|
307
|
+
mockUseLocation.mockReturnValue({ pathname: '/storyboard/canvas/unknown-board', search: '', hash: '' })
|
|
308
|
+
|
|
309
|
+
render(
|
|
310
|
+
<StoryboardProvider>
|
|
311
|
+
<ContextReader />
|
|
312
|
+
</StoryboardProvider>,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
expect(screen.getByText('Canvas not found')).toBeInTheDocument()
|
|
316
|
+
} finally {
|
|
317
|
+
window.__SB_LOCAL_DEV__ = origLocalDev
|
|
318
|
+
window.__SB_DEV_DOMAIN__ = origDomain
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('does not strip dev-domain prefix when __SB_LOCAL_DEV__ is not set', () => {
|
|
323
|
+
// Same URL shape, but production-like: prefix should be left alone so it
|
|
324
|
+
// doesn't accidentally match canvas routes for unrelated path segments.
|
|
325
|
+
const origDomain = window.__SB_DEV_DOMAIN__
|
|
326
|
+
window.__SB_DEV_DOMAIN__ = 'storyboard'
|
|
327
|
+
try {
|
|
328
|
+
mockUseLocation.mockReturnValue({ pathname: '/storyboard/canvas/unknown-board', search: '', hash: '' })
|
|
329
|
+
|
|
330
|
+
render(
|
|
331
|
+
<StoryboardProvider>
|
|
332
|
+
<ContextReader />
|
|
333
|
+
</StoryboardProvider>,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
// isCanvasPath sees `/storyboard/canvas/...` → does NOT start with `/canvas/`
|
|
337
|
+
// → no 404, just renders flow context
|
|
338
|
+
expect(screen.queryByText('Canvas not found')).not.toBeInTheDocument()
|
|
339
|
+
} finally {
|
|
340
|
+
window.__SB_DEV_DOMAIN__ = origDomain
|
|
341
|
+
}
|
|
342
|
+
})
|
|
296
343
|
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import { fetchArtifactIndex } from '../../core/data/artifactIndex.js'
|
|
3
|
+
|
|
4
|
+
function isLocalDev() {
|
|
5
|
+
return typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ === true
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useArtifactIndex(basePath) {
|
|
9
|
+
const normalizedBasePath = useMemo(() => basePath || '/', [basePath])
|
|
10
|
+
const localDev = isLocalDev()
|
|
11
|
+
const [state, setState] = useState(() => ({ artifactIndex: null, loading: !localDev }))
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (localDev) return undefined
|
|
15
|
+
|
|
16
|
+
let cancelled = false
|
|
17
|
+
|
|
18
|
+
fetchArtifactIndex(normalizedBasePath)
|
|
19
|
+
.then(index => {
|
|
20
|
+
if (!cancelled) setState({ artifactIndex: index, loading: false })
|
|
21
|
+
})
|
|
22
|
+
.catch(() => {
|
|
23
|
+
if (!cancelled) setState({ artifactIndex: null, loading: false })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return () => { cancelled = true }
|
|
27
|
+
}, [localDev, normalizedBasePath])
|
|
28
|
+
|
|
29
|
+
if (localDev) return { artifactIndex: null, loading: false }
|
|
30
|
+
return state
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react'
|
|
2
|
+
import { useArtifactIndex } from './useArtifactIndex.js'
|
|
3
|
+
|
|
4
|
+
describe('useArtifactIndex', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
delete window.__SB_LOCAL_DEV__
|
|
7
|
+
vi.unstubAllGlobals()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('short-circuits in local dev without fetching', () => {
|
|
11
|
+
window.__SB_LOCAL_DEV__ = true
|
|
12
|
+
const fetchMock = vi.fn()
|
|
13
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
14
|
+
|
|
15
|
+
const { result } = renderHook(() => useArtifactIndex('/storyboard/'))
|
|
16
|
+
|
|
17
|
+
expect(result.current).toEqual({ artifactIndex: null, loading: false })
|
|
18
|
+
expect(fetchMock).not.toHaveBeenCalled()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('returns the parsed index after fetching', async () => {
|
|
22
|
+
const index = { schemaVersion: 1, branches: [], prototypes: {}, canvases: {} }
|
|
23
|
+
const fetchMock = vi.fn(async () => ({ ok: true, json: async () => index }))
|
|
24
|
+
vi.stubGlobal('fetch', fetchMock)
|
|
25
|
+
|
|
26
|
+
const { result } = renderHook(() => useArtifactIndex('/storyboard/branch--feature-foo/'))
|
|
27
|
+
|
|
28
|
+
await waitFor(() => {
|
|
29
|
+
expect(result.current.artifactIndex).toBe(index)
|
|
30
|
+
expect(result.current.loading).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
expect(fetchMock).toHaveBeenCalledWith('/storyboard/artifacts.index.json')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('returns null on fetch failure without throwing', async () => {
|
|
36
|
+
vi.stubGlobal('fetch', vi.fn(async () => { throw new Error('offline') }))
|
|
37
|
+
|
|
38
|
+
const { result } = renderHook(() => useArtifactIndex('/storyboard/'))
|
|
39
|
+
|
|
40
|
+
await waitFor(() => {
|
|
41
|
+
expect(result.current.loading).toBe(false)
|
|
42
|
+
})
|
|
43
|
+
expect(result.current.artifactIndex).toBeNull()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Embed reload guard — suppresses Vite HMR full-reloads for iframe-embedded
|
|
3
|
+
* pages (prototypes and stories alike).
|
|
3
4
|
*
|
|
4
5
|
* Controlled by the "prototype-auto-reload" feature flag (default: false).
|
|
6
|
+
* The flag name predates story-widget support; it now toggles the guard
|
|
7
|
+
* for both prototypes AND stories, which share the policy: drop
|
|
8
|
+
* `full-reload` payloads so in-page state survives unrelated file edits,
|
|
9
|
+
* but let `update` payloads through so React Fast Refresh works.
|
|
5
10
|
*
|
|
6
11
|
* The default is false (guard ON) because the dev server emits frequent
|
|
7
12
|
* full-reload broadcasts from data-plugin watchers; without the guard,
|
|
8
|
-
*
|
|
13
|
+
* embedded pages get stuck in a reload loop. Users can opt in via the
|
|
9
14
|
* devtools menu.
|
|
10
|
-
* When the flag is false (user opted out), sends heartbeat messages to the
|
|
11
|
-
* Vite dev server which suppresses full-reload and update payloads for this
|
|
12
|
-
* client. Custom storyboard events (canvas file changes, story changes, etc.)
|
|
13
|
-
* always pass through.
|
|
14
15
|
*
|
|
15
16
|
* Heartbeats are sent every 3s and auto-expire server-side after 5s, so
|
|
16
17
|
* closed tabs never leave the guard stuck.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} [opts]
|
|
20
|
+
* @param {boolean} [opts.enabled=true] — when false, no heartbeats are sent
|
|
21
|
+
* (used by pages that only want to guard when in embed mode).
|
|
17
22
|
*/
|
|
18
23
|
import { useEffect } from 'react'
|
|
19
24
|
import { getFlag, subscribeToStorage } from '../../core/index.js'
|
|
@@ -21,8 +26,9 @@ import { getFlag, subscribeToStorage } from '../../core/index.js'
|
|
|
21
26
|
const FLAG_KEY = 'prototype-auto-reload'
|
|
22
27
|
const HEARTBEAT_MS = 3000
|
|
23
28
|
|
|
24
|
-
export default function usePrototypeReloadGuard() {
|
|
29
|
+
export default function usePrototypeReloadGuard({ enabled = true } = {}) {
|
|
25
30
|
useEffect(() => {
|
|
31
|
+
if (!enabled) return
|
|
26
32
|
if (!import.meta.hot) return
|
|
27
33
|
|
|
28
34
|
let interval = null
|
|
@@ -64,5 +70,5 @@ export default function usePrototypeReloadGuard() {
|
|
|
64
70
|
stop()
|
|
65
71
|
unsub()
|
|
66
72
|
}
|
|
67
|
-
}, [])
|
|
73
|
+
}, [enabled])
|
|
68
74
|
}
|
package/src/internals/index.js
CHANGED
|
@@ -41,9 +41,15 @@ export { default as Workspace } from './Workspace.jsx'
|
|
|
41
41
|
// Deprecated alias — use Workspace instead
|
|
42
42
|
export { default as Viewfinder } from './Workspace.jsx'
|
|
43
43
|
|
|
44
|
+
// Simplified workspace — dev-only "old index" view, falls back to Workspace in prod
|
|
45
|
+
export { default as SimpleWorkspace } from './SimpleWorkspace/SimpleWorkspace.jsx'
|
|
46
|
+
|
|
44
47
|
// Command Palette (includes BranchBar automatically)
|
|
45
48
|
export { default as StoryboardCommandPalette } from './CommandPalette/CommandPalette.jsx'
|
|
46
49
|
|
|
50
|
+
// Create-only page (used by `storyboard create --ui`)
|
|
51
|
+
export { default as CreatePage } from './CreatePage/CreatePage.jsx'
|
|
52
|
+
|
|
47
53
|
// Branch Bar (standalone, for consumers who don't use CommandPalette)
|
|
48
54
|
export { default as BranchBar } from './BranchBar/BranchBar.jsx'
|
|
49
55
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loadConsumerWrapper — resolves the consumer's `src/_prototype.jsx` or
|
|
3
|
+
* `src/_story.jsx` at runtime so storyboard internals (prototype routes,
|
|
4
|
+
* story page, inline story renderer, isolate iframes) can wrap rendered
|
|
5
|
+
* content with the consumer's design-system providers.
|
|
6
|
+
*
|
|
7
|
+
* These two files are documented as **the** seam between storyboard
|
|
8
|
+
* scaffolding and consumer source code. See plan
|
|
9
|
+
* `.github/plans/style-separation.md`.
|
|
10
|
+
*
|
|
11
|
+
* Falls back to a Fragment pass-through when the wrapper file is absent —
|
|
12
|
+
* which corresponds to "blank slate", the new default.
|
|
13
|
+
*
|
|
14
|
+
* Why a glob: `import.meta.glob` resolves paths against the *consumer*
|
|
15
|
+
* Vite project root, so the same code path works in source-repo dev and in
|
|
16
|
+
* published-package consumers.
|
|
17
|
+
*
|
|
18
|
+
* Why lazy (no eager): we want to defer wrapper-file evaluation until
|
|
19
|
+
* something actually needs to render. Eager evaluation would pull every
|
|
20
|
+
* wrapper module into the test environment (the wrappers typically import
|
|
21
|
+
* design-system code that doesn't mock cleanly), and would inflate the
|
|
22
|
+
* initial bundle even when no story/prototype is visible.
|
|
23
|
+
*/
|
|
24
|
+
import { Fragment, lazy, createElement } from 'react'
|
|
25
|
+
|
|
26
|
+
const PROTOTYPE_MODULES = import.meta.glob(
|
|
27
|
+
['/src/_prototype.jsx', '/src/_prototype.tsx'],
|
|
28
|
+
)
|
|
29
|
+
const STORY_MODULES = import.meta.glob(
|
|
30
|
+
['/src/_story.jsx', '/src/_story.tsx'],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
function PassThrough({ children }) {
|
|
34
|
+
return createElement(Fragment, null, children)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeLazyWrapper(modules) {
|
|
38
|
+
const keys = Object.keys(modules)
|
|
39
|
+
if (keys.length === 0) return PassThrough
|
|
40
|
+
return lazy(async () => {
|
|
41
|
+
const mod = await modules[keys[0]]()
|
|
42
|
+
if (typeof mod?.default === 'function') return { default: mod.default }
|
|
43
|
+
return { default: PassThrough }
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let _protoCache
|
|
48
|
+
let _storyCache
|
|
49
|
+
|
|
50
|
+
/** Wrapper for prototype routes (workspace SPA + iframe entries). */
|
|
51
|
+
export function getPrototypeWrapper() {
|
|
52
|
+
if (!_protoCache) _protoCache = makeLazyWrapper(PROTOTYPE_MODULES)
|
|
53
|
+
return _protoCache
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Wrapper for story renders (StoryPage, ComponentSetPage, isolates, inline renderer). */
|
|
57
|
+
export function getStoryWrapper() {
|
|
58
|
+
if (!_storyCache) _storyCache = makeLazyWrapper(STORY_MODULES)
|
|
59
|
+
return _storyCache
|
|
60
|
+
}
|
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
* `storyboard:component-set:select` to the parent window so the
|
|
13
13
|
* canvas widget can track which export the user picked.
|
|
14
14
|
*/
|
|
15
|
-
import { useState, useEffect, useLayoutEffect, useMemo, useCallback, useRef } from 'react'
|
|
15
|
+
import { useState, useEffect, useLayoutEffect, useMemo, useCallback, useRef, Suspense } from 'react'
|
|
16
16
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
17
17
|
import { getStoryData } from '../../core/index.js'
|
|
18
|
-
import {
|
|
18
|
+
import { getStoryWrapper } from '../loadConsumerWrapper.js'
|
|
19
|
+
import usePrototypeReloadGuard from '../hooks/usePrototypeReloadGuard.js'
|
|
19
20
|
import styles from './ComponentSetPage.module.css'
|
|
20
21
|
|
|
22
|
+
const StoryWrapper = getStoryWrapper()
|
|
23
|
+
|
|
21
24
|
export default function ComponentSetPage({ name }) {
|
|
22
25
|
const location = useLocation()
|
|
23
26
|
const navigate = useNavigate()
|
|
@@ -28,19 +31,8 @@ export default function ComponentSetPage({ name }) {
|
|
|
28
31
|
const selected = searchParams.get('selected') || ''
|
|
29
32
|
const isEmbed = searchParams.has('_sb_embed')
|
|
30
33
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
if (!isEmbed || !import.meta.hot) return
|
|
34
|
-
const msg = { active: true }
|
|
35
|
-
import.meta.hot.send('storyboard:canvas-hmr-guard', msg)
|
|
36
|
-
const interval = setInterval(() => {
|
|
37
|
-
import.meta.hot.send('storyboard:canvas-hmr-guard', msg)
|
|
38
|
-
}, 3000)
|
|
39
|
-
return () => {
|
|
40
|
-
clearInterval(interval)
|
|
41
|
-
import.meta.hot.send('storyboard:canvas-hmr-guard', { active: false })
|
|
42
|
-
}
|
|
43
|
-
}, [isEmbed])
|
|
34
|
+
// Same reload-guard policy as StoryPage / prototype embeds.
|
|
35
|
+
usePrototypeReloadGuard({ enabled: isEmbed })
|
|
44
36
|
|
|
45
37
|
const story = useMemo(() => getStoryData(name), [name])
|
|
46
38
|
const [exports, setExports] = useState(null)
|
|
@@ -89,28 +81,77 @@ export default function ComponentSetPage({ name }) {
|
|
|
89
81
|
|
|
90
82
|
const gridRef = useRef(null)
|
|
91
83
|
|
|
92
|
-
// Post
|
|
93
|
-
//
|
|
84
|
+
// Post size info to parent widget so the canvas can auto-fit on first
|
|
85
|
+
// render AND snap-to-content on resize end.
|
|
86
|
+
//
|
|
87
|
+
// Two messages, mirrored from the legacy componentSetIsolate.jsx:
|
|
88
|
+
// - :initial-size — squarish layout hint computed from the *max* cell
|
|
89
|
+
// dimensions, used by StorySetWidget when the widget has no width/height
|
|
90
|
+
// set yet.
|
|
91
|
+
// - :content-size — live cell-bounding-box extent, used by StorySetWidget
|
|
92
|
+
// to snap width/height down to fit after a user resize. Measured from
|
|
93
|
+
// bounding boxes (not scrollWidth/scrollHeight) so the reported size
|
|
94
|
+
// reflects the *natural* cell extent — scrollWidth always matches the
|
|
95
|
+
// container width when the widget is wider than needed, which would
|
|
96
|
+
// defeat width-snapping.
|
|
94
97
|
useLayoutEffect(() => {
|
|
95
98
|
const grid = gridRef.current
|
|
96
99
|
if (!grid || !exports) return
|
|
100
|
+
if (!isEmbed || window.parent === window) return
|
|
101
|
+
|
|
102
|
+
let postedInitial = false
|
|
103
|
+
function postInitialSize() {
|
|
104
|
+
if (postedInitial) return
|
|
105
|
+
const cells = grid.querySelectorAll(`.${styles.cell}`)
|
|
106
|
+
if (cells.length === 0) return
|
|
107
|
+
let maxW = 0
|
|
108
|
+
let maxH = 0
|
|
109
|
+
cells.forEach((cell) => {
|
|
110
|
+
const r = cell.getBoundingClientRect()
|
|
111
|
+
if (r.width > maxW) maxW = r.width
|
|
112
|
+
if (r.height > maxH) maxH = r.height
|
|
113
|
+
})
|
|
114
|
+
if (maxW < 10 || maxH < 10) return
|
|
115
|
+
const count = cells.length
|
|
116
|
+
const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count))))
|
|
117
|
+
const rows = Math.ceil(count / cols)
|
|
118
|
+
const gap = 16
|
|
119
|
+
const pad = 32 // 16px padding on both sides
|
|
120
|
+
const width = Math.ceil(maxW * cols + gap * (cols - 1) + pad)
|
|
121
|
+
const height = Math.ceil(maxH * rows + gap * (rows - 1) + pad)
|
|
122
|
+
postedInitial = true
|
|
123
|
+
window.parent.postMessage({
|
|
124
|
+
type: 'storyboard:component-set:initial-size',
|
|
125
|
+
width,
|
|
126
|
+
height,
|
|
127
|
+
}, '*')
|
|
128
|
+
}
|
|
97
129
|
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
function postContentSize() {
|
|
131
|
+
const cells = grid.querySelectorAll(`.${styles.cell}`)
|
|
132
|
+
if (!cells.length) return
|
|
133
|
+
const gridRect = grid.getBoundingClientRect()
|
|
134
|
+
let maxRight = 0
|
|
135
|
+
let maxBottom = 0
|
|
136
|
+
cells.forEach((cell) => {
|
|
137
|
+
const r = cell.getBoundingClientRect()
|
|
138
|
+
const right = r.right - gridRect.left
|
|
139
|
+
const bottom = r.bottom - gridRect.top
|
|
140
|
+
if (right > maxRight) maxRight = right
|
|
141
|
+
if (bottom > maxBottom) maxBottom = bottom
|
|
142
|
+
})
|
|
143
|
+
const pad = 16 // mirror grid padding
|
|
144
|
+
window.parent.postMessage({
|
|
145
|
+
type: 'storyboard:component-set:content-size',
|
|
146
|
+
width: Math.ceil(maxRight + pad),
|
|
147
|
+
height: Math.ceil(maxBottom + pad),
|
|
148
|
+
}, '*')
|
|
108
149
|
}
|
|
109
150
|
|
|
110
|
-
|
|
111
|
-
document.fonts.ready.then(() => requestAnimationFrame(
|
|
151
|
+
requestAnimationFrame(() => requestAnimationFrame(postInitialSize))
|
|
152
|
+
document.fonts.ready.then(() => requestAnimationFrame(postInitialSize))
|
|
112
153
|
|
|
113
|
-
const ro = new ResizeObserver(
|
|
154
|
+
const ro = new ResizeObserver(postContentSize)
|
|
114
155
|
ro.observe(grid)
|
|
115
156
|
return () => ro.disconnect()
|
|
116
157
|
}, [exports, layout, density, isEmbed])
|
|
@@ -151,8 +192,8 @@ export default function ComponentSetPage({ name }) {
|
|
|
151
192
|
const exportNames = Object.keys(exports)
|
|
152
193
|
|
|
153
194
|
return (
|
|
154
|
-
<
|
|
155
|
-
<
|
|
195
|
+
<Suspense fallback={null}>
|
|
196
|
+
<StoryWrapper>
|
|
156
197
|
<div
|
|
157
198
|
ref={gridRef}
|
|
158
199
|
className={styles.grid}
|
|
@@ -188,7 +229,7 @@ export default function ComponentSetPage({ name }) {
|
|
|
188
229
|
)
|
|
189
230
|
})}
|
|
190
231
|
</div>
|
|
191
|
-
</
|
|
192
|
-
</
|
|
232
|
+
</StoryWrapper>
|
|
233
|
+
</Suspense>
|
|
193
234
|
)
|
|
194
235
|
}
|
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
import { useState, useEffect, useMemo, lazy, Suspense } from 'react'
|
|
9
9
|
import { useLocation } from 'react-router-dom'
|
|
10
10
|
import { getStoryData } from '../../core/index.js'
|
|
11
|
-
import {
|
|
11
|
+
import { getStoryWrapper } from '../loadConsumerWrapper.js'
|
|
12
|
+
import usePrototypeReloadGuard from '../hooks/usePrototypeReloadGuard.js'
|
|
12
13
|
import styles from './StoryPage.module.css'
|
|
13
14
|
|
|
15
|
+
const StoryWrapper = getStoryWrapper()
|
|
16
|
+
|
|
14
17
|
const ComponentSetPageLazy = lazy(() => import('./ComponentSetPage.jsx'))
|
|
15
18
|
|
|
16
19
|
function StoryErrorFallback({ name, error }) {
|
|
@@ -29,22 +32,10 @@ export default function StoryPage({ name }) {
|
|
|
29
32
|
const isEmbed = searchParams.has('_sb_embed')
|
|
30
33
|
const isComponentSet = searchParams.has('_sb_component_set')
|
|
31
34
|
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (!isEmbed || !import.meta.hot) return
|
|
38
|
-
const msg = { active: true }
|
|
39
|
-
import.meta.hot.send('storyboard:canvas-hmr-guard', msg)
|
|
40
|
-
const interval = setInterval(() => {
|
|
41
|
-
import.meta.hot.send('storyboard:canvas-hmr-guard', msg)
|
|
42
|
-
}, 3000)
|
|
43
|
-
return () => {
|
|
44
|
-
clearInterval(interval)
|
|
45
|
-
import.meta.hot.send('storyboard:canvas-hmr-guard', { active: false })
|
|
46
|
-
}
|
|
47
|
-
}, [isEmbed])
|
|
35
|
+
// Share the same reload-guard policy as prototypes: drop full-reloads so
|
|
36
|
+
// the embedding canvas's data-file edits don't flash the story iframe,
|
|
37
|
+
// but let `update` payloads through so React Fast Refresh works.
|
|
38
|
+
usePrototypeReloadGuard({ enabled: isEmbed })
|
|
48
39
|
|
|
49
40
|
const story = useMemo(() => getStoryData(name), [name])
|
|
50
41
|
const [exports, setExports] = useState(null)
|
|
@@ -123,11 +114,11 @@ export default function StoryPage({ name }) {
|
|
|
123
114
|
}
|
|
124
115
|
|
|
125
116
|
return (
|
|
126
|
-
<
|
|
127
|
-
<
|
|
117
|
+
<Suspense fallback={null}>
|
|
118
|
+
<StoryWrapper>
|
|
128
119
|
<Component />
|
|
129
|
-
</
|
|
130
|
-
</
|
|
120
|
+
</StoryWrapper>
|
|
121
|
+
</Suspense>
|
|
131
122
|
)
|
|
132
123
|
}
|
|
133
124
|
|
|
@@ -135,13 +126,13 @@ export default function StoryPage({ name }) {
|
|
|
135
126
|
const exportNames = Object.keys(exports)
|
|
136
127
|
|
|
137
128
|
return (
|
|
138
|
-
<
|
|
139
|
-
<
|
|
129
|
+
<Suspense fallback={null}>
|
|
130
|
+
<StoryWrapper>
|
|
140
131
|
{exportNames.map((exportName) => {
|
|
141
132
|
const Component = exports[exportName]
|
|
142
133
|
return <Component key={exportName} />
|
|
143
134
|
})}
|
|
144
|
-
</
|
|
145
|
-
</
|
|
135
|
+
</StoryWrapper>
|
|
136
|
+
</Suspense>
|
|
146
137
|
)
|
|
147
138
|
}
|