@dfosco/storyboard 0.5.0-alpha.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/commandpalette.config.json +152 -0
- package/dist/storyboard-ui.css +1 -0
- package/dist/storyboard-ui.js +21328 -0
- package/dist/storyboard-ui.js.map +1 -0
- package/dist/tailwind.css +2 -0
- package/dist/tiny-canvas.css +1 -0
- package/dist/tiny-canvas.js +389 -0
- package/package.json +121 -0
- package/paste.config.json +67 -0
- package/scaffold/AGENTS.md +432 -0
- package/scaffold/agents/prompt-agent.agent.md +181 -0
- package/scaffold/agents/terminal-agent.agent.md +351 -0
- package/scaffold/codex/config.toml +246 -0
- package/scaffold/deploy.yml +103 -0
- package/scaffold/githooks/pre-push +114 -0
- package/scaffold/gitignore +64 -0
- package/scaffold/manifest.json +56 -0
- package/scaffold/preview.yml +181 -0
- package/scaffold/scripts/link.sh +26 -0
- package/scaffold/scripts/unlink.sh +10 -0
- package/scaffold/skills/agent-browser/SKILL.md +260 -0
- package/scaffold/skills/canvas/SKILL.md +364 -0
- package/scaffold/skills/create/SKILL.md +501 -0
- package/scaffold/skills/ship/SKILL.md +237 -0
- package/scaffold/skills/storyboard/SKILL.md +360 -0
- package/scaffold/skills/update-storyboard/SKILL.md +16 -0
- package/scaffold/skills/update-storyboard/update-storyboard-packages.sh +26 -0
- package/scaffold/skills/vitest/GENERATION.md +5 -0
- package/scaffold/skills/vitest/SKILL.md +52 -0
- package/scaffold/skills/vitest/references/advanced-environments.md +264 -0
- package/scaffold/skills/vitest/references/advanced-projects.md +300 -0
- package/scaffold/skills/vitest/references/advanced-type-testing.md +237 -0
- package/scaffold/skills/vitest/references/advanced-vi.md +249 -0
- package/scaffold/skills/vitest/references/core-cli.md +166 -0
- package/scaffold/skills/vitest/references/core-config.md +174 -0
- package/scaffold/skills/vitest/references/core-describe.md +193 -0
- package/scaffold/skills/vitest/references/core-expect.md +219 -0
- package/scaffold/skills/vitest/references/core-hooks.md +244 -0
- package/scaffold/skills/vitest/references/core-test-api.md +233 -0
- package/scaffold/skills/vitest/references/features-concurrency.md +250 -0
- package/scaffold/skills/vitest/references/features-context.md +238 -0
- package/scaffold/skills/vitest/references/features-coverage.md +207 -0
- package/scaffold/skills/vitest/references/features-filtering.md +211 -0
- package/scaffold/skills/vitest/references/features-mocking.md +265 -0
- package/scaffold/skills/vitest/references/features-snapshots.md +207 -0
- package/scaffold/skills/worktree/SKILL.md +93 -0
- package/scaffold/storyboard.config.json +44 -0
- package/src/canvas/Canvas.jsx +78 -0
- package/src/canvas/Draggable.jsx +235 -0
- package/src/canvas/index.d.ts +41 -0
- package/src/canvas/index.js +6 -0
- package/src/canvas/style.css +118 -0
- package/src/canvas/useResetCanvas.js +17 -0
- package/src/canvas/utils.js +136 -0
- package/src/core/assets/fonts/IoskeleyMono-Bold.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-Italic.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-Medium.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-Regular.woff2 +0 -0
- package/src/core/assets/fonts/IoskeleyMono-SemiBold.woff2 +0 -0
- package/src/core/autosync/server.js +714 -0
- package/src/core/autosync/server.test.js +158 -0
- package/src/core/canvas/__tests__/agent-integration.test.js +596 -0
- package/src/core/canvas/__tests__/helpers/browser.js +95 -0
- package/src/core/canvas/__tests__/helpers/canvas-api.js +129 -0
- package/src/core/canvas/__tests__/helpers/perf.js +118 -0
- package/src/core/canvas/__tests__/helpers/setup.js +176 -0
- package/src/core/canvas/__tests__/helpers/tmux.js +130 -0
- package/src/core/canvas/__tests__/helpers/transcript.js +132 -0
- package/src/core/canvas/__tests__/terminal-integration.test.js +177 -0
- package/src/core/canvas/collision.js +292 -0
- package/src/core/canvas/collision.test.js +371 -0
- package/src/core/canvas/compact.js +83 -0
- package/src/core/canvas/deriveCanvasId.test.js +40 -0
- package/src/core/canvas/githubEmbeds.js +527 -0
- package/src/core/canvas/githubEmbeds.test.js +302 -0
- package/src/core/canvas/hot-pool.js +766 -0
- package/src/core/canvas/identity.js +107 -0
- package/src/core/canvas/identity.test.js +100 -0
- package/src/core/canvas/materializer.js +259 -0
- package/src/core/canvas/materializer.test.js +356 -0
- package/src/core/canvas/selectedWidgets.js +270 -0
- package/src/core/canvas/selectedWidgets.test.js +321 -0
- package/src/core/canvas/server.js +3134 -0
- package/src/core/canvas/server.test.js +379 -0
- package/src/core/canvas/terminal-config.js +330 -0
- package/src/core/canvas/terminal-registry.js +465 -0
- package/src/core/canvas/terminal-server.js +1436 -0
- package/src/core/canvas/writeGuard.js +53 -0
- package/src/core/cli/agent.js +85 -0
- package/src/core/cli/branch.js +386 -0
- package/src/core/cli/canvasAdd.js +241 -0
- package/src/core/cli/canvasBatch.js +98 -0
- package/src/core/cli/canvasBounds.js +160 -0
- package/src/core/cli/canvasRead.js +236 -0
- package/src/core/cli/canvasUpdate.js +179 -0
- package/src/core/cli/code.js +67 -0
- package/src/core/cli/compact.js +62 -0
- package/src/core/cli/create.js +674 -0
- package/src/core/cli/dev-helpers.js +53 -0
- package/src/core/cli/dev-helpers.test.js +53 -0
- package/src/core/cli/dev.js +430 -0
- package/src/core/cli/exit.js +38 -0
- package/src/core/cli/flags.js +174 -0
- package/src/core/cli/flags.test.js +155 -0
- package/src/core/cli/index.js +233 -0
- package/src/core/cli/intro.js +37 -0
- package/src/core/cli/proxy.js +319 -0
- package/src/core/cli/proxy.test.js +63 -0
- package/src/core/cli/schemas.js +223 -0
- package/src/core/cli/server.js +192 -0
- package/src/core/cli/serverUrl.js +61 -0
- package/src/core/cli/sessions.js +459 -0
- package/src/core/cli/setup.js +404 -0
- package/src/core/cli/terminal-commands.js +287 -0
- package/src/core/cli/terminal-messaging.js +231 -0
- package/src/core/cli/terminal-welcome.js +515 -0
- package/src/core/cli/updateVersion.js +124 -0
- package/src/core/comments/api.js +284 -0
- package/src/core/comments/api.test.js +282 -0
- package/src/core/comments/auth.js +151 -0
- package/src/core/comments/auth.test.js +167 -0
- package/src/core/comments/commentCache.js +109 -0
- package/src/core/comments/commentCache.test.js +48 -0
- package/src/core/comments/commentDrafts.js +68 -0
- package/src/core/comments/commentMode.js +63 -0
- package/src/core/comments/commentMode.test.js +90 -0
- package/src/core/comments/config.js +47 -0
- package/src/core/comments/config.test.js +77 -0
- package/src/core/comments/graphql.js +65 -0
- package/src/core/comments/graphql.test.js +95 -0
- package/src/core/comments/index.js +42 -0
- package/src/core/comments/metadata.js +52 -0
- package/src/core/comments/metadata.test.js +110 -0
- package/src/core/comments/queries.js +245 -0
- package/src/core/comments/ui/AuthModal.jsx +114 -0
- package/src/core/comments/ui/CommentOverlay.js +52 -0
- package/src/core/comments/ui/CommentWindow.jsx +329 -0
- package/src/core/comments/ui/CommentsDrawer.jsx +102 -0
- package/src/core/comments/ui/Composer.jsx +64 -0
- package/src/core/comments/ui/authModal.js +66 -0
- package/src/core/comments/ui/authModal.test.js +76 -0
- package/src/core/comments/ui/comment-cursor-dark.svg +1 -0
- package/src/core/comments/ui/comment-cursor.svg +1 -0
- package/src/core/comments/ui/comment-layout.css +142 -0
- package/src/core/comments/ui/commentWindow.js +121 -0
- package/src/core/comments/ui/comments.css +242 -0
- package/src/core/comments/ui/commentsDrawer.js +84 -0
- package/src/core/comments/ui/composer.js +136 -0
- package/src/core/comments/ui/index.js +14 -0
- package/src/core/comments/ui/mount.js +687 -0
- package/src/core/comments/ui/mount.test.js +336 -0
- package/src/core/data/dotPath.js +53 -0
- package/src/core/data/dotPath.test.js +114 -0
- package/src/core/data/loader.js +409 -0
- package/src/core/data/loader.test.js +599 -0
- package/src/core/data/viewfinder.js +363 -0
- package/src/core/data/viewfinder.test.js +456 -0
- package/src/core/devtools/devtools-consumer.js +28 -0
- package/src/core/devtools/devtools.js +144 -0
- package/src/core/devtools/devtools.test.js +75 -0
- package/src/core/devtools/sceneDebug.js +112 -0
- package/src/core/devtools/sceneDebug.test.js +141 -0
- package/src/core/index.js +124 -0
- package/src/core/inspector/fiberWalker.js +239 -0
- package/src/core/inspector/highlighter.js +275 -0
- package/src/core/inspector/mouseMode.js +259 -0
- package/src/core/lib/components/ui/alert/alert-action.jsx +11 -0
- package/src/core/lib/components/ui/alert/alert-description.jsx +11 -0
- package/src/core/lib/components/ui/alert/alert-title.jsx +11 -0
- package/src/core/lib/components/ui/alert/alert.jsx +25 -0
- package/src/core/lib/components/ui/alert/index.js +17 -0
- package/src/core/lib/components/ui/avatar/avatar-badge.jsx +22 -0
- package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +18 -0
- package/src/core/lib/components/ui/avatar/avatar-group-count.jsx +19 -0
- package/src/core/lib/components/ui/avatar/avatar-group.jsx +19 -0
- package/src/core/lib/components/ui/avatar/avatar-image.jsx +15 -0
- package/src/core/lib/components/ui/avatar/avatar.jsx +19 -0
- package/src/core/lib/components/ui/avatar/index.js +22 -0
- package/src/core/lib/components/ui/badge/badge.jsx +31 -0
- package/src/core/lib/components/ui/badge/index.js +2 -0
- package/src/core/lib/components/ui/button/button.jsx +100 -0
- package/src/core/lib/components/ui/button/index.js +12 -0
- package/src/core/lib/components/ui/card/card-action.jsx +11 -0
- package/src/core/lib/components/ui/card/card-content.jsx +11 -0
- package/src/core/lib/components/ui/card/card-description.jsx +11 -0
- package/src/core/lib/components/ui/card/card-footer.jsx +11 -0
- package/src/core/lib/components/ui/card/card-header.jsx +19 -0
- package/src/core/lib/components/ui/card/card-title.jsx +11 -0
- package/src/core/lib/components/ui/card/card.jsx +17 -0
- package/src/core/lib/components/ui/card/index.js +25 -0
- package/src/core/lib/components/ui/checkbox/checkbox.jsx +29 -0
- package/src/core/lib/components/ui/checkbox/index.js +6 -0
- package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +7 -0
- package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +7 -0
- package/src/core/lib/components/ui/collapsible/collapsible.jsx +7 -0
- package/src/core/lib/components/ui/collapsible/index.js +13 -0
- package/src/core/lib/components/ui/dialog/dialog-close.jsx +7 -0
- package/src/core/lib/components/ui/dialog/dialog-content.jsx +34 -0
- package/src/core/lib/components/ui/dialog/dialog-description.jsx +15 -0
- package/src/core/lib/components/ui/dialog/dialog-footer.jsx +23 -0
- package/src/core/lib/components/ui/dialog/dialog-header.jsx +11 -0
- package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +15 -0
- package/src/core/lib/components/ui/dialog/dialog-portal.jsx +4 -0
- package/src/core/lib/components/ui/dialog/dialog-title.jsx +15 -0
- package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +7 -0
- package/src/core/lib/components/ui/dialog/dialog.jsx +4 -0
- package/src/core/lib/components/ui/dialog/index.js +34 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +8 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +30 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +22 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +16 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +7 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +20 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-label.jsx +17 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +4 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +7 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +29 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +15 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.jsx +16 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +15 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +23 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +4 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +7 -0
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +4 -0
- package/src/core/lib/components/ui/dropdown-menu/index.js +54 -0
- package/src/core/lib/components/ui/input/index.js +7 -0
- package/src/core/lib/components/ui/input/input.jsx +19 -0
- package/src/core/lib/components/ui/label/index.js +7 -0
- package/src/core/lib/components/ui/label/label.jsx +19 -0
- package/src/core/lib/components/ui/panel/index.js +24 -0
- package/src/core/lib/components/ui/panel/panel-body.jsx +11 -0
- package/src/core/lib/components/ui/panel/panel-close.jsx +16 -0
- package/src/core/lib/components/ui/panel/panel-content.jsx +29 -0
- package/src/core/lib/components/ui/panel/panel-footer.jsx +11 -0
- package/src/core/lib/components/ui/panel/panel-header.jsx +11 -0
- package/src/core/lib/components/ui/panel/panel-title.jsx +12 -0
- package/src/core/lib/components/ui/panel/panel.jsx +4 -0
- package/src/core/lib/components/ui/popover/index.js +28 -0
- package/src/core/lib/components/ui/popover/popover-close.jsx +7 -0
- package/src/core/lib/components/ui/popover/popover-content.jsx +22 -0
- package/src/core/lib/components/ui/popover/popover-description.jsx +11 -0
- package/src/core/lib/components/ui/popover/popover-header.jsx +11 -0
- package/src/core/lib/components/ui/popover/popover-portal.jsx +4 -0
- package/src/core/lib/components/ui/popover/popover-title.jsx +11 -0
- package/src/core/lib/components/ui/popover/popover-trigger.jsx +8 -0
- package/src/core/lib/components/ui/popover/popover.jsx +4 -0
- package/src/core/lib/components/ui/searchable-list.jsx +160 -0
- package/src/core/lib/components/ui/select/index.js +37 -0
- package/src/core/lib/components/ui/select/select-content.jsx +30 -0
- package/src/core/lib/components/ui/select/select-group-heading.jsx +17 -0
- package/src/core/lib/components/ui/select/select-group.jsx +15 -0
- package/src/core/lib/components/ui/select/select-item.jsx +26 -0
- package/src/core/lib/components/ui/select/select-label.jsx +11 -0
- package/src/core/lib/components/ui/select/select-portal.jsx +4 -0
- package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +18 -0
- package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +18 -0
- package/src/core/lib/components/ui/select/select-separator.jsx +15 -0
- package/src/core/lib/components/ui/select/select-trigger.jsx +25 -0
- package/src/core/lib/components/ui/select/select.jsx +4 -0
- package/src/core/lib/components/ui/separator/index.js +7 -0
- package/src/core/lib/components/ui/separator/separator.jsx +22 -0
- package/src/core/lib/components/ui/sheet/index.js +34 -0
- package/src/core/lib/components/ui/sheet/sheet-close.jsx +7 -0
- package/src/core/lib/components/ui/sheet/sheet-content.jsx +35 -0
- package/src/core/lib/components/ui/sheet/sheet-description.jsx +15 -0
- package/src/core/lib/components/ui/sheet/sheet-footer.jsx +11 -0
- package/src/core/lib/components/ui/sheet/sheet-header.jsx +11 -0
- package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +15 -0
- package/src/core/lib/components/ui/sheet/sheet-portal.jsx +4 -0
- package/src/core/lib/components/ui/sheet/sheet-title.jsx +15 -0
- package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +7 -0
- package/src/core/lib/components/ui/sheet/sheet.jsx +4 -0
- package/src/core/lib/components/ui/textarea/index.js +7 -0
- package/src/core/lib/components/ui/textarea/textarea.jsx +18 -0
- package/src/core/lib/components/ui/toggle/index.js +8 -0
- package/src/core/lib/components/ui/toggle/toggle.jsx +36 -0
- package/src/core/lib/components/ui/toggle-group/index.js +10 -0
- package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +29 -0
- package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +43 -0
- package/src/core/lib/components/ui/tooltip/index.js +3 -0
- package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +21 -0
- package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +23 -0
- package/src/core/lib/components/ui/tooltip/tooltip.jsx +11 -0
- package/src/core/lib/components/ui/trigger-button/index.js +6 -0
- package/src/core/lib/components/ui/trigger-button/trigger-button.css +38 -0
- package/src/core/lib/components/ui/trigger-button/trigger-button.jsx +63 -0
- package/src/core/lib/utils/index.js +6 -0
- package/src/core/logger/devLogger.js +238 -0
- package/src/core/logger/devLogger.test.js +193 -0
- package/src/core/modes/modes.css +98 -0
- package/src/core/modes/modes.js +492 -0
- package/src/core/modes/modes.test.js +562 -0
- package/src/core/mountStoryboardCore.js +478 -0
- package/src/core/rename-watcher/config.json +23 -0
- package/src/core/rename-watcher/watcher.js +531 -0
- package/src/core/scaffold.js +100 -0
- package/src/core/server/index.js +391 -0
- package/src/core/session/bodyClasses.js +128 -0
- package/src/core/session/bodyClasses.test.js +192 -0
- package/src/core/session/hashSubscribe.js +19 -0
- package/src/core/session/hashSubscribe.test.js +62 -0
- package/src/core/session/hideMode.js +424 -0
- package/src/core/session/hideMode.test.js +268 -0
- package/src/core/session/interceptHideParams.js +35 -0
- package/src/core/session/interceptHideParams.test.js +90 -0
- package/src/core/session/localStorage.js +134 -0
- package/src/core/session/localStorage.test.js +148 -0
- package/src/core/session/session.js +76 -0
- package/src/core/session/session.test.js +91 -0
- package/src/core/stores/canvasConfig.js +134 -0
- package/src/core/stores/canvasConfig.test.js +120 -0
- package/src/core/stores/commandActions.js +284 -0
- package/src/core/stores/commandPaletteConfig.js +31 -0
- package/src/core/stores/configSchema.js +232 -0
- package/src/core/stores/configSchema.test.js +72 -0
- package/src/core/stores/configStore.js +161 -0
- package/src/core/stores/customerModeConfig.js +30 -0
- package/src/core/stores/featureFlags.js +127 -0
- package/src/core/stores/paletteProviders.js +360 -0
- package/src/core/stores/paletteProviders.test.js +186 -0
- package/src/core/stores/plugins.js +40 -0
- package/src/core/stores/plugins.test.js +68 -0
- package/src/core/stores/recentArtifacts.js +68 -0
- package/src/core/stores/recentArtifacts.test.js +71 -0
- package/src/core/stores/sidePanelStore.ts +143 -0
- package/src/core/stores/themeStore.ts +291 -0
- package/src/core/stores/toolRegistry.js +227 -0
- package/src/core/stores/toolStateStore.js +183 -0
- package/src/core/stores/toolStateStore.test.js +220 -0
- package/src/core/stores/toolbarConfigStore.js +165 -0
- package/src/core/stores/uiConfig.js +64 -0
- package/src/core/stores/uiConfig.test.js +63 -0
- package/src/core/styles/tailwind.css +204 -0
- package/src/core/tools/handlers/autosync.js +12 -0
- package/src/core/tools/handlers/canvasAddWidget.js +11 -0
- package/src/core/tools/handlers/canvasAgents.js +20 -0
- package/src/core/tools/handlers/canvasToolbar.js +56 -0
- package/src/core/tools/handlers/commandPalette.js +9 -0
- package/src/core/tools/handlers/comments.js +16 -0
- package/src/core/tools/handlers/create.js +39 -0
- package/src/core/tools/handlers/devtools.js +122 -0
- package/src/core/tools/handlers/devtools.test.js +87 -0
- package/src/core/tools/handlers/featureFlags.js +21 -0
- package/src/core/tools/handlers/flows.js +68 -0
- package/src/core/tools/handlers/hideChrome.js +9 -0
- package/src/core/tools/handlers/hideToolbars.js +25 -0
- package/src/core/tools/handlers/inspector.js +19 -0
- package/src/core/tools/handlers/paletteTheme.js +35 -0
- package/src/core/tools/handlers/theme.js +9 -0
- package/src/core/tools/registry.js +26 -0
- package/src/core/tools/surfaces/canvasToolbar.js +10 -0
- package/src/core/tools/surfaces/commandList.js +10 -0
- package/src/core/tools/surfaces/mainToolbar.js +11 -0
- package/src/core/tools/surfaces/registry.js +19 -0
- package/src/core/ui/ActionMenuButton.jsx +114 -0
- package/src/core/ui/AutosyncMenuButton.css +67 -0
- package/src/core/ui/AutosyncMenuButton.jsx +242 -0
- package/src/core/ui/BranchSelect.jsx +29 -0
- package/src/core/ui/BranchSelect.module.css +30 -0
- package/src/core/ui/CanvasAgentsMenu.jsx +89 -0
- package/src/core/ui/CanvasCreateMenu.jsx +611 -0
- package/src/core/ui/CanvasSnap.css +27 -0
- package/src/core/ui/CanvasSnap.jsx +51 -0
- package/src/core/ui/CanvasUndoRedo.css +36 -0
- package/src/core/ui/CanvasUndoRedo.jsx +62 -0
- package/src/core/ui/CanvasZoomControl.css +53 -0
- package/src/core/ui/CanvasZoomControl.jsx +49 -0
- package/src/core/ui/CanvasZoomToFit.css +18 -0
- package/src/core/ui/CanvasZoomToFit.jsx +26 -0
- package/src/core/ui/CommandMenu.css +8 -0
- package/src/core/ui/CommandMenu.jsx +287 -0
- package/src/core/ui/CommandPalette.jsx +35 -0
- package/src/core/ui/CommandPaletteTrigger.jsx +25 -0
- package/src/core/ui/CommentsMenuButton.jsx +40 -0
- package/src/core/ui/CoreUIBar.css +47 -0
- package/src/core/ui/CoreUIBar.jsx +905 -0
- package/src/core/ui/CreateMenuButton.jsx +117 -0
- package/src/core/ui/HideChromeTrigger.jsx +48 -0
- package/src/core/ui/Icon.jsx +279 -0
- package/src/core/ui/InspectorPanel.css +109 -0
- package/src/core/ui/InspectorPanel.jsx +632 -0
- package/src/core/ui/PwaInstallBanner.css +42 -0
- package/src/core/ui/PwaInstallBanner.jsx +124 -0
- package/src/core/ui/SidePanel.jsx +261 -0
- package/src/core/ui/ThemeMenuButton.jsx +139 -0
- package/src/core/ui/core-ui-colors.css +129 -0
- package/src/core/ui/design-modes.ts +7 -0
- package/src/core/ui/sidepanel.css +301 -0
- package/src/core/ui/viewfinder.ts +7 -0
- package/src/core/ui-entry.js +30 -0
- package/src/core/utils/fuzzySearch.js +117 -0
- package/src/core/utils/fuzzySearch.test.js +119 -0
- package/src/core/utils/mobileViewport.js +57 -0
- package/src/core/utils/mobileViewport.test.js +68 -0
- package/src/core/utils/prodMode.js +38 -0
- package/src/core/utils/smoothCorners.js +20 -0
- package/src/core/vite/docs-handler.js +155 -0
- package/src/core/vite/server-plugin.js +797 -0
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +260 -0
- package/src/core/workshop/features/createCanvas/index.js +14 -0
- package/src/core/workshop/features/createFlow/CreateFlowForm.jsx +334 -0
- package/src/core/workshop/features/createFlow/index.js +19 -0
- package/src/core/workshop/features/createFlow/server.js +663 -0
- package/src/core/workshop/features/createPage/CreatePageForm.jsx +304 -0
- package/src/core/workshop/features/createPage/index.js +11 -0
- package/src/core/workshop/features/createPrototype/CreatePrototypeForm.jsx +289 -0
- package/src/core/workshop/features/createPrototype/index.js +19 -0
- package/src/core/workshop/features/createPrototype/server.js +433 -0
- package/src/core/workshop/features/createStory/CreateStoryForm.jsx +208 -0
- package/src/core/workshop/features/createStory/index.js +14 -0
- package/src/core/workshop/features/registry-server.js +22 -0
- package/src/core/workshop/features/registry.js +28 -0
- package/src/core/workshop/features/templateIndex.js +155 -0
- package/src/core/workshop/ui/WorkshopPanel.jsx +98 -0
- package/src/core/workshop/ui/mount.ts +6 -0
- package/src/core/worktree/port.js +268 -0
- package/src/core/worktree/port.test.js +222 -0
- package/src/core/worktree/serverRegistry.js +120 -0
- package/src/internals/AuthModal/AuthModal.jsx +132 -0
- package/src/internals/AuthModal/AuthModal.module.css +221 -0
- package/src/internals/BranchBar/BranchBar.jsx +87 -0
- package/src/internals/BranchBar/BranchBar.module.css +247 -0
- package/src/internals/BranchBar/useBranches.js +93 -0
- package/src/internals/BranchBar/useBranches.test.js +68 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +1361 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +219 -0
- package/src/internals/CommandPalette/command-palette.css +180 -0
- package/src/internals/FlowError.module.css +30 -0
- package/src/internals/Icon.jsx +279 -0
- package/src/internals/StoryboardContext.js +3 -0
- package/src/internals/Viewfinder.jsx +1479 -0
- package/src/internals/Viewfinder.module.css +1540 -0
- package/src/internals/Workspace.jsx +7 -0
- package/src/internals/__mocks__/virtual-storyboard-data-index.js +4 -0
- package/src/internals/canvas/CanvasControls.jsx +112 -0
- package/src/internals/canvas/CanvasControls.module.css +135 -0
- package/src/internals/canvas/CanvasPage.bridge.test.jsx +387 -0
- package/src/internals/canvas/CanvasPage.dragdrop.test.jsx +350 -0
- package/src/internals/canvas/CanvasPage.jsx +3092 -0
- package/src/internals/canvas/CanvasPage.module.css +187 -0
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +358 -0
- package/src/internals/canvas/CanvasToolbar.jsx +73 -0
- package/src/internals/canvas/CanvasToolbar.module.css +92 -0
- package/src/internals/canvas/ComponentErrorBoundary.jsx +50 -0
- package/src/internals/canvas/ConnectorLayer.jsx +208 -0
- package/src/internals/canvas/ConnectorLayer.module.css +129 -0
- package/src/internals/canvas/MarqueeOverlay.jsx +20 -0
- package/src/internals/canvas/PageSelector.jsx +587 -0
- package/src/internals/canvas/PageSelector.module.css +261 -0
- package/src/internals/canvas/PageSelector.test.jsx +113 -0
- package/src/internals/canvas/WebGLContextPool.jsx +292 -0
- package/src/internals/canvas/WebGLContextPool.test.jsx +165 -0
- package/src/internals/canvas/canvasApi.js +164 -0
- package/src/internals/canvas/canvasReloadGuard.js +37 -0
- package/src/internals/canvas/canvasReloadGuard.test.js +27 -0
- package/src/internals/canvas/canvasTheme.js +118 -0
- package/src/internals/canvas/componentIsolate.jsx +165 -0
- package/src/internals/canvas/componentSetIsolate.jsx +257 -0
- package/src/internals/canvas/computeCanvasBounds.test.js +121 -0
- package/src/internals/canvas/connectorGeometry.js +132 -0
- package/src/internals/canvas/hotPoolDevLogs.js +25 -0
- package/src/internals/canvas/textSelection.js +10 -0
- package/src/internals/canvas/textSelection.test.js +26 -0
- package/src/internals/canvas/useCanvas.js +126 -0
- package/src/internals/canvas/useCanvas.test.js +26 -0
- package/src/internals/canvas/useMarqueeSelect.js +213 -0
- package/src/internals/canvas/useMarqueeSelect.test.js +78 -0
- package/src/internals/canvas/useUndoRedo.js +86 -0
- package/src/internals/canvas/useUndoRedo.test.js +231 -0
- package/src/internals/canvas/widgets/CodePenEmbed.jsx +293 -0
- package/src/internals/canvas/widgets/CodePenEmbed.module.css +161 -0
- package/src/internals/canvas/widgets/ComponentSetWidget.jsx +2 -0
- package/src/internals/canvas/widgets/ComponentSetWidget.module.css +89 -0
- package/src/internals/canvas/widgets/ComponentWidget.jsx +14 -0
- package/src/internals/canvas/widgets/ComponentWidget.module.css +0 -0
- package/src/internals/canvas/widgets/CropOverlay.jsx +179 -0
- package/src/internals/canvas/widgets/CropOverlay.module.css +154 -0
- package/src/internals/canvas/widgets/ExpandedPane.jsx +474 -0
- package/src/internals/canvas/widgets/ExpandedPane.module.css +179 -0
- package/src/internals/canvas/widgets/ExpandedPane.test.jsx +240 -0
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
- package/src/internals/canvas/widgets/FigmaEmbed.jsx +296 -0
- package/src/internals/canvas/widgets/FigmaEmbed.module.css +222 -0
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +151 -0
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +83 -0
- package/src/internals/canvas/widgets/ImageWidget.jsx +287 -0
- package/src/internals/canvas/widgets/ImageWidget.module.css +81 -0
- package/src/internals/canvas/widgets/LinkPreview.jsx +439 -0
- package/src/internals/canvas/widgets/LinkPreview.module.css +585 -0
- package/src/internals/canvas/widgets/LinkPreview.test.jsx +193 -0
- package/src/internals/canvas/widgets/MarkdownBlock.jsx +354 -0
- package/src/internals/canvas/widgets/MarkdownBlock.module.css +377 -0
- package/src/internals/canvas/widgets/MarkdownBlock.test.jsx +92 -0
- package/src/internals/canvas/widgets/PromptWidget.jsx +428 -0
- package/src/internals/canvas/widgets/PromptWidget.module.css +273 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +463 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.module.css +579 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +10 -0
- package/src/internals/canvas/widgets/ResizeHandle.jsx +67 -0
- package/src/internals/canvas/widgets/ResizeHandle.module.css +29 -0
- package/src/internals/canvas/widgets/StickyNote.jsx +92 -0
- package/src/internals/canvas/widgets/StickyNote.module.css +70 -0
- package/src/internals/canvas/widgets/StickyNote.test.jsx +116 -0
- package/src/internals/canvas/widgets/StorySetWidget.jsx +208 -0
- package/src/internals/canvas/widgets/StorySetWidget.module.css +89 -0
- package/src/internals/canvas/widgets/StoryWidget.jsx +334 -0
- package/src/internals/canvas/widgets/StoryWidget.module.css +211 -0
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +146 -0
- package/src/internals/canvas/widgets/TerminalReadWidget.module.css +94 -0
- package/src/internals/canvas/widgets/TerminalWidget.jsx +704 -0
- package/src/internals/canvas/widgets/TerminalWidget.module.css +444 -0
- package/src/internals/canvas/widgets/TilesWidget.jsx +300 -0
- package/src/internals/canvas/widgets/TilesWidget.module.css +133 -0
- package/src/internals/canvas/widgets/WidgetChrome.jsx +580 -0
- package/src/internals/canvas/widgets/WidgetChrome.module.css +421 -0
- package/src/internals/canvas/widgets/WidgetWrapper.jsx +15 -0
- package/src/internals/canvas/widgets/WidgetWrapper.module.css +25 -0
- package/src/internals/canvas/widgets/codepenUrl.js +75 -0
- package/src/internals/canvas/widgets/codepenUrl.test.js +76 -0
- package/src/internals/canvas/widgets/embedInteraction.test.jsx +173 -0
- package/src/internals/canvas/widgets/embedOverlay.module.css +35 -0
- package/src/internals/canvas/widgets/embedTheme.js +148 -0
- package/src/internals/canvas/widgets/expandUtils.js +559 -0
- package/src/internals/canvas/widgets/expandUtils.test.js +155 -0
- package/src/internals/canvas/widgets/figmaUrl.js +118 -0
- package/src/internals/canvas/widgets/figmaUrl.test.js +139 -0
- package/src/internals/canvas/widgets/githubUrl.js +82 -0
- package/src/internals/canvas/widgets/githubUrl.test.js +74 -0
- package/src/internals/canvas/widgets/iframeDevLogs.js +49 -0
- package/src/internals/canvas/widgets/iframeDevLogs.test.jsx +81 -0
- package/src/internals/canvas/widgets/index.js +42 -0
- package/src/internals/canvas/widgets/pasteRules.js +295 -0
- package/src/internals/canvas/widgets/pasteRules.test.js +474 -0
- package/src/internals/canvas/widgets/snapshotDisplay.test.jsx +211 -0
- package/src/internals/canvas/widgets/tilePool.js +23 -0
- package/src/internals/canvas/widgets/tiles/diagonal-bl.png +0 -0
- package/src/internals/canvas/widgets/tiles/diagonal-br.png +0 -0
- package/src/internals/canvas/widgets/tiles/diagonal-tl.png +0 -0
- package/src/internals/canvas/widgets/tiles/leaf.png +0 -0
- package/src/internals/canvas/widgets/tiles/quarter-tl.png +0 -0
- package/src/internals/canvas/widgets/tiles/quarter-tr.png +0 -0
- package/src/internals/canvas/widgets/tiles/solid-a.png +0 -0
- package/src/internals/canvas/widgets/tiles/solid-b.png +0 -0
- package/src/internals/canvas/widgets/widgetConfig.js +291 -0
- package/src/internals/canvas/widgets/widgetConfig.test.js +68 -0
- package/src/internals/canvas/widgets/widgetIcons.jsx +190 -0
- package/src/internals/canvas/widgets/widgetProps.js +133 -0
- package/src/internals/context/FormContext.js +13 -0
- package/src/internals/context/FormContext.test.js +48 -0
- package/src/internals/context.jsx +481 -0
- package/src/internals/context.test.jsx +296 -0
- package/src/internals/hashPreserver.js +73 -0
- package/src/internals/hashPreserver.test.js +107 -0
- package/src/internals/hooks/useConfig.js +14 -0
- package/src/internals/hooks/useFeatureFlag.js +14 -0
- package/src/internals/hooks/useFlows.js +50 -0
- package/src/internals/hooks/useFlows.test.js +134 -0
- package/src/internals/hooks/useHideMode.js +31 -0
- package/src/internals/hooks/useHideMode.test.js +43 -0
- package/src/internals/hooks/useLocalStorage.js +57 -0
- package/src/internals/hooks/useLocalStorage.test.js +75 -0
- package/src/internals/hooks/useMode.js +43 -0
- package/src/internals/hooks/useObject.js +101 -0
- package/src/internals/hooks/useObject.test.js +74 -0
- package/src/internals/hooks/useOverride.js +84 -0
- package/src/internals/hooks/useOverride.test.js +71 -0
- package/src/internals/hooks/usePrototypeReloadGuard.js +64 -0
- package/src/internals/hooks/useRecord.js +158 -0
- package/src/internals/hooks/useRecord.test.js +221 -0
- package/src/internals/hooks/useScene.js +38 -0
- package/src/internals/hooks/useScene.test.js +66 -0
- package/src/internals/hooks/useSceneData.js +108 -0
- package/src/internals/hooks/useSceneData.test.js +136 -0
- package/src/internals/hooks/useSession.js +4 -0
- package/src/internals/hooks/useSession.test.js +8 -0
- package/src/internals/hooks/useThemeState.js +61 -0
- package/src/internals/hooks/useThemeState.test.js +66 -0
- package/src/internals/hooks/useUndoRedo.js +28 -0
- package/src/internals/hooks/useUndoRedo.test.js +64 -0
- package/src/internals/index.js +58 -0
- package/src/internals/story/ComponentSetPage.jsx +198 -0
- package/src/internals/story/ComponentSetPage.module.css +129 -0
- package/src/internals/story/StoryPage.jsx +147 -0
- package/src/internals/story/StoryPage.module.css +18 -0
- package/src/internals/test-utils.js +45 -0
- package/src/internals/vite/data-plugin.js +1508 -0
- package/src/internals/vite/data-plugin.test.js +1223 -0
- package/src/test-utils.js +44 -0
- package/toolbar.config.json +271 -0
- package/widgets.config.json +1537 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { useContext, useMemo, useSyncExternalStore } from 'react'
|
|
2
|
+
import { useParams } from 'react-router-dom'
|
|
3
|
+
import { loadRecord, resolveRecordName } from '../../core/index.js'
|
|
4
|
+
import { deepClone, setByPath } from '../../core/index.js'
|
|
5
|
+
import { getAllParams } from '../../core/index.js'
|
|
6
|
+
import { isHideMode, getAllShadows } from '../../core/index.js'
|
|
7
|
+
import { subscribeToHash, getHashSnapshot } from '../../core/index.js'
|
|
8
|
+
import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
|
|
9
|
+
import { StoryboardContext } from '../StoryboardContext.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Collect overrides for a record and merge them into the base array.
|
|
13
|
+
*
|
|
14
|
+
* In normal mode reads from URL hash params; in hide mode reads from
|
|
15
|
+
* localStorage shadow snapshots.
|
|
16
|
+
*
|
|
17
|
+
* Hash convention: record.{recordName}.{entryId}.{field}=value
|
|
18
|
+
*
|
|
19
|
+
* - Existing entries (matched by id) get fields merged on top.
|
|
20
|
+
* - Unknown ids create new entries appended to the array.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array} baseRecords - The original record array (will be deep-cloned)
|
|
23
|
+
* @param {string} resolvedName - Resolved (possibly scoped) record name (e.g. "security/rules")
|
|
24
|
+
* @param {string} [plainName] - Original unscoped record name (e.g. "rules"). Falls back to resolvedName.
|
|
25
|
+
* @returns {Array} Merged array
|
|
26
|
+
*/
|
|
27
|
+
function applyRecordOverrides(baseRecords, resolvedName, plainName) {
|
|
28
|
+
const allParams = isHideMode() ? getAllShadows() : getAllParams()
|
|
29
|
+
|
|
30
|
+
// Check both the resolved (scoped) prefix and the plain (unscoped) prefix.
|
|
31
|
+
// Callers write overrides with the plain name, but the data index resolves
|
|
32
|
+
// to the scoped name — we need to match both so overrides are not silently
|
|
33
|
+
// dropped for prototype-scoped records.
|
|
34
|
+
const resolvedPrefix = `record.${resolvedName}.`
|
|
35
|
+
const plainPrefix = plainName && plainName !== resolvedName
|
|
36
|
+
? `record.${plainName}.`
|
|
37
|
+
: null
|
|
38
|
+
|
|
39
|
+
// Collect only the params that target this record
|
|
40
|
+
const overrideKeys = Object.keys(allParams).filter(k =>
|
|
41
|
+
k.startsWith(resolvedPrefix) || (plainPrefix && k.startsWith(plainPrefix))
|
|
42
|
+
)
|
|
43
|
+
if (overrideKeys.length === 0) return baseRecords
|
|
44
|
+
|
|
45
|
+
const records = deepClone(baseRecords)
|
|
46
|
+
|
|
47
|
+
// Group overrides by entry id
|
|
48
|
+
// key format: record.{name}.{entryId}.{field...}
|
|
49
|
+
const byEntryId = {}
|
|
50
|
+
for (const key of overrideKeys) {
|
|
51
|
+
// Determine which prefix matched to slice correctly
|
|
52
|
+
const prefix = key.startsWith(resolvedPrefix) ? resolvedPrefix : plainPrefix
|
|
53
|
+
const rest = key.slice(prefix.length) // "{entryId}.{field...}"
|
|
54
|
+
const dotIdx = rest.indexOf('.')
|
|
55
|
+
if (dotIdx === -1) continue // no field path — skip
|
|
56
|
+
const entryId = rest.slice(0, dotIdx)
|
|
57
|
+
const fieldPath = rest.slice(dotIdx + 1)
|
|
58
|
+
if (!byEntryId[entryId]) byEntryId[entryId] = {}
|
|
59
|
+
byEntryId[entryId][fieldPath] = allParams[key]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const [entryId, fields] of Object.entries(byEntryId)) {
|
|
63
|
+
const existing = records.find(e => e.id === entryId)
|
|
64
|
+
if (existing) {
|
|
65
|
+
// Merge fields into existing entry
|
|
66
|
+
for (const [fieldPath, value] of Object.entries(fields)) {
|
|
67
|
+
setByPath(existing, fieldPath, value)
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// Create new entry and append
|
|
71
|
+
const newEntry = { id: entryId }
|
|
72
|
+
for (const [fieldPath, value] of Object.entries(fields)) {
|
|
73
|
+
setByPath(newEntry, fieldPath, value)
|
|
74
|
+
}
|
|
75
|
+
records.push(newEntry)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return records
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Loads a single record entry from a record collection, matched by URL param.
|
|
84
|
+
* Hash overrides are applied before lookup — both field overrides on existing
|
|
85
|
+
* entries and entirely new entries added via the URL are supported.
|
|
86
|
+
*
|
|
87
|
+
* The `paramName` serves double duty: it's both the route param to read from
|
|
88
|
+
* the URL and the record field to match against. This maps naturally to the
|
|
89
|
+
* file-based routing convention — `[id].jsx` matches entry.id,
|
|
90
|
+
* `[permalink].jsx` would match entry.permalink, etc.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} recordName - Name of the record file (e.g., "posts")
|
|
93
|
+
* @param {string} paramName - Route param name, also used as the entry field to match
|
|
94
|
+
* @returns {object|null} The matched record entry, or null if not found
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // In pages/issues/[id].jsx:
|
|
98
|
+
* const issue = useRecord('issues', 'id')
|
|
99
|
+
* // URL /issues/refactor-auth-sso → finds entry where entry.id === 'refactor-auth-sso'
|
|
100
|
+
*
|
|
101
|
+
* // In pages/posts/[permalink].jsx:
|
|
102
|
+
* const post = useRecord('posts', 'permalink')
|
|
103
|
+
* // URL /posts/hello-world → finds entry where entry.permalink === 'hello-world'
|
|
104
|
+
*/
|
|
105
|
+
export function useRecord(recordName, paramName = 'id') {
|
|
106
|
+
const params = useParams()
|
|
107
|
+
const paramValue = params[paramName]
|
|
108
|
+
const context = useContext(StoryboardContext)
|
|
109
|
+
const prototypeName = context?.prototypeName ?? null
|
|
110
|
+
|
|
111
|
+
// Re-render on hash or localStorage changes so overrides are reactive
|
|
112
|
+
const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
|
|
113
|
+
const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
|
|
114
|
+
|
|
115
|
+
return useMemo(() => {
|
|
116
|
+
if (!paramValue) return null
|
|
117
|
+
try {
|
|
118
|
+
const resolvedName = resolveRecordName(prototypeName, recordName)
|
|
119
|
+
const base = loadRecord(resolvedName)
|
|
120
|
+
const merged = applyRecordOverrides(base, resolvedName, recordName)
|
|
121
|
+
return merged.find(e => e[paramName] === paramValue) ?? null
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error(`[useRecord] ${err.message}`)
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
}, [recordName, paramName, paramValue, prototypeName, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Loads all entries from a record collection.
|
|
131
|
+
* Hash overrides are applied — existing entries can be modified and
|
|
132
|
+
* new entries can be created entirely from URL hash params.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} recordName - Name of the record file (e.g., "posts")
|
|
135
|
+
* @returns {Array} All record entries (with overrides applied)
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const allPosts = useRecords('posts')
|
|
139
|
+
*/
|
|
140
|
+
export function useRecords(recordName) {
|
|
141
|
+
const context = useContext(StoryboardContext)
|
|
142
|
+
const prototypeName = context?.prototypeName ?? null
|
|
143
|
+
|
|
144
|
+
// Re-render on hash or localStorage changes so overrides are reactive
|
|
145
|
+
const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
|
|
146
|
+
const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
|
|
147
|
+
|
|
148
|
+
return useMemo(() => {
|
|
149
|
+
try {
|
|
150
|
+
const resolvedName = resolveRecordName(prototypeName, recordName)
|
|
151
|
+
const base = loadRecord(resolvedName)
|
|
152
|
+
return applyRecordOverrides(base, resolvedName, recordName)
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error(`[useRecords] ${err.message}`)
|
|
155
|
+
return []
|
|
156
|
+
}
|
|
157
|
+
}, [recordName, prototypeName, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
158
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { renderHook, act } from '@testing-library/react'
|
|
3
|
+
import { seedTestData, TEST_RECORDS } from '../../test-utils.js'
|
|
4
|
+
import { activateHideMode, setShadow, init } from '../../core/index.js'
|
|
5
|
+
import { StoryboardContext } from '../StoryboardContext.js'
|
|
6
|
+
|
|
7
|
+
vi.mock('react-router-dom', async () => {
|
|
8
|
+
const actual = await vi.importActual('react-router-dom')
|
|
9
|
+
return { ...actual, useParams: vi.fn(() => ({})) }
|
|
10
|
+
})
|
|
11
|
+
import { useParams } from 'react-router-dom'
|
|
12
|
+
|
|
13
|
+
import { useRecord, useRecords } from './useRecord.js'
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
seedTestData()
|
|
17
|
+
useParams.mockReturnValue({})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a wrapper that provides StoryboardContext with a prototypeName,
|
|
22
|
+
* used for testing scoped (prototype-level) records.
|
|
23
|
+
*/
|
|
24
|
+
function createPrototypeWrapper(prototypeName) {
|
|
25
|
+
return function Wrapper({ children }) {
|
|
26
|
+
return React.createElement(
|
|
27
|
+
StoryboardContext.Provider,
|
|
28
|
+
{ value: { data: {}, prototypeName } },
|
|
29
|
+
children,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── useRecord ──
|
|
35
|
+
|
|
36
|
+
describe('useRecord', () => {
|
|
37
|
+
it('returns null when no URL param matches', () => {
|
|
38
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
39
|
+
expect(result.current).toBeNull()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('returns matching record entry when param is set', () => {
|
|
43
|
+
useParams.mockReturnValue({ id: 'post-1' })
|
|
44
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
45
|
+
expect(result.current).toEqual(TEST_RECORDS.posts[0])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('returns null when param value does not match any entry', () => {
|
|
49
|
+
useParams.mockReturnValue({ id: 'nonexistent' })
|
|
50
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
51
|
+
expect(result.current).toBeNull()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('defaults paramName to id', () => {
|
|
55
|
+
useParams.mockReturnValue({ id: 'post-2' })
|
|
56
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
57
|
+
expect(result.current).toEqual(TEST_RECORDS.posts[1])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('returns null gracefully when record collection does not exist', () => {
|
|
61
|
+
useParams.mockReturnValue({ id: 'post-1' })
|
|
62
|
+
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
63
|
+
const { result } = renderHook(() => useRecord('nonexistent'))
|
|
64
|
+
expect(result.current).toBeNull()
|
|
65
|
+
console.error.mockRestore()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// ── useRecords ──
|
|
70
|
+
|
|
71
|
+
describe('useRecords', () => {
|
|
72
|
+
it('returns all entries from a record collection', () => {
|
|
73
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
74
|
+
expect(result.current).toEqual(TEST_RECORDS.posts)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('returns empty array when record does not exist', () => {
|
|
78
|
+
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
79
|
+
const { result } = renderHook(() => useRecords('nonexistent'))
|
|
80
|
+
expect(result.current).toEqual([])
|
|
81
|
+
console.error.mockRestore()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('applies hash overrides to existing entries', () => {
|
|
85
|
+
window.location.hash = 'record.posts.post-1.title=Updated'
|
|
86
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
87
|
+
const post1 = result.current.find(e => e.id === 'post-1')
|
|
88
|
+
expect(post1.title).toBe('Updated')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('creates new entries from hash overrides', () => {
|
|
92
|
+
window.location.hash = 'record.posts.new-post.title=New'
|
|
93
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
94
|
+
const newPost = result.current.find(e => e.id === 'new-post')
|
|
95
|
+
expect(newPost).toBeTruthy()
|
|
96
|
+
expect(newPost.title).toBe('New')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// ── Hide mode ──
|
|
101
|
+
|
|
102
|
+
describe('useRecord (hide mode)', () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
act(() => { activateHideMode() })
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('reads overrides from localStorage shadow in hide mode', () => {
|
|
108
|
+
useParams.mockReturnValue({ id: 'post-1' })
|
|
109
|
+
act(() => { setShadow('record.posts.post-1.title', 'Shadow Title') })
|
|
110
|
+
|
|
111
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
112
|
+
expect(result.current.title).toBe('Shadow Title')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('reactively updates when shadow changes in hide mode', () => {
|
|
116
|
+
useParams.mockReturnValue({ id: 'post-1' })
|
|
117
|
+
const { result } = renderHook(() => useRecord('posts'))
|
|
118
|
+
expect(result.current.title).toBe('First Post')
|
|
119
|
+
|
|
120
|
+
act(() => { setShadow('record.posts.post-1.title', 'Updated via Shadow') })
|
|
121
|
+
expect(result.current.title).toBe('Updated via Shadow')
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('useRecords (hide mode)', () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
act(() => { activateHideMode() })
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('applies shadow overrides to existing entries', () => {
|
|
131
|
+
act(() => { setShadow('record.posts.post-1.title', 'Hidden Update') })
|
|
132
|
+
|
|
133
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
134
|
+
const post1 = result.current.find(e => e.id === 'post-1')
|
|
135
|
+
expect(post1.title).toBe('Hidden Update')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('creates new entries from shadow overrides', () => {
|
|
139
|
+
act(() => { setShadow('record.posts.shadow-post.title', 'New Shadow') })
|
|
140
|
+
|
|
141
|
+
const { result } = renderHook(() => useRecords('posts'))
|
|
142
|
+
const newPost = result.current.find(e => e.id === 'shadow-post')
|
|
143
|
+
expect(newPost).toBeTruthy()
|
|
144
|
+
expect(newPost.title).toBe('New Shadow')
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// ── Scoped (prototype) records ──
|
|
149
|
+
|
|
150
|
+
const SCOPED_RECORDS = {
|
|
151
|
+
'security/rules': [
|
|
152
|
+
{ id: 'constant-condition', title: 'Constant Condition', state: 'open' },
|
|
153
|
+
{ id: 'unused-var', title: 'Unused Variable', state: 'open' },
|
|
154
|
+
],
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function seedScopedData() {
|
|
158
|
+
init({
|
|
159
|
+
flows: {},
|
|
160
|
+
objects: {},
|
|
161
|
+
records: SCOPED_RECORDS,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
describe('useRecords (scoped records)', () => {
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
seedScopedData()
|
|
168
|
+
window.location.hash = ''
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('applies overrides written with the plain (unscoped) record name', () => {
|
|
172
|
+
// Callers write: record.rules.constant-condition.state=dismissed
|
|
173
|
+
// Reader resolves to "security/rules" — this was the bug
|
|
174
|
+
window.location.hash = 'record.rules.constant-condition.state=dismissed'
|
|
175
|
+
|
|
176
|
+
const wrapper = createPrototypeWrapper('security')
|
|
177
|
+
const { result } = renderHook(() => useRecords('rules'), { wrapper })
|
|
178
|
+
|
|
179
|
+
const rule = result.current.find(e => e.id === 'constant-condition')
|
|
180
|
+
expect(rule.state).toBe('dismissed')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('applies overrides written with the resolved (scoped) record name', () => {
|
|
184
|
+
window.location.hash = 'record.security/rules.constant-condition.state=dismissed'
|
|
185
|
+
|
|
186
|
+
const wrapper = createPrototypeWrapper('security')
|
|
187
|
+
const { result } = renderHook(() => useRecords('rules'), { wrapper })
|
|
188
|
+
|
|
189
|
+
const rule = result.current.find(e => e.id === 'constant-condition')
|
|
190
|
+
expect(rule.state).toBe('dismissed')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('merges overrides from both plain and scoped prefixes', () => {
|
|
194
|
+
window.location.hash =
|
|
195
|
+
'record.rules.constant-condition.state=dismissed' +
|
|
196
|
+
'&record.security/rules.unused-var.state=resolved'
|
|
197
|
+
|
|
198
|
+
const wrapper = createPrototypeWrapper('security')
|
|
199
|
+
const { result } = renderHook(() => useRecords('rules'), { wrapper })
|
|
200
|
+
|
|
201
|
+
expect(result.current.find(e => e.id === 'constant-condition').state).toBe('dismissed')
|
|
202
|
+
expect(result.current.find(e => e.id === 'unused-var').state).toBe('resolved')
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('useRecord (scoped records)', () => {
|
|
207
|
+
beforeEach(() => {
|
|
208
|
+
seedScopedData()
|
|
209
|
+
window.location.hash = ''
|
|
210
|
+
useParams.mockReturnValue({ id: 'constant-condition' })
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('applies overrides written with the plain (unscoped) record name', () => {
|
|
214
|
+
window.location.hash = 'record.rules.constant-condition.state=dismissed'
|
|
215
|
+
|
|
216
|
+
const wrapper = createPrototypeWrapper('security')
|
|
217
|
+
const { result } = renderHook(() => useRecord('rules'), { wrapper })
|
|
218
|
+
|
|
219
|
+
expect(result.current.state).toBe('dismissed')
|
|
220
|
+
})
|
|
221
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useContext, useCallback } from 'react'
|
|
2
|
+
import { StoryboardContext } from '../StoryboardContext.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read the current flow name and programmatically switch flows.
|
|
6
|
+
*
|
|
7
|
+
* @returns {{ flowName: string, switchFlow: (name: string) => void }}
|
|
8
|
+
* - flowName – current active flow (e.g. "default")
|
|
9
|
+
* - switchFlow – navigate to a different flow by updating ?flow= param
|
|
10
|
+
*/
|
|
11
|
+
export function useFlow() {
|
|
12
|
+
const context = useContext(StoryboardContext)
|
|
13
|
+
if (context === null) {
|
|
14
|
+
throw new Error('useFlow must be used within a <StoryboardProvider>')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const switchFlow = useCallback((name) => {
|
|
18
|
+
const url = new URL(window.location.href)
|
|
19
|
+
url.searchParams.delete('scene')
|
|
20
|
+
url.searchParams.set('flow', name)
|
|
21
|
+
// Preserve hash params across flow switches
|
|
22
|
+
window.location.href = url.toString()
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
flowName: context.flowName,
|
|
27
|
+
switchFlow,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @deprecated Use useFlow() */
|
|
32
|
+
export function useScene() {
|
|
33
|
+
const { flowName, switchFlow } = useFlow()
|
|
34
|
+
return {
|
|
35
|
+
sceneName: flowName,
|
|
36
|
+
switchScene: switchFlow,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react'
|
|
2
|
+
import { useFlow } from './useScene.js'
|
|
3
|
+
import { useScene } from './useScene.js'
|
|
4
|
+
import { seedTestData, createWrapper, TEST_FLOWS } from '../test-utils.js'
|
|
5
|
+
|
|
6
|
+
const flowData = TEST_FLOWS.default
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
seedTestData()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('useFlow', () => {
|
|
13
|
+
it('returns { flowName, switchFlow }', () => {
|
|
14
|
+
const { result } = renderHook(() => useFlow(), {
|
|
15
|
+
wrapper: createWrapper(flowData),
|
|
16
|
+
})
|
|
17
|
+
expect(result.current).toHaveProperty('flowName')
|
|
18
|
+
expect(result.current).toHaveProperty('switchFlow')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('flowName matches the value from context', () => {
|
|
22
|
+
const { result } = renderHook(() => useFlow(), {
|
|
23
|
+
wrapper: createWrapper(flowData, 'other'),
|
|
24
|
+
})
|
|
25
|
+
expect(result.current.flowName).toBe('other')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('switchFlow is a function', () => {
|
|
29
|
+
const { result } = renderHook(() => useFlow(), {
|
|
30
|
+
wrapper: createWrapper(flowData),
|
|
31
|
+
})
|
|
32
|
+
expect(typeof result.current.switchFlow).toBe('function')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('throws when used outside StoryboardProvider', () => {
|
|
36
|
+
expect(() => {
|
|
37
|
+
renderHook(() => useFlow())
|
|
38
|
+
}).toThrow('useFlow must be used within a <StoryboardProvider>')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// ── useScene (deprecated alias) ──
|
|
43
|
+
|
|
44
|
+
describe('useScene (deprecated alias)', () => {
|
|
45
|
+
it('returns { sceneName, switchScene }', () => {
|
|
46
|
+
const { result } = renderHook(() => useScene(), {
|
|
47
|
+
wrapper: createWrapper(flowData),
|
|
48
|
+
})
|
|
49
|
+
expect(result.current).toHaveProperty('sceneName')
|
|
50
|
+
expect(result.current).toHaveProperty('switchScene')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('sceneName matches the flow name from context', () => {
|
|
54
|
+
const { result } = renderHook(() => useScene(), {
|
|
55
|
+
wrapper: createWrapper(flowData, 'other'),
|
|
56
|
+
})
|
|
57
|
+
expect(result.current.sceneName).toBe('other')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('switchScene is a function', () => {
|
|
61
|
+
const { result } = renderHook(() => useScene(), {
|
|
62
|
+
wrapper: createWrapper(flowData),
|
|
63
|
+
})
|
|
64
|
+
expect(typeof result.current.switchScene).toBe('function')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useContext, useMemo, useSyncExternalStore } from 'react'
|
|
2
|
+
import { StoryboardContext } from '../StoryboardContext.js'
|
|
3
|
+
import { getByPath, deepClone, setByPath } from '../../core/index.js'
|
|
4
|
+
import { getParam, getAllParams } from '../../core/index.js'
|
|
5
|
+
import { subscribeToHash, getHashSnapshot } from '../../core/index.js'
|
|
6
|
+
import { isHideMode, getShadow, getAllShadows } from '../../core/index.js'
|
|
7
|
+
import { subscribeToStorage, getStorageSnapshot } from '../../core/index.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Access flow data by dot-notation path.
|
|
11
|
+
* Hash params override flow data — both exact matches and nested paths.
|
|
12
|
+
*
|
|
13
|
+
* Examples:
|
|
14
|
+
* useFlowData('user.name') with #user.name=Alice → "Alice"
|
|
15
|
+
* useFlowData('repositories') with #repositories.0.name=Foo
|
|
16
|
+
* → deep clone of repositories array with [0].name overridden to "Foo"
|
|
17
|
+
*
|
|
18
|
+
* @param {string} [path] - Dot-notation path (e.g. 'user.profile.name').
|
|
19
|
+
* Omit to get the entire flow object.
|
|
20
|
+
* @param {{ optional?: boolean }} [opts] - Pass { optional: true } to suppress
|
|
21
|
+
* the "path not found" warning for optional data.
|
|
22
|
+
* @returns {*} The resolved value. Returns {} if path is missing after loading.
|
|
23
|
+
* @throws If used outside a StoryboardProvider.
|
|
24
|
+
*/
|
|
25
|
+
export function useFlowData(path, opts) {
|
|
26
|
+
const context = useContext(StoryboardContext)
|
|
27
|
+
|
|
28
|
+
if (context === null) {
|
|
29
|
+
throw new Error('useFlowData must be used within a <StoryboardProvider>')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { data, loading, error } = context
|
|
33
|
+
|
|
34
|
+
// Re-render on any hash or localStorage change
|
|
35
|
+
const hashString = useSyncExternalStore(subscribeToHash, getHashSnapshot)
|
|
36
|
+
const storageString = useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
|
|
37
|
+
|
|
38
|
+
// Collect overrides relevant to this path
|
|
39
|
+
// eslint-disable-next-line react-hooks/preserve-manual-memoization
|
|
40
|
+
const result = useMemo(() => {
|
|
41
|
+
if (loading || error || data == null) return undefined
|
|
42
|
+
|
|
43
|
+
const hidden = isHideMode()
|
|
44
|
+
// In hide mode, read from shadow localStorage; otherwise from URL hash
|
|
45
|
+
const readParam = hidden ? getShadow : getParam
|
|
46
|
+
const readAllParams = hidden ? getAllShadows : getAllParams
|
|
47
|
+
|
|
48
|
+
if (!path) {
|
|
49
|
+
// No path → return full scene data with all overrides applied
|
|
50
|
+
const allParams = readAllParams()
|
|
51
|
+
const keys = Object.keys(allParams)
|
|
52
|
+
if (keys.length === 0) return data
|
|
53
|
+
const merged = deepClone(data)
|
|
54
|
+
for (const key of keys) setByPath(merged, key, allParams[key])
|
|
55
|
+
return merged
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Exact match: param directly for this path
|
|
59
|
+
const exact = readParam(path)
|
|
60
|
+
if (exact !== null) return exact
|
|
61
|
+
|
|
62
|
+
// Child overrides: params that are nested under this path
|
|
63
|
+
const prefix = path + '.'
|
|
64
|
+
const allParams = readAllParams()
|
|
65
|
+
const childKeys = Object.keys(allParams).filter(k => k.startsWith(prefix))
|
|
66
|
+
|
|
67
|
+
const sceneValue = getByPath(data, path)
|
|
68
|
+
|
|
69
|
+
if (childKeys.length > 0 && sceneValue !== undefined) {
|
|
70
|
+
const merged = deepClone(sceneValue)
|
|
71
|
+
for (const key of childKeys) {
|
|
72
|
+
const relativePath = key.slice(prefix.length)
|
|
73
|
+
setByPath(merged, relativePath, allParams[key])
|
|
74
|
+
}
|
|
75
|
+
return merged
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (sceneValue === undefined) {
|
|
79
|
+
if (!opts?.optional && data != null && Object.keys(data).length > 0) {
|
|
80
|
+
console.warn(`[useFlowData] Path "${path}" not found in flow data.`)
|
|
81
|
+
}
|
|
82
|
+
return {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return sceneValue
|
|
86
|
+
}, [data, loading, error, path, hashString, storageString]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @deprecated Use useFlowData() */
|
|
92
|
+
export const useSceneData = useFlowData
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns true while flow data is still loading.
|
|
96
|
+
*/
|
|
97
|
+
export function useFlowLoading() {
|
|
98
|
+
const context = useContext(StoryboardContext)
|
|
99
|
+
|
|
100
|
+
if (context === null) {
|
|
101
|
+
throw new Error('useFlowLoading must be used within a <StoryboardProvider>')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return context.loading
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @deprecated Use useFlowLoading() */
|
|
108
|
+
export const useSceneLoading = useFlowLoading
|