@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.
- package/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +14128 -10953
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/dist/tiny-canvas.css +1 -1
- package/package.json +138 -128
- 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 +41 -0
- package/scaffold/src/library/_app.module.css +2 -0
- package/scaffold/src/library/create.jsx +10 -0
- package/scaffold/src/library/index.jsx +14 -0
- package/scaffold/src/library/mount.jsx +51 -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/canvas/style.css +16 -16
- package/src/core/artifact/operations.js +54 -15
- package/src/core/artifact/schemas/canvas.schema.json +7 -1
- package/src/core/autosync/server.test.js +1 -1
- package/src/core/canvas/hot-pool.js +21 -33
- package/src/core/canvas/materializer.js +39 -13
- package/src/core/canvas/selectedWidgets.js +1 -1
- package/src/core/canvas/server.js +376 -100
- package/src/core/canvas/server.test.js +472 -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/artifact.js +2 -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 +151 -23
- package/src/core/cli/dev.js +23 -35
- package/src/core/cli/index.js +21 -0
- package/src/core/cli/schemas.js +8 -4
- 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/comments/ui/comment-layout.css +14 -14
- 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 +24 -1
- package/src/core/inspector/highlighter.js +1 -1
- package/src/core/inspector/mouseMode.js +2 -2
- 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/modes/modes.css +4 -4
- package/src/core/mountStoryboardCore.js +287 -83
- package/src/core/mountStoryboardCore.test.js +198 -0
- package/src/core/rename-watcher/config.json +1 -1
- package/src/core/rename-watcher/watcher.js +330 -37
- 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/canvasInteractionStore.js +120 -0
- package/src/core/stores/canvasInteractionStore.test.js +126 -0
- package/src/core/stores/configSchema.js +64 -8
- package/src/core/stores/configSchema.test.js +3 -0
- package/src/core/stores/customerModeConfig.js +24 -3
- package/src/core/stores/customerModeConfig.test.js +46 -0
- package/src/core/stores/presentationStore.js +118 -0
- package/src/core/stores/presentationStore.test.js +114 -0
- 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/stores/uiConfig.js +10 -2
- package/src/core/styles/generateThemesCss.js +63 -0
- package/src/core/styles/tailwind.css +150 -94
- package/src/core/styles/workspace-chrome.css +13 -0
- package/src/core/tools/handlers/paletteTheme.js +11 -8
- package/src/core/ui/ActionMenuButton.jsx +2 -1
- package/src/core/ui/AutosyncMenuButton.css +7 -7
- package/src/core/ui/BranchSelect.module.css +5 -5
- package/src/core/ui/CanvasCreateMenu.jsx +1 -19
- package/src/core/ui/ChromeSlot.jsx +92 -0
- package/src/core/ui/ChromeSlot.test.jsx +140 -0
- package/src/core/ui/CoreUIBar.css +5 -5
- package/src/core/ui/CoreUIBar.jsx +170 -142
- package/src/core/ui/InspectorPanel.css +5 -5
- package/src/core/ui/InspectorPanel.jsx +9 -9
- package/src/core/ui/PwaInstallBanner.css +5 -5
- package/src/core/ui/PwaInstallBanner.jsx +5 -5
- package/src/core/ui/ThemeMenuButton.jsx +45 -30
- package/src/core/ui/core-ui-colors.css +28 -66
- package/src/core/ui/sidepanel.css +12 -12
- package/src/core/vite/server-plugin.js +13 -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 +130 -34
- 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 +51 -69
- package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
- package/src/internals/AuthModal/AuthModal.module.css +34 -34
- package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
- package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
- package/src/internals/BranchBar/BranchBar.module.css +4 -4
- package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +41 -29
- package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
- package/src/internals/CommandPalette/WidgetArtifactDialog.css +16 -16
- package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
- package/src/internals/CommandPalette/command-palette.css +14 -14
- 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/FlowError.module.css +5 -5
- package/src/internals/PrototypeErrorBoundary.module.css +15 -15
- package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +534 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
- package/src/internals/Tooltip.jsx +39 -0
- package/src/internals/Viewfinder.jsx +229 -64
- package/src/internals/Viewfinder.module.css +253 -182
- package/src/internals/canvas/CanvasControls.module.css +11 -11
- package/src/internals/canvas/CanvasPage.bridge.test.jsx +11 -37
- package/src/internals/canvas/CanvasPage.jsx +358 -217
- package/src/internals/canvas/CanvasPage.module.css +20 -19
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
- package/src/internals/canvas/CanvasToolbar.module.css +11 -11
- package/src/internals/canvas/ConnectorLayer.jsx +39 -2
- package/src/internals/canvas/ConnectorLayer.module.css +10 -10
- package/src/internals/canvas/PageSelector.jsx +15 -1
- package/src/internals/canvas/PageSelector.module.css +41 -27
- package/src/internals/canvas/canvasApi.js +40 -5
- package/src/internals/canvas/canvasTheme.js +22 -111
- 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 +140 -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/CodePenEmbed.jsx +16 -36
- package/src/internals/canvas/widgets/CodePenEmbed.module.css +12 -12
- package/src/internals/canvas/widgets/CropOverlay.module.css +23 -23
- package/src/internals/canvas/widgets/ExpandedPane.module.css +8 -8
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
- package/src/internals/canvas/widgets/FigmaEmbed.jsx +15 -36
- package/src/internals/canvas/widgets/FigmaEmbed.module.css +15 -15
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +5 -5
- package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
- package/src/internals/canvas/widgets/ImageWidget.module.css +3 -3
- package/src/internals/canvas/widgets/LinkPreview.module.css +79 -80
- package/src/internals/canvas/widgets/MarkdownBlock.module.css +31 -31
- package/src/internals/canvas/widgets/PromptWidget.jsx +2 -16
- package/src/internals/canvas/widgets/PromptWidget.module.css +33 -33
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +92 -17
- package/src/internals/canvas/widgets/PrototypeEmbed.module.css +58 -56
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +118 -4
- package/src/internals/canvas/widgets/ResizeHandle.module.css +4 -4
- package/src/internals/canvas/widgets/StorySetWidget.jsx +53 -25
- package/src/internals/canvas/widgets/StoryWidget.jsx +71 -56
- package/src/internals/canvas/widgets/StoryWidget.module.css +22 -20
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
- package/src/internals/canvas/widgets/TerminalReadWidget.module.css +4 -4
- package/src/internals/canvas/widgets/TerminalWidget.jsx +56 -45
- package/src/internals/canvas/widgets/TerminalWidget.module.css +10 -10
- package/src/internals/canvas/widgets/TilesWidget.module.css +17 -17
- package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
- package/src/internals/canvas/widgets/WidgetChrome.module.css +76 -61
- package/src/internals/canvas/widgets/WidgetWrapper.module.css +2 -2
- package/src/internals/canvas/widgets/embedOverlay.module.css +14 -4
- package/src/internals/canvas/widgets/embedTheme.js +12 -104
- package/src/internals/canvas/widgets/expandUtils.js +19 -19
- package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
- package/src/internals/canvas/widgets/widgetConfig.js +5 -5
- 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/ComponentSetPage.module.css +14 -14
- package/src/internals/story/StoryPage.jsx +16 -25
- package/src/internals/story/StoryPage.module.css +2 -2
- package/src/internals/vite/data-plugin.js +317 -149
- 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/WebGLContextPool.jsx +0 -291
- package/src/internals/canvas/WebGLContextPool.test.jsx +0 -165
- package/src/internals/canvas/componentIsolate.jsx +0 -144
- package/src/internals/canvas/componentSetIsolate.jsx +0 -300
- package/src/internals/canvas/widgets/ComponentSetWidget.module.css +0 -89
- package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
- package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
- package/src/internals/canvas/widgets/StorySetWidget.module.css +0 -89
- package/src/primer/ThemeSync.jsx +0 -73
- package/src/primer/index.js +0 -1
|
@@ -20,6 +20,23 @@ async function request(path, method, body) {
|
|
|
20
20
|
return res.json()
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Like `request` but also returns the HTTP status code so callers can
|
|
25
|
+
* distinguish between "endpoint succeeded but logical no-op" and "the
|
|
26
|
+
* target resource is gone" (which undo/redo handle differently after
|
|
27
|
+
* compaction wipes event ids).
|
|
28
|
+
*/
|
|
29
|
+
async function requestWithStatus(path, method, body) {
|
|
30
|
+
const url = getApiBase() + path
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method,
|
|
33
|
+
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
|
34
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
35
|
+
})
|
|
36
|
+
const data = await res.json().catch(() => ({}))
|
|
37
|
+
return { status: res.status, ok: res.ok, data }
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
export function listCanvases() {
|
|
24
41
|
return request('/list', 'GET')
|
|
25
42
|
}
|
|
@@ -32,6 +49,18 @@ export function updateCanvas(canvasId, { widgets, sources, settings, connectors
|
|
|
32
49
|
return request('/update', 'PUT', { name: canvasId, widgets, sources, settings, connectors })
|
|
33
50
|
}
|
|
34
51
|
|
|
52
|
+
export function patchWidget(canvasId, widgetId, { props, position } = {}) {
|
|
53
|
+
return request('/widget', 'PATCH', { name: canvasId, widgetId, props, position })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function undoEvent(canvasId, eventId) {
|
|
57
|
+
return requestWithStatus('/undo', 'POST', { name: canvasId, eventId })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function redoEvent(canvasId, eventId) {
|
|
61
|
+
return requestWithStatus('/redo', 'POST', { name: canvasId, eventId })
|
|
62
|
+
}
|
|
63
|
+
|
|
35
64
|
export function addWidget(canvasId, { type, props, position }) {
|
|
36
65
|
return request('/widget', 'POST', { name: canvasId, type, props, position })
|
|
37
66
|
}
|
|
@@ -56,9 +85,10 @@ export function duplicateImage(filename) {
|
|
|
56
85
|
|
|
57
86
|
/**
|
|
58
87
|
* Crop an image client-side and upload the result.
|
|
59
|
-
* @param {string} imageSrc — current image filename
|
|
88
|
+
* @param {string} imageSrc — current image filename, possibly prefixed with `drafts/` and/or `~`
|
|
89
|
+
* (e.g. "drafts/~canvas--2026-01-01--12-00-00.png")
|
|
60
90
|
* @param {{ x: number, y: number, width: number, height: number }} cropRect — crop region in natural image pixels
|
|
61
|
-
* @param {string} canvasId — canvas name for directory resolution
|
|
91
|
+
* @param {string} canvasId — canvas name for directory resolution (server uses this to decide drafts/ vs public)
|
|
62
92
|
* @returns {Promise<{ success: boolean, filename: string }>}
|
|
63
93
|
*/
|
|
64
94
|
export async function cropAndUpload(imageSrc, cropRect, canvasId) {
|
|
@@ -89,15 +119,20 @@ export async function cropAndUpload(imageSrc, cropRect, canvasId) {
|
|
|
89
119
|
canvas.width, canvas.height,
|
|
90
120
|
)
|
|
91
121
|
|
|
122
|
+
// Strip optional `drafts/` directory prefix — the server re-adds it based
|
|
123
|
+
// on canvasId. We only pass the basename through the cropped-filename
|
|
124
|
+
// pipeline (the server validates `--cropped--` filenames as basenames).
|
|
125
|
+
const srcBase = imageSrc.includes('/') ? imageSrc.slice(imageSrc.lastIndexOf('/') + 1) : imageSrc
|
|
126
|
+
|
|
92
127
|
// Determine output format from original filename
|
|
93
|
-
const ext =
|
|
128
|
+
const ext = srcBase.split('.').pop()?.toLowerCase() || 'png'
|
|
94
129
|
const mimeMap = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif' }
|
|
95
130
|
const mime = mimeMap[ext] || 'image/png'
|
|
96
131
|
const dataUrl = canvas.toDataURL(mime, 0.92)
|
|
97
132
|
|
|
98
133
|
// Build cropped filename: strip any previous --cropped-- suffix, append new one
|
|
99
|
-
const privacyPrefix =
|
|
100
|
-
const baseName =
|
|
134
|
+
const privacyPrefix = srcBase.startsWith('~') ? '~' : ''
|
|
135
|
+
const baseName = srcBase.replace(/^~/, '')
|
|
101
136
|
const withoutCrop = baseName.replace(/--cropped--\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2}/, '')
|
|
102
137
|
const nameWithoutExt = withoutCrop.replace(/\.\w+$/, '')
|
|
103
138
|
const now = new Date()
|
|
@@ -1,118 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canvasTheme.js — DEPRECATED COMPATIBILITY SHIM.
|
|
3
|
+
*
|
|
4
|
+
* Color variables previously written here as inline styles now live in
|
|
5
|
+
* `core/styles/tailwind.css` and cascade automatically from
|
|
6
|
+
* `[data-sb-canvas-theme^="dark"]` (set by CanvasPage on the canvas root).
|
|
7
|
+
*
|
|
8
|
+
* `getCanvasPrimerAttrs` is preserved as a no-op to keep external consumers
|
|
9
|
+
* (Primer React inside prototypes) functional via `data-color-mode` — but
|
|
10
|
+
* storyboard core no longer depends on this attribute internally.
|
|
11
|
+
*
|
|
12
|
+
* Both exports return empty objects so existing call sites continue to
|
|
13
|
+
* destructure/spread without errors. Remove the call sites in a follow-up
|
|
14
|
+
* sweep, then delete this file.
|
|
15
|
+
*/
|
|
16
|
+
|
|
1
17
|
export function getCanvasPrimerAttrs(theme) {
|
|
2
18
|
const value = String(theme || 'light')
|
|
3
|
-
|
|
4
|
-
return {
|
|
5
|
-
'data-color-mode': 'dark',
|
|
6
|
-
'data-dark-theme': value,
|
|
7
|
-
'data-light-theme': 'light',
|
|
8
|
-
}
|
|
9
|
-
}
|
|
19
|
+
const isDark = value.startsWith('dark')
|
|
10
20
|
return {
|
|
11
|
-
'data-color-mode': 'light',
|
|
12
|
-
'data-
|
|
13
|
-
'data-
|
|
21
|
+
'data-color-mode': isDark ? 'dark' : 'light',
|
|
22
|
+
'data-light-theme': isDark ? 'light' : (value.startsWith('light') ? value : 'light'),
|
|
23
|
+
'data-dark-theme': isDark ? value : 'dark',
|
|
14
24
|
}
|
|
15
25
|
}
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Each theme gets its own entry so high-contrast, colorblind, and dimmed
|
|
20
|
-
* variants all render with the correct background, dot, and text colors.
|
|
21
|
-
*/
|
|
22
|
-
const THEME_VARS = {
|
|
23
|
-
light: {
|
|
24
|
-
'--sb--canvas-bg': '#f6f8fa',
|
|
25
|
-
'--bgColor-default': '#ffffff',
|
|
26
|
-
'--bgColor-muted': '#f6f8fa',
|
|
27
|
-
'--bgColor-neutral-muted': '#818b981f',
|
|
28
|
-
'--bgColor-accent-emphasis': '#0969da',
|
|
29
|
-
'--tc-bg-muted': '#f6f8fa',
|
|
30
|
-
'--tc-dot-color': 'rgba(0, 0, 0, 0.08)',
|
|
31
|
-
'--overlay-backdrop-bgColor': 'rgba(0, 0, 0, 0.08)',
|
|
32
|
-
'--fgColor-muted': '#59636e',
|
|
33
|
-
'--fgColor-default': '#1f2328',
|
|
34
|
-
'--fgColor-onEmphasis': '#ffffff',
|
|
35
|
-
'--borderColor-default': '#d1d9e0',
|
|
36
|
-
'--borderColor-muted': '#d1d9e0b3',
|
|
37
|
-
},
|
|
38
|
-
light_colorblind: {
|
|
39
|
-
'--sb--canvas-bg': '#f6f8fa',
|
|
40
|
-
'--bgColor-default': '#ffffff',
|
|
41
|
-
'--bgColor-muted': '#f6f8fa',
|
|
42
|
-
'--bgColor-neutral-muted': '#818b981f',
|
|
43
|
-
'--bgColor-accent-emphasis': '#0969da',
|
|
44
|
-
'--tc-bg-muted': '#f6f8fa',
|
|
45
|
-
'--tc-dot-color': 'rgba(0, 0, 0, 0.08)',
|
|
46
|
-
'--overlay-backdrop-bgColor': 'rgba(0, 0, 0, 0.08)',
|
|
47
|
-
'--fgColor-muted': '#59636e',
|
|
48
|
-
'--fgColor-default': '#1f2328',
|
|
49
|
-
'--fgColor-onEmphasis': '#ffffff',
|
|
50
|
-
'--borderColor-default': '#d1d9e0',
|
|
51
|
-
'--borderColor-muted': '#d1d9e0b3',
|
|
52
|
-
},
|
|
53
|
-
dark: {
|
|
54
|
-
'--sb--canvas-bg': '#151b23',
|
|
55
|
-
'--bgColor-default': '#0d1117',
|
|
56
|
-
'--bgColor-muted': '#151b23',
|
|
57
|
-
'--bgColor-neutral-muted': '#656c7633',
|
|
58
|
-
'--bgColor-accent-emphasis': '#1f6feb',
|
|
59
|
-
'--tc-bg-muted': '#151b23',
|
|
60
|
-
'--tc-dot-color': 'rgba(255, 255, 255, 0.1)',
|
|
61
|
-
'--overlay-backdrop-bgColor': 'rgba(255, 255, 255, 0.1)',
|
|
62
|
-
'--fgColor-muted': '#9198a1',
|
|
63
|
-
'--fgColor-default': '#f0f6fc',
|
|
64
|
-
'--fgColor-onEmphasis': '#ffffff',
|
|
65
|
-
'--borderColor-default': '#3d444d',
|
|
66
|
-
'--borderColor-muted': '#3d444db3',
|
|
67
|
-
},
|
|
68
|
-
dark_dimmed: {
|
|
69
|
-
'--sb--canvas-bg': '#262c36',
|
|
70
|
-
'--bgColor-default': '#212830',
|
|
71
|
-
'--bgColor-muted': '#262c36',
|
|
72
|
-
'--bgColor-neutral-muted': '#656c7633',
|
|
73
|
-
'--bgColor-accent-emphasis': '#316dca',
|
|
74
|
-
'--tc-bg-muted': '#262c36',
|
|
75
|
-
'--tc-dot-color': 'rgba(209, 215, 224, 0.18)',
|
|
76
|
-
'--overlay-backdrop-bgColor': 'rgba(209, 215, 224, 0.18)',
|
|
77
|
-
'--fgColor-muted': '#9198a1',
|
|
78
|
-
'--fgColor-default': '#d1d7e0',
|
|
79
|
-
'--fgColor-onEmphasis': '#f0f6fc',
|
|
80
|
-
'--borderColor-default': '#3d444d',
|
|
81
|
-
'--borderColor-muted': '#3d444db3',
|
|
82
|
-
},
|
|
83
|
-
dark_colorblind: {
|
|
84
|
-
'--sb--canvas-bg': '#151b23',
|
|
85
|
-
'--bgColor-default': '#0d1117',
|
|
86
|
-
'--bgColor-muted': '#151b23',
|
|
87
|
-
'--bgColor-neutral-muted': '#656c7633',
|
|
88
|
-
'--bgColor-accent-emphasis': '#1f6feb',
|
|
89
|
-
'--tc-bg-muted': '#151b23',
|
|
90
|
-
'--tc-dot-color': 'rgba(255, 255, 255, 0.1)',
|
|
91
|
-
'--overlay-backdrop-bgColor': 'rgba(255, 255, 255, 0.1)',
|
|
92
|
-
'--fgColor-muted': '#9198a1',
|
|
93
|
-
'--fgColor-default': '#f0f6fc',
|
|
94
|
-
'--fgColor-onEmphasis': '#ffffff',
|
|
95
|
-
'--borderColor-default': '#3d444d',
|
|
96
|
-
'--borderColor-muted': '#3d444db3',
|
|
97
|
-
},
|
|
98
|
-
dark_high_contrast: {
|
|
99
|
-
'--sb--canvas-bg': '#151b23',
|
|
100
|
-
'--bgColor-default': '#010409',
|
|
101
|
-
'--bgColor-muted': '#151b23',
|
|
102
|
-
'--bgColor-neutral-muted': '#212830',
|
|
103
|
-
'--bgColor-accent-emphasis': '#194fb1',
|
|
104
|
-
'--tc-bg-muted': '#151b23',
|
|
105
|
-
'--tc-dot-color': 'rgba(183, 189, 200, 0.25)',
|
|
106
|
-
'--overlay-backdrop-bgColor': 'rgba(183, 189, 200, 0.25)',
|
|
107
|
-
'--fgColor-muted': '#b7bdc8',
|
|
108
|
-
'--fgColor-default': '#ffffff',
|
|
109
|
-
'--fgColor-onEmphasis': '#ffffff',
|
|
110
|
-
'--borderColor-default': '#b7bdc8',
|
|
111
|
-
'--borderColor-muted': '#b7bdc8',
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function getCanvasThemeVars(theme) {
|
|
116
|
-
const value = String(theme || 'light')
|
|
117
|
-
return THEME_VARS[value] || THEME_VARS.light
|
|
27
|
+
export function getCanvasThemeVars() {
|
|
28
|
+
return {}
|
|
118
29
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite middleware factory for iframe-isolation entries.
|
|
3
|
+
*
|
|
4
|
+
* Both `prototypes.html` (prototype embeds) and `stories.html` (story /
|
|
5
|
+
* component-set widgets) follow the same recipe: serve a minimal HTML shell
|
|
6
|
+
* that boots a library-side React entry, with a theme bootstrap inlined so
|
|
7
|
+
* first paint matches the active theme. This factory captures that recipe
|
|
8
|
+
* so the two surfaces can't drift apart.
|
|
9
|
+
*
|
|
10
|
+
* Returns a Connect middleware that matches `/${name}` and `/${name}/*`
|
|
11
|
+
* (after the configured Vite base path is stripped). Non-matching URLs are
|
|
12
|
+
* passed through to `next()`.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} opts
|
|
15
|
+
* @param {string} opts.name — entry name, including extension, e.g. 'prototypes.html'
|
|
16
|
+
* @param {string} opts.entryPath — absolute on-disk path to the JS entry to /@fs-load
|
|
17
|
+
* @param {string} opts.surface — value for `data-sb-surface` attribute on <body>
|
|
18
|
+
* @param {string} opts.title — document <title>
|
|
19
|
+
* @param {() => object|undefined} opts.getThemes — read live themes config; called per request
|
|
20
|
+
* @returns {(req, res, next) => void}
|
|
21
|
+
*/
|
|
22
|
+
export function createIsolationMiddleware({ name, entryPath, surface, title, getThemes }, { renderInlineBootstrapScript, server }) {
|
|
23
|
+
const matchPath = `/${name}`
|
|
24
|
+
return async function isolationMiddleware(req, res, next) {
|
|
25
|
+
if (!req.url) return next()
|
|
26
|
+
let url = req.url
|
|
27
|
+
const baseNoTrail = (server.config.base || '/').replace(/\/$/, '')
|
|
28
|
+
if (baseNoTrail && url.startsWith(baseNoTrail)) {
|
|
29
|
+
url = url.slice(baseNoTrail.length) || '/'
|
|
30
|
+
}
|
|
31
|
+
const cleanUrl = url.split('?')[0].split('#')[0]
|
|
32
|
+
if (cleanUrl !== matchPath && !cleanUrl.startsWith(`${matchPath}/`)) return next()
|
|
33
|
+
|
|
34
|
+
const themes = getThemes?.()
|
|
35
|
+
const themeBootstrap = themes ? renderInlineBootstrapScript(themes) : ''
|
|
36
|
+
|
|
37
|
+
const rawHtml = [
|
|
38
|
+
'<!DOCTYPE html>',
|
|
39
|
+
'<html lang="en"><head>',
|
|
40
|
+
'<meta charset="UTF-8" />',
|
|
41
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />',
|
|
42
|
+
`<title>${escapeHtml(title)}</title>`,
|
|
43
|
+
themeBootstrap,
|
|
44
|
+
`</head><body data-sb-surface="${escapeAttr(surface)}">`,
|
|
45
|
+
'<div id="root"></div>',
|
|
46
|
+
`<script type="module" src="/@fs${entryPath}"></script>`,
|
|
47
|
+
'</body></html>',
|
|
48
|
+
].join('\n')
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const html = await server.transformIndexHtml(req.url, rawHtml)
|
|
52
|
+
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
53
|
+
res.end(html)
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error(`[storyboard] ${name} transform failed:`, err)
|
|
56
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
57
|
+
res.end(`${name} failed`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function escapeHtml(s) {
|
|
63
|
+
return String(s).replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function escapeAttr(s) {
|
|
67
|
+
return String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]))
|
|
68
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mount helper for isolated iframe entries.
|
|
3
|
+
*
|
|
4
|
+
* Used by `prototypesEntry.jsx` and `storiesEntry.jsx` — they both:
|
|
5
|
+
* 1. Read `storyboard.config.json` via import.meta.glob
|
|
6
|
+
* 2. Call `mountStoryboardCore` so the embed gets ui-runtime CSS, theme
|
|
7
|
+
* sync, and the iframe→parent postMessage bridge
|
|
8
|
+
* 3. Mount a React tree with `createBrowserRouter` scoped to the entry
|
|
9
|
+
* page (basename = `${BASE_URL}/${name}`)
|
|
10
|
+
*
|
|
11
|
+
* Centralizing this here means bug fixes / iterations land in one place
|
|
12
|
+
* for both surfaces. Each surface entry shrinks to a few lines:
|
|
13
|
+
*
|
|
14
|
+
* import { routes, getRoutesForProto } from './prototypeRoutes.jsx'
|
|
15
|
+
* import { mountIsolationApp } from './mountIsolationApp.jsx'
|
|
16
|
+
* mountIsolationApp({
|
|
17
|
+
* name: 'prototypes.html',
|
|
18
|
+
* wrapperKind: 'prototype',
|
|
19
|
+
* resolveRoutes: ({ firstSegment, search }) => {
|
|
20
|
+
* const proto = firstSegment || search.get('proto') || ''
|
|
21
|
+
* return proto ? getRoutesForProto(proto) : routes
|
|
22
|
+
* },
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* Provider chain: the consumer's `src/_prototype.jsx` (or `src/_story.jsx`)
|
|
26
|
+
* wraps the routes. When absent, both wrappers degrade to a Fragment
|
|
27
|
+
* pass-through — prototypes render with no shared providers (blank slate).
|
|
28
|
+
*
|
|
29
|
+
* @param {object} opts
|
|
30
|
+
* @param {string} opts.name — entry name, e.g. 'prototypes.html'
|
|
31
|
+
* @param {'prototype' | 'story'} [opts.wrapperKind='prototype']
|
|
32
|
+
* — which consumer wrapper to mount around the router. Prototype
|
|
33
|
+
* uses `src/_prototype.jsx`, story uses `src/_story.jsx`.
|
|
34
|
+
* @param {RouteObject[]} [opts.routes] — fixed route table (used when resolveRoutes is omitted)
|
|
35
|
+
* @param {(ctx) => RouteObject[]} [opts.resolveRoutes]
|
|
36
|
+
* — callback that returns the route table to mount, given
|
|
37
|
+
* `{ basename, pathname, search, firstSegment }`. Use this when a
|
|
38
|
+
* surface needs to narrow routes per-request (e.g. prototypes
|
|
39
|
+
* isolate a broken sibling proto from the matched lazy() chain).
|
|
40
|
+
*/
|
|
41
|
+
import { StrictMode, Suspense } from 'react'
|
|
42
|
+
import { createRoot } from 'react-dom/client'
|
|
43
|
+
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
|
|
44
|
+
import { mountStoryboardCore } from '../../../core/index.js'
|
|
45
|
+
import { getPrototypeWrapper, getStoryWrapper } from '../../loadConsumerWrapper.js'
|
|
46
|
+
import '../../../core/comments/ui/comment-layout.css'
|
|
47
|
+
|
|
48
|
+
const configModules = import.meta.glob('/storyboard.config.json', { eager: true })
|
|
49
|
+
|
|
50
|
+
export function mountIsolationApp({ name, wrapperKind = 'prototype', routes, resolveRoutes }) {
|
|
51
|
+
const storyboardConfig = Object.values(configModules)[0]?.default || {}
|
|
52
|
+
|
|
53
|
+
// Mount storyboard core. In embed mode (`_sb_embed` in the URL — always true
|
|
54
|
+
// for canvas iframes) this short-circuits past UI mounting and only:
|
|
55
|
+
// - applies the canvas-aware theme (_sb_theme_target, _sb_canvas_theme)
|
|
56
|
+
// - sets up the iframe→parent postMessage navigation/wheel bridge
|
|
57
|
+
mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
|
|
58
|
+
|
|
59
|
+
const basePathNoTrail = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
|
|
60
|
+
const basename = `${basePathNoTrail}/${name}`
|
|
61
|
+
|
|
62
|
+
const pathname = window.location.pathname
|
|
63
|
+
const search = new URLSearchParams(window.location.search)
|
|
64
|
+
const afterBasename = pathname.startsWith(basename) ? pathname.slice(basename.length) : ''
|
|
65
|
+
const firstSegment = (afterBasename.match(/^\/([^/?#]+)/) || [])[1] || ''
|
|
66
|
+
|
|
67
|
+
const activeRoutes = resolveRoutes
|
|
68
|
+
? resolveRoutes({ basename, pathname, search, firstSegment })
|
|
69
|
+
: routes
|
|
70
|
+
|
|
71
|
+
const router = createBrowserRouter(activeRoutes, { basename })
|
|
72
|
+
|
|
73
|
+
const Wrapper = wrapperKind === 'story' ? getStoryWrapper() : getPrototypeWrapper()
|
|
74
|
+
|
|
75
|
+
const rootElement = document.getElementById('root')
|
|
76
|
+
const root = createRoot(rootElement)
|
|
77
|
+
|
|
78
|
+
root.render(
|
|
79
|
+
<StrictMode>
|
|
80
|
+
<Suspense fallback={null}>
|
|
81
|
+
<Wrapper>
|
|
82
|
+
<RouterProvider router={router} />
|
|
83
|
+
</Wrapper>
|
|
84
|
+
</Suspense>
|
|
85
|
+
</StrictMode>,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library-side prototype routes builder for the isolated prototypes iframe.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the consumer-scaffold convention (src/routes.jsx) but lives inside
|
|
5
|
+
* the library so consumers no longer need to maintain their own
|
|
6
|
+
* prototypes-entry plumbing. Uses absolute globs (`/src/prototypes/...`),
|
|
7
|
+
* which Vite resolves against the *consumer's* project root regardless of
|
|
8
|
+
* where this file physically lives.
|
|
9
|
+
*
|
|
10
|
+
* Used by `prototypesEntry.jsx`, which is the iframe entry served by the
|
|
11
|
+
* `prototypes.html` middleware in `data-plugin.js`.
|
|
12
|
+
*/
|
|
13
|
+
import { Fragment, Suspense } from 'react'
|
|
14
|
+
import { Outlet, useLocation } from 'react-router-dom'
|
|
15
|
+
import {
|
|
16
|
+
generatePreservedRoutes,
|
|
17
|
+
generateRegularRoutes,
|
|
18
|
+
generateModalRoutes,
|
|
19
|
+
patterns,
|
|
20
|
+
} from '@generouted/react-router/core'
|
|
21
|
+
import PrototypeErrorBoundary, { ImportErrorFallback } from '../../PrototypeErrorBoundary.jsx'
|
|
22
|
+
|
|
23
|
+
// Strip src/library/, src/prototypes/, .folder/, and `drafts/` segments
|
|
24
|
+
// from route paths. `_app.jsx` lives in src/library/ (since
|
|
25
|
+
// b144dace5); user-authored prototype routes live in src/prototypes/.
|
|
26
|
+
// Both prefixes are stripped so the generated route paths (and the
|
|
27
|
+
// preservedRoutes _app key) come out clean.
|
|
28
|
+
//
|
|
29
|
+
// A `drafts/` dir is a gitignored scratch root — prototypes inside route at
|
|
30
|
+
// the same URL as a non-prefixed sibling. The lookbehind ensures we only
|
|
31
|
+
// match `drafts/` as a directory segment, not as part of a filename.
|
|
32
|
+
patterns.route = [/^.*\/src\/(library|prototypes)\/|^\/(library|prototypes)\/|[^/]*\.folder\/|(?<=^|\/)drafts\/|\.(jsx|tsx|mdx)$/g, '']
|
|
33
|
+
|
|
34
|
+
// `_app.jsx` lives in src/library/ — it's the consumer's SPA shell that
|
|
35
|
+
// wraps every route in <StoryboardProvider>. Without this glob hitting
|
|
36
|
+
// the right path, the iframe entry would render prototypes with no
|
|
37
|
+
// provider and any useFlowData / useObject / useRecord call would crash.
|
|
38
|
+
const PRESERVED = import.meta.glob('/src/library/(_app|404).{jsx,tsx}', { eager: true })
|
|
39
|
+
const MODALS = import.meta.glob('/src/prototypes/**/[+]*.{jsx,tsx}', { eager: true })
|
|
40
|
+
|
|
41
|
+
// Lazy-load prototype routes — only the matched route's module is fetched.
|
|
42
|
+
// This prevents story/canvas URLs from paying for every prototype page's
|
|
43
|
+
// imports (Primer, Reshaped, user components, etc.) on first load.
|
|
44
|
+
const ROUTES = import.meta.glob(
|
|
45
|
+
['/src/prototypes/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/prototypes/**/(_!(layout)*(/*)?|_app|404)*'],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const preservedRoutes = generatePreservedRoutes(PRESERVED)
|
|
49
|
+
const modalRoutes = generateModalRoutes(MODALS)
|
|
50
|
+
|
|
51
|
+
const regularRoutes = generateRegularRoutes(ROUTES, (importFn, key) => {
|
|
52
|
+
const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('prototypes/index') ? { index: true } : {}
|
|
53
|
+
return {
|
|
54
|
+
...index,
|
|
55
|
+
lazy: async () => {
|
|
56
|
+
try {
|
|
57
|
+
const module = await importFn()
|
|
58
|
+
const Default = module?.default || Fragment
|
|
59
|
+
const Page = () =>
|
|
60
|
+
module?.Pending ? (
|
|
61
|
+
<Suspense fallback={<module.Pending />}>
|
|
62
|
+
<Default />
|
|
63
|
+
</Suspense>
|
|
64
|
+
) : (
|
|
65
|
+
<Default />
|
|
66
|
+
)
|
|
67
|
+
return {
|
|
68
|
+
Component: Page,
|
|
69
|
+
ErrorBoundary: module?.Catch || PrototypeErrorBoundary,
|
|
70
|
+
loader: module?.Loader,
|
|
71
|
+
action: module?.Action,
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return {
|
|
75
|
+
Component: () => <ImportErrorFallback error={err} route={key} />,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const _app = preservedRoutes?.['_app']
|
|
83
|
+
const _404 = preservedRoutes?.['404']
|
|
84
|
+
const Default = _app?.default || Outlet
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
87
|
+
const Modals_ = () => {
|
|
88
|
+
const Modal = modalRoutes[useLocation().state?.modal] || Fragment
|
|
89
|
+
return <Modal />
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
93
|
+
const Layout = () => (
|
|
94
|
+
<>
|
|
95
|
+
<Default /> <Modals_ />
|
|
96
|
+
</>
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
100
|
+
const App = () =>
|
|
101
|
+
_app?.Pending ? (
|
|
102
|
+
<Suspense fallback={<_app.Pending />}>
|
|
103
|
+
<Layout />
|
|
104
|
+
</Suspense>
|
|
105
|
+
) : (
|
|
106
|
+
<Layout />
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader }
|
|
110
|
+
const fallback = { path: '*', Component: _404?.default || Fragment }
|
|
111
|
+
|
|
112
|
+
export const routes = [{ ...app, children: [...regularRoutes, fallback] }]
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Return a routes tree filtered to a single prototype subtree. Used by the
|
|
116
|
+
* per-prototype iframe entry — filtering at this layer keeps a broken
|
|
117
|
+
* sibling prototype from ever appearing in the matched route's lazy() chain
|
|
118
|
+
* (only the requested prototype's modules are reachable from the router).
|
|
119
|
+
*/
|
|
120
|
+
export function getRoutesForProto(proto) {
|
|
121
|
+
if (!proto) return routes
|
|
122
|
+
const matchesProto = (route) => {
|
|
123
|
+
const seg = (route.path || '').split('/').filter(Boolean)[0]
|
|
124
|
+
return seg === proto
|
|
125
|
+
}
|
|
126
|
+
const filter = (children) => children
|
|
127
|
+
.map((r) => {
|
|
128
|
+
if (r.path === '*') return r
|
|
129
|
+
if (!r.path) {
|
|
130
|
+
if (r.children) return { ...r, children: filter(r.children) }
|
|
131
|
+
return r
|
|
132
|
+
}
|
|
133
|
+
if (matchesProto(r)) {
|
|
134
|
+
return r.children ? { ...r, children: filter(r.children) } : r
|
|
135
|
+
}
|
|
136
|
+
return null
|
|
137
|
+
})
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
return [{ ...routes[0], children: filter(routes[0].children || []) }]
|
|
140
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library-provided entry for the isolated prototypes iframe.
|
|
3
|
+
*
|
|
4
|
+
* Loaded by the prototypes.html middleware in `data-plugin.js`. All the
|
|
5
|
+
* mount plumbing (consumer stylesheet pull-in, storyboard.config.json read,
|
|
6
|
+
* mountStoryboardCore, basename derivation, React tree) lives in the shared
|
|
7
|
+
* `mountIsolationApp` helper so this file and `storiesEntry.jsx` stay in
|
|
8
|
+
* sync. See `.agents/plans/vite-isolation.md` for the isolation pattern.
|
|
9
|
+
*
|
|
10
|
+
* The `resolveRoutes` callback narrows the route table to a single
|
|
11
|
+
* prototype subtree based on the first path segment after the basename
|
|
12
|
+
* (e.g. `/prototypes.html/MyProto/SignupForm` → narrow to MyProto). This
|
|
13
|
+
* keeps a broken sibling prototype from ever appearing in the matched
|
|
14
|
+
* route's lazy() chain — module-graph isolation.
|
|
15
|
+
*
|
|
16
|
+
* The legacy `?proto=` query param is preserved as a fallback for older
|
|
17
|
+
* canvases / older builds.
|
|
18
|
+
*/
|
|
19
|
+
import { routes, getRoutesForProto } from './prototypeRoutes.jsx'
|
|
20
|
+
import { mountIsolationApp } from './mountIsolationApp.jsx'
|
|
21
|
+
|
|
22
|
+
mountIsolationApp({
|
|
23
|
+
name: 'prototypes.html',
|
|
24
|
+
resolveRoutes: ({ firstSegment, search }) => {
|
|
25
|
+
const proto = firstSegment || search.get('proto') || ''
|
|
26
|
+
return proto ? getRoutesForProto(proto) : routes
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library-provided entry for the isolated stories iframe.
|
|
3
|
+
*
|
|
4
|
+
* Loaded by the stories.html middleware in `data-plugin.js`. All the mount
|
|
5
|
+
* plumbing lives in the shared `mountIsolationApp` helper so this file
|
|
6
|
+
* and `prototypesEntry.jsx` stay in sync. See `.agents/plans/vite-isolation.md`
|
|
7
|
+
* for the isolation pattern.
|
|
8
|
+
*
|
|
9
|
+
* No `resolveRoutes` callback — stories don't need per-segment narrowing
|
|
10
|
+
* because each `.story.jsx` is dynamic-imported inside StoryPage, so the
|
|
11
|
+
* route module graph never eagerly pulls in sibling stories.
|
|
12
|
+
*/
|
|
13
|
+
import { storyRoutes } from './storyRoutes.jsx'
|
|
14
|
+
import { mountIsolationApp } from './mountIsolationApp.jsx'
|
|
15
|
+
|
|
16
|
+
mountIsolationApp({
|
|
17
|
+
name: 'stories.html',
|
|
18
|
+
wrapperKind: 'story',
|
|
19
|
+
routes: storyRoutes,
|
|
20
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route table for the isolated stories iframe (`/stories.html/components/...`).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the canvas SPA's story-route matching but built as a real react-router
|
|
5
|
+
* route table so the route lives in the URL (deep-linkable, "open in new tab"
|
|
6
|
+
* works) and a broken `.story.jsx` only crashes the matched story — not the
|
|
7
|
+
* canvas chrome (module-graph isolation: each .story.jsx is dynamic-imported
|
|
8
|
+
* by StoryPage._storyImport, not eagerly globbed).
|
|
9
|
+
*
|
|
10
|
+
* Uses a single splat route — the story name is derived at render time by
|
|
11
|
+
* matching the full route path against the stories index (`_route` field).
|
|
12
|
+
* Falls back to a friendly "story not found" message when no match.
|
|
13
|
+
*/
|
|
14
|
+
import { lazy, Suspense } from 'react'
|
|
15
|
+
import { useLocation } from 'react-router-dom'
|
|
16
|
+
import { stories } from 'virtual:storyboard-data-index'
|
|
17
|
+
|
|
18
|
+
const StoryPageLazy = lazy(() => import('../../story/StoryPage.jsx'))
|
|
19
|
+
|
|
20
|
+
function resolveStoryName(pathname) {
|
|
21
|
+
const normalized = pathname.replace(/\/+$/, '') || '/'
|
|
22
|
+
for (const [name, data] of Object.entries(stories || {})) {
|
|
23
|
+
if (data?._route && data._route.replace(/\/+$/, '') === normalized) {
|
|
24
|
+
return name
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function StoryRouteComponent() {
|
|
31
|
+
const location = useLocation()
|
|
32
|
+
const storyName = resolveStoryName(location.pathname)
|
|
33
|
+
|
|
34
|
+
if (!storyName) {
|
|
35
|
+
return (
|
|
36
|
+
<div style={{ padding: 24, fontFamily: 'system-ui, sans-serif', color: 'var(--color-foreground-muted, #57606a)' }}>
|
|
37
|
+
<strong>Story not found</strong>
|
|
38
|
+
<br />
|
|
39
|
+
<code>{location.pathname}</code>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Suspense fallback={null}>
|
|
46
|
+
<StoryPageLazy name={storyName} />
|
|
47
|
+
</Suspense>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const storyRoutes = [
|
|
52
|
+
{ path: '/components', element: <StoryRouteComponent /> },
|
|
53
|
+
{ path: '/components/*', element: <StoryRouteComponent /> },
|
|
54
|
+
{ path: '*', element: <StoryRouteComponent /> },
|
|
55
|
+
]
|