@dfosco/storyboard 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/favicon.svg +12 -0
- package/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +13907 -10816
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +138 -127
- package/scaffold/AGENTS.md +9 -7
- package/scaffold/README.md +18 -0
- package/scaffold/deploy.yml +21 -0
- package/scaffold/gitignore +8 -14
- package/scaffold/preview.yml +40 -0
- package/scaffold/{manifest.json → scaffold.config.json} +73 -3
- package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
- package/scaffold/skills/canvas/SKILL.md +1 -1
- package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
- package/scaffold/skills/ship/SKILL.md +2 -2
- package/scaffold/skills/worktree/SKILL.md +1 -1
- package/scaffold/src/_prototype.jsx +17 -0
- package/scaffold/src/_story.jsx +13 -0
- package/scaffold/src/library/README.md +34 -0
- package/scaffold/src/library/_app.jsx +53 -0
- package/scaffold/src/library/_app.module.css +8 -0
- package/scaffold/src/library/create.jsx +10 -0
- package/scaffold/src/library/index.jsx +14 -0
- package/scaffold/src/library/mount.jsx +52 -0
- package/scaffold/src/library/prototypes-entry.jsx +57 -0
- package/scaffold/src/library/routes.jsx +205 -0
- package/scaffold/src/library/viewfinder.jsx +13 -0
- package/scaffold/src/library/workspace.jsx +17 -0
- package/scripts/aggregate-artifacts.mjs +257 -0
- package/src/core/artifact/operations.js +19 -13
- package/src/core/autosync/server.test.js +1 -1
- package/src/core/canvas/githubEmbeds.js +12 -0
- package/src/core/canvas/githubEmbeds.test.js +71 -2
- package/src/core/canvas/hot-pool.js +10 -11
- package/src/core/canvas/materializer.js +39 -13
- package/src/core/canvas/server.js +324 -69
- package/src/core/canvas/server.test.js +365 -2
- package/src/core/canvas/terminal-server.js +144 -57
- package/src/core/canvas/undoRedo.js +174 -0
- package/src/core/canvas/undoRedo.test.js +250 -0
- package/src/core/cli/buildManifest.js +34 -0
- package/src/core/cli/canvasRedo.js +44 -0
- package/src/core/cli/canvasUndo.js +40 -0
- package/src/core/cli/create.js +116 -18
- package/src/core/cli/dev.js +23 -35
- package/src/core/cli/index.js +21 -0
- package/src/core/cli/schemas.js +2 -3
- package/src/core/cli/serverUrl.js +24 -0
- package/src/core/cli/setup.js +14 -25
- package/src/core/cli/terminal-welcome.js +12 -6
- package/src/core/cli/updateVersion.js +42 -2
- package/src/core/cli/workleaf.js +140 -0
- package/src/core/comments/api.js +1 -1
- package/src/core/comments/queries.js +3 -0
- package/src/core/comments/ui/CommentWindow.jsx +3 -2
- package/src/core/data/artifactIndex.js +235 -0
- package/src/core/data/artifactIndex.test.js +203 -0
- package/src/core/data/artifactManifest.js +290 -0
- package/src/core/data/artifactManifest.test.js +207 -0
- package/src/core/data/viewfinder.js +47 -7
- package/src/core/index.js +6 -1
- package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
- package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
- package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
- package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
- package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
- package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
- package/src/core/lib/components/ui/dialog/index.js +4 -34
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
- package/src/core/lib/components/ui/label/label.jsx +1 -2
- package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
- package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
- package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
- package/src/core/lib/components/ui/panel/panel.jsx +11 -2
- package/src/core/lib/components/ui/separator/separator.jsx +6 -4
- package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
- package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
- package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
- package/src/core/mountStoryboardCore.js +234 -74
- package/src/core/mountStoryboardCore.test.js +174 -0
- package/src/core/rename-watcher/watcher.js +329 -36
- package/src/core/rename-watcher/watcher.test.js +253 -0
- package/src/core/scaffold/fragments.js +287 -0
- package/src/core/scaffold/fragments.test.js +401 -0
- package/src/core/scaffold/index.js +291 -0
- package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
- package/src/core/scaffold/scaffold-integration.test.js +247 -0
- package/src/core/scaffold.js +4 -97
- package/src/core/stores/configSchema.js +41 -2
- package/src/core/stores/recentArtifacts.js +49 -2
- package/src/core/stores/recentArtifacts.test.js +59 -6
- package/src/core/stores/themeBootstrap.js +264 -0
- package/src/core/stores/themeBootstrap.test.js +211 -0
- package/src/core/stores/themeStore.test.js +154 -0
- package/src/core/stores/themeStore.ts +235 -220
- package/src/core/styles/generateThemesCss.js +63 -0
- package/src/core/styles/tailwind.css +12 -4
- package/src/core/styles/workspace-chrome.css +30 -0
- package/src/core/tools/handlers/paletteTheme.js +11 -8
- package/src/core/ui/ActionMenuButton.jsx +2 -1
- package/src/core/ui/CanvasCreateMenu.jsx +1 -19
- package/src/core/ui/CoreUIBar.jsx +10 -7
- package/src/core/ui/ThemeMenuButton.jsx +45 -30
- package/src/core/vite/server-plugin.js +36 -0
- package/src/core/workleaf/cleanup.js +102 -0
- package/src/core/workleaf/create.js +199 -0
- package/src/core/workleaf/delete.js +101 -0
- package/src/core/workleaf/list.js +57 -0
- package/src/core/workleaf/merge.js +238 -0
- package/src/core/workleaf/paths.js +146 -0
- package/src/core/workleaf/paths.test.js +230 -0
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +28 -21
- package/src/core/workshop/features/createFlow/server.js +5 -1
- package/src/core/workshop/features/createPrototype/server.js +5 -1
- package/src/core/workshop/features/templateIndex.js +28 -1
- package/src/core/worktree/port.js +48 -5
- package/src/core/worktree/port.test.js +42 -0
- package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
- package/src/internals/ArtifactForm/ArtifactForm.module.css +48 -66
- package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
- package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
- package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +25 -19
- package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
- package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
- package/src/internals/CommandPalette/createHelpers.js +30 -0
- package/src/internals/CreatePage/CreatePage.jsx +57 -0
- package/src/internals/CreatePage/CreatePage.module.css +34 -0
- package/src/internals/Dialog.jsx +51 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +533 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
- package/src/internals/Tooltip.jsx +39 -0
- package/src/internals/Viewfinder.jsx +196 -48
- package/src/internals/Viewfinder.module.css +72 -1
- package/src/internals/canvas/CanvasPage.jsx +267 -124
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
- package/src/internals/canvas/PageSelector.jsx +15 -1
- package/src/internals/canvas/PageSelector.module.css +14 -0
- package/src/internals/canvas/canvasApi.js +40 -5
- package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
- package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
- package/src/internals/canvas/isolation/prototypeRoutes.jsx +131 -0
- package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
- package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
- package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
- package/src/internals/canvas/useUndoRedo.js +75 -60
- package/src/internals/canvas/useUndoRedo.test.js +46 -178
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
- package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
- package/src/internals/canvas/widgets/LinkPreview.jsx +45 -1
- package/src/internals/canvas/widgets/LinkPreview.module.css +71 -0
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +69 -10
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +114 -0
- package/src/internals/canvas/widgets/StorySetWidget.jsx +8 -4
- package/src/internals/canvas/widgets/StoryWidget.jsx +21 -38
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
- package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
- package/src/internals/canvas/widgets/WidgetChrome.module.css +15 -0
- package/src/internals/canvas/widgets/embedTheme.js +2 -2
- package/src/internals/canvas/widgets/expandUtils.js +19 -19
- package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
- package/src/internals/context.jsx +22 -1
- package/src/internals/context.test.jsx +47 -0
- package/src/internals/hooks/useArtifactIndex.js +31 -0
- package/src/internals/hooks/useArtifactIndex.test.js +45 -0
- package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
- package/src/internals/index.js +6 -0
- package/src/internals/loadConsumerWrapper.js +60 -0
- package/src/internals/story/ComponentSetPage.jsx +75 -34
- package/src/internals/story/StoryPage.jsx +16 -25
- package/src/internals/vite/data-plugin.js +313 -148
- package/src/internals/vite/data-plugin.test.js +11 -11
- package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
- package/terminal.config.json +1 -1
- package/src/core/lib/components/ui/card/card-action.jsx +0 -11
- package/src/core/lib/components/ui/card/card-content.jsx +0 -11
- package/src/core/lib/components/ui/card/card-description.jsx +0 -11
- package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
- package/src/core/lib/components/ui/card/card-header.jsx +0 -19
- package/src/core/lib/components/ui/card/card-title.jsx +0 -11
- package/src/core/lib/components/ui/card/card.jsx +0 -17
- package/src/core/lib/components/ui/card/index.js +0 -25
- package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/index.js +0 -13
- package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
- package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
- package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
- package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
- package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
- package/src/core/lib/components/ui/popover/index.js +0 -28
- package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
- package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
- package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
- package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
- package/src/core/lib/components/ui/popover/popover.jsx +0 -4
- package/src/core/lib/components/ui/select/index.js +0 -37
- package/src/core/lib/components/ui/select/select-content.jsx +0 -30
- package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
- package/src/core/lib/components/ui/select/select-group.jsx +0 -15
- package/src/core/lib/components/ui/select/select-item.jsx +0 -26
- package/src/core/lib/components/ui/select/select-label.jsx +0 -11
- package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
- package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
- package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
- package/src/core/lib/components/ui/select/select.jsx +0 -4
- package/src/core/lib/components/ui/sheet/index.js +0 -34
- package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
- package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
- package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
- package/src/core/lib/components/ui/toggle/index.js +0 -8
- package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
- package/src/core/lib/components/ui/toggle-group/index.js +0 -10
- package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
- package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
- package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
- package/src/internals/canvas/componentIsolate.jsx +0 -144
- package/src/internals/canvas/componentSetIsolate.jsx +0 -300
- package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
- package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
- package/src/primer/ThemeSync.jsx +0 -73
- package/src/primer/index.js +0 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import process from 'process'
|
|
6
|
+
|
|
7
|
+
const INDEX_FILENAME = 'artifacts.index.json'
|
|
8
|
+
const TMP_FILENAME = '.artifacts.index.json.tmp'
|
|
9
|
+
const SCHEMA_VERSION = 1
|
|
10
|
+
|
|
11
|
+
function compareBranchNames(a, b) {
|
|
12
|
+
if (a.branch === 'main' && b.branch !== 'main') return -1
|
|
13
|
+
if (b.branch === 'main' && a.branch !== 'main') return 1
|
|
14
|
+
return String(a.branch || '').localeCompare(String(b.branch || ''))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function withTrailingSlash(folder) {
|
|
18
|
+
if (!folder) return ''
|
|
19
|
+
return folder.endsWith('/') ? folder : `${folder}/`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function generatedAtMs(manifest) {
|
|
23
|
+
const value = Date.parse(manifest?.generatedAt || '')
|
|
24
|
+
return Number.isNaN(value) ? 0 : value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function deriveFolder(manifest, dir, ghPagesDir) {
|
|
28
|
+
if (dir === ghPagesDir) return ''
|
|
29
|
+
if (typeof manifest.folder === 'string') return manifest.folder
|
|
30
|
+
return path.basename(dir)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deriveBranch(manifest, folder) {
|
|
34
|
+
if (!folder) return 'main'
|
|
35
|
+
if (typeof manifest.branch === 'string' && manifest.branch) return manifest.branch
|
|
36
|
+
return manifest.deployBranch || folder.replace(/^branch--/, '')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deriveDeployBranch(manifest, branch, folder) {
|
|
40
|
+
if (!folder || branch === 'main') return 'main'
|
|
41
|
+
if (typeof manifest.deployBranch === 'string' && manifest.deployBranch) return manifest.deployBranch
|
|
42
|
+
return folder.replace(/^branch--/, '')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readManifest(dir) {
|
|
46
|
+
const manifestPath = path.join(dir, 'artifacts.json')
|
|
47
|
+
const label = path.basename(dir) || dir
|
|
48
|
+
|
|
49
|
+
let raw
|
|
50
|
+
try {
|
|
51
|
+
raw = fs.readFileSync(manifestPath, 'utf8')
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err?.code === 'ENOENT') {
|
|
54
|
+
console.info(`[aggregate-artifacts] Skipping ${label}: artifacts.json not found`)
|
|
55
|
+
return { manifest: null, readError: false }
|
|
56
|
+
}
|
|
57
|
+
console.warn(`[aggregate-artifacts] Skipping ${label}: could not read artifacts.json (${err.message})`)
|
|
58
|
+
return { manifest: null, readError: true }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let manifest
|
|
62
|
+
try {
|
|
63
|
+
manifest = JSON.parse(raw)
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn(`[aggregate-artifacts] Skipping ${label}: malformed artifacts.json (${err.message})`)
|
|
66
|
+
return { manifest: null, readError: false }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (manifest?.schemaVersion !== SCHEMA_VERSION) {
|
|
70
|
+
console.warn(`[aggregate-artifacts] Skipping ${label}: unsupported schemaVersion ${manifest?.schemaVersion}`)
|
|
71
|
+
return { manifest: null, readError: false }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { manifest, readError: false }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function listManifestDirs(ghPagesDir) {
|
|
78
|
+
const entries = fs.readdirSync(ghPagesDir, { withFileTypes: true })
|
|
79
|
+
const branchDirs = entries
|
|
80
|
+
.filter(entry => entry.isDirectory() && entry.name.startsWith('branch--'))
|
|
81
|
+
.map(entry => path.join(ghPagesDir, entry.name))
|
|
82
|
+
.sort((a, b) => path.basename(a).localeCompare(path.basename(b)))
|
|
83
|
+
|
|
84
|
+
return [ghPagesDir, ...branchDirs]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function collectManifests(ghPagesDir) {
|
|
88
|
+
const manifestsByFolder = new Map()
|
|
89
|
+
let readErrorCount = 0
|
|
90
|
+
|
|
91
|
+
for (const dir of listManifestDirs(ghPagesDir)) {
|
|
92
|
+
const { manifest, readError } = readManifest(dir)
|
|
93
|
+
if (readError) readErrorCount += 1
|
|
94
|
+
if (!manifest) continue
|
|
95
|
+
|
|
96
|
+
const folder = deriveFolder(manifest, dir, ghPagesDir)
|
|
97
|
+
const current = { manifest, dir, folder }
|
|
98
|
+
const previous = manifestsByFolder.get(folder)
|
|
99
|
+
|
|
100
|
+
if (!previous) {
|
|
101
|
+
manifestsByFolder.set(folder, current)
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const previousBranch = deriveBranch(previous.manifest, previous.folder)
|
|
106
|
+
const currentBranch = deriveBranch(manifest, folder)
|
|
107
|
+
const keepCurrent = generatedAtMs(manifest) > generatedAtMs(previous.manifest)
|
|
108
|
+
const keptBranch = keepCurrent ? currentBranch : previousBranch
|
|
109
|
+
|
|
110
|
+
console.warn(
|
|
111
|
+
`[aggregate-artifacts] Branch folder collision for "${folder || '(root)'}" between "${previousBranch}" and "${currentBranch}"; keeping "${keptBranch}"`,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (keepCurrent) manifestsByFolder.set(folder, current)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (readErrorCount > 1) {
|
|
118
|
+
throw new Error(`Failed to read ${readErrorCount} artifacts manifests; preserving existing ${INDEX_FILENAME}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const manifests = [...manifestsByFolder.values()]
|
|
122
|
+
if (manifests.length === 0) {
|
|
123
|
+
throw new Error(`No readable artifacts.json manifests found; preserving existing ${INDEX_FILENAME}`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return manifests
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function addPrototype(index, manifestInfo, item) {
|
|
130
|
+
const id = item?.artifactId || item?.id
|
|
131
|
+
if (!id) return
|
|
132
|
+
|
|
133
|
+
const { manifest, folder } = manifestInfo
|
|
134
|
+
const branch = deriveBranch(manifest, folder)
|
|
135
|
+
const key = String(id)
|
|
136
|
+
|
|
137
|
+
if (!index.prototypes[key]) {
|
|
138
|
+
index.prototypes[key] = { id: key, branches: [] }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
index.prototypes[key].branches.push({
|
|
142
|
+
branch,
|
|
143
|
+
folder: withTrailingSlash(folder),
|
|
144
|
+
title: item.title ?? item.id ?? key,
|
|
145
|
+
route: item.route ?? null,
|
|
146
|
+
isExternal: Boolean(item.isExternal),
|
|
147
|
+
externalUrl: item.externalUrl ?? null,
|
|
148
|
+
folderName: item.folder ?? null,
|
|
149
|
+
sha: manifest.sha ?? null,
|
|
150
|
+
lastModified: item.lastModified ?? null,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function addCanvas(index, manifestInfo, item) {
|
|
155
|
+
const id = item?.artifactId || item?.id
|
|
156
|
+
if (!id) return
|
|
157
|
+
|
|
158
|
+
const { manifest, folder } = manifestInfo
|
|
159
|
+
const branch = deriveBranch(manifest, folder)
|
|
160
|
+
const key = String(id)
|
|
161
|
+
|
|
162
|
+
if (!index.canvases[key]) {
|
|
163
|
+
index.canvases[key] = { id: key, branches: [] }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
index.canvases[key].branches.push({
|
|
167
|
+
branch,
|
|
168
|
+
folder: withTrailingSlash(folder),
|
|
169
|
+
name: item.name ?? item.id ?? key,
|
|
170
|
+
route: item.route ?? null,
|
|
171
|
+
folderName: item.folder ?? null,
|
|
172
|
+
sha: manifest.sha ?? null,
|
|
173
|
+
lastModified: item.lastModified ?? null,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sortArtifactMap(map) {
|
|
178
|
+
const sorted = {}
|
|
179
|
+
|
|
180
|
+
for (const key of Object.keys(map).sort((a, b) => a.localeCompare(b))) {
|
|
181
|
+
sorted[key] = {
|
|
182
|
+
...map[key],
|
|
183
|
+
branches: [...map[key].branches].sort(compareBranchNames),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return sorted
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildIndex(manifests) {
|
|
191
|
+
const index = {
|
|
192
|
+
schemaVersion: SCHEMA_VERSION,
|
|
193
|
+
generatedAt: new Date().toISOString(),
|
|
194
|
+
branches: [],
|
|
195
|
+
prototypes: {},
|
|
196
|
+
canvases: {},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const manifestInfo of manifests) {
|
|
200
|
+
const { manifest, folder } = manifestInfo
|
|
201
|
+
const branch = deriveBranch(manifest, folder)
|
|
202
|
+
const deployBranch = deriveDeployBranch(manifest, branch, folder)
|
|
203
|
+
|
|
204
|
+
index.branches.push({ branch, deployBranch, folder })
|
|
205
|
+
|
|
206
|
+
for (const item of Array.isArray(manifest.prototypes) ? manifest.prototypes : []) {
|
|
207
|
+
addPrototype(index, manifestInfo, item)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const item of Array.isArray(manifest.canvases) ? manifest.canvases : []) {
|
|
211
|
+
addCanvas(index, manifestInfo, item)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
index.branches = [...index.branches].sort(compareBranchNames)
|
|
216
|
+
index.prototypes = sortArtifactMap(index.prototypes)
|
|
217
|
+
index.canvases = sortArtifactMap(index.canvases)
|
|
218
|
+
|
|
219
|
+
return index
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function writeIndex(ghPagesDir, index) {
|
|
223
|
+
const targetPath = path.join(ghPagesDir, INDEX_FILENAME)
|
|
224
|
+
const tmpPath = path.join(ghPagesDir, TMP_FILENAME)
|
|
225
|
+
const contents = `${JSON.stringify(index, null, 2)}\n`
|
|
226
|
+
|
|
227
|
+
fs.writeFileSync(tmpPath, contents, 'utf8')
|
|
228
|
+
fs.renameSync(tmpPath, targetPath)
|
|
229
|
+
|
|
230
|
+
return targetPath
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function aggregateArtifacts(ghPagesDir) {
|
|
234
|
+
const resolvedDir = path.resolve(ghPagesDir)
|
|
235
|
+
const manifests = collectManifests(resolvedDir)
|
|
236
|
+
const index = buildIndex(manifests)
|
|
237
|
+
return writeIndex(resolvedDir, index)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function main() {
|
|
241
|
+
const ghPagesDir = process.argv[2]
|
|
242
|
+
|
|
243
|
+
if (!ghPagesDir) {
|
|
244
|
+
console.error('Usage: node aggregate-artifacts.mjs <gh-pages-dir>')
|
|
245
|
+
process.exit(1)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const targetPath = aggregateArtifacts(ghPagesDir)
|
|
250
|
+
console.info(`[aggregate-artifacts] Wrote ${targetPath}`)
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error(`[aggregate-artifacts] ${err.message}`)
|
|
253
|
+
process.exit(1)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
main()
|
|
@@ -128,8 +128,6 @@ function createPrototype(values, root) {
|
|
|
128
128
|
? path.join(prototypesDir, `${values.folder}.folder`, values.name)
|
|
129
129
|
: path.join(prototypesDir, values.name)
|
|
130
130
|
|
|
131
|
-
fs.mkdirSync(targetDir, { recursive: true })
|
|
132
|
-
|
|
133
131
|
const files = []
|
|
134
132
|
const relDir = path.relative(root, targetDir)
|
|
135
133
|
|
|
@@ -148,6 +146,22 @@ function createPrototype(values, root) {
|
|
|
148
146
|
return { success: false, errors: [{ message: partialError }] }
|
|
149
147
|
}
|
|
150
148
|
|
|
149
|
+
// Pre-resolve the partial's component file *before* writing anything.
|
|
150
|
+
// If the partial's template/recipe directory is missing or empty we'd
|
|
151
|
+
// otherwise leave an orphan .prototype.json on disk and Vite would
|
|
152
|
+
// then try (and fail) to lazy-load a non-existent index.jsx. Validate
|
|
153
|
+
// first, write second.
|
|
154
|
+
let componentFile = null
|
|
155
|
+
if (partialEntry && !values.url) {
|
|
156
|
+
const partialDir = path.join(root, 'src', partialEntry.baseDir, partialEntry.name)
|
|
157
|
+
componentFile = findComponentFile(partialDir)
|
|
158
|
+
if (!componentFile) {
|
|
159
|
+
return { success: false, errors: [{ message: `No .jsx or .tsx file found in src/${partialEntry.baseDir}/${partialEntry.name}/` }] }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fs.mkdirSync(targetDir, { recursive: true })
|
|
164
|
+
|
|
151
165
|
// Write .prototype.json (with $global from partial when applicable)
|
|
152
166
|
const protoFile = `${values.name}.prototype.json`
|
|
153
167
|
const protoBody = JSON.parse(prototypeJson(values))
|
|
@@ -159,22 +173,14 @@ function createPrototype(values, root) {
|
|
|
159
173
|
|
|
160
174
|
// Write index.jsx (only for non-external prototypes)
|
|
161
175
|
if (!values.url) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const partialDir = path.join(root, 'src', partialEntry.baseDir, partialEntry.name)
|
|
165
|
-
const componentFile = findComponentFile(partialDir)
|
|
166
|
-
if (!componentFile) {
|
|
167
|
-
return { success: false, errors: [{ message: `No .jsx or .tsx file found in src/${partialEntry.baseDir}/${partialEntry.name}/` }] }
|
|
168
|
-
}
|
|
169
|
-
body = renderPartialIndexJsx({
|
|
176
|
+
const body = partialEntry
|
|
177
|
+
? renderPartialIndexJsx({
|
|
170
178
|
partialEntry,
|
|
171
179
|
componentFile,
|
|
172
180
|
componentName: toPascalCase(values.name),
|
|
173
181
|
title: values.title || values.name,
|
|
174
182
|
})
|
|
175
|
-
|
|
176
|
-
body = prototypeIndexJsx(values.name)
|
|
177
|
-
}
|
|
183
|
+
: prototypeIndexJsx(values.name)
|
|
178
184
|
fs.writeFileSync(path.join(targetDir, 'index.jsx'), body, 'utf-8')
|
|
179
185
|
files.push(`${relDir}/index.jsx`)
|
|
180
186
|
}
|
|
@@ -21,7 +21,7 @@ describe('autosync scope helpers', () => {
|
|
|
21
21
|
expect(matchesAutosyncScope('canvas', 'src/canvas/notes.txt')).toBe(true)
|
|
22
22
|
expect(matchesAutosyncScope('canvas', 'assets/canvas/images/photo.png')).toBe(true)
|
|
23
23
|
expect(matchesAutosyncScope('canvas', 'assets/canvas/snapshots/snapshot-widget--latest.webp')).toBe(true)
|
|
24
|
-
expect(matchesAutosyncScope('canvas', 'assets/.storyboard-public/terminal-snapshots/
|
|
24
|
+
expect(matchesAutosyncScope('canvas', 'assets/.storyboard-public/terminal-snapshots/agents.snapshot.json')).toBe(true)
|
|
25
25
|
expect(matchesAutosyncScope('canvas', 'src/prototypes/demo/default.flow.json')).toBe(false)
|
|
26
26
|
})
|
|
27
27
|
|
|
@@ -153,6 +153,10 @@ function toContext(target) {
|
|
|
153
153
|
|
|
154
154
|
function buildPullRequestSnapshot(target, pr) {
|
|
155
155
|
const number = pr?.number ?? target.number
|
|
156
|
+
const merged = Boolean(pr?.merged_at || pr?.merged)
|
|
157
|
+
const draft = Boolean(pr?.draft)
|
|
158
|
+
const rawState = pr?.state === 'closed' ? 'closed' : 'open'
|
|
159
|
+
const state = merged ? 'merged' : (draft && rawState === 'open' ? 'draft' : rawState)
|
|
156
160
|
return {
|
|
157
161
|
kind: 'pull_request',
|
|
158
162
|
parentKind: 'pull_request',
|
|
@@ -164,6 +168,14 @@ function buildPullRequestSnapshot(target, pr) {
|
|
|
164
168
|
createdAt: pr?.created_at ?? null,
|
|
165
169
|
updatedAt: pr?.updated_at ?? null,
|
|
166
170
|
url: target.url,
|
|
171
|
+
state,
|
|
172
|
+
merged,
|
|
173
|
+
draft,
|
|
174
|
+
baseRef: typeof pr?.base?.ref === 'string' ? pr.base.ref : null,
|
|
175
|
+
headRef: typeof pr?.head?.ref === 'string' ? pr.head.ref : null,
|
|
176
|
+
additions: typeof pr?.additions === 'number' ? pr.additions : null,
|
|
177
|
+
deletions: typeof pr?.deletions === 'number' ? pr.deletions : null,
|
|
178
|
+
changedFiles: typeof pr?.changed_files === 'number' ? pr.changed_files : null,
|
|
167
179
|
}
|
|
168
180
|
}
|
|
169
181
|
|
|
@@ -202,6 +202,76 @@ describe('fetchGitHubEmbedSnapshot', () => {
|
|
|
202
202
|
expect(graphqlArgs.slice(0, 2)).toEqual(['api', 'graphql'])
|
|
203
203
|
})
|
|
204
204
|
|
|
205
|
+
it('hydrates pull request snapshot metadata (state, branches, diff)', () => {
|
|
206
|
+
ghExec
|
|
207
|
+
.mockReturnValueOnce('gh version 2.58.0')
|
|
208
|
+
.mockReturnValueOnce(JSON.stringify({
|
|
209
|
+
number: 79,
|
|
210
|
+
title: 'fix(viewfinder): eliminate FOUC',
|
|
211
|
+
body: 'A PR body',
|
|
212
|
+
state: 'closed',
|
|
213
|
+
merged: true,
|
|
214
|
+
merged_at: '2026-04-10T00:00:00Z',
|
|
215
|
+
draft: false,
|
|
216
|
+
user: { login: 'dfosco' },
|
|
217
|
+
created_at: '2026-04-01T00:00:00Z',
|
|
218
|
+
updated_at: '2026-04-10T00:00:00Z',
|
|
219
|
+
base: { ref: '3.11.0' },
|
|
220
|
+
head: { ref: '3.11.0--fix-viewfinder-fouc' },
|
|
221
|
+
additions: 63,
|
|
222
|
+
deletions: 18,
|
|
223
|
+
changed_files: 4,
|
|
224
|
+
}))
|
|
225
|
+
|
|
226
|
+
const snapshot = fetchGitHubEmbedSnapshot('https://github.com/dfosco/storyboard/pull/79')
|
|
227
|
+
|
|
228
|
+
expect(snapshot).toMatchObject({
|
|
229
|
+
kind: 'pull_request',
|
|
230
|
+
parentKind: 'pull_request',
|
|
231
|
+
title: '#79 fix(viewfinder): eliminate FOUC',
|
|
232
|
+
state: 'merged',
|
|
233
|
+
merged: true,
|
|
234
|
+
draft: false,
|
|
235
|
+
baseRef: '3.11.0',
|
|
236
|
+
headRef: '3.11.0--fix-viewfinder-fouc',
|
|
237
|
+
additions: 63,
|
|
238
|
+
deletions: 18,
|
|
239
|
+
changedFiles: 4,
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('marks draft PRs and unmerged closed PRs correctly', () => {
|
|
244
|
+
ghExec
|
|
245
|
+
.mockReturnValueOnce('gh version 2.58.0')
|
|
246
|
+
.mockReturnValueOnce(JSON.stringify({
|
|
247
|
+
number: 5,
|
|
248
|
+
title: 'WIP: refactor',
|
|
249
|
+
state: 'open',
|
|
250
|
+
draft: true,
|
|
251
|
+
merged: false,
|
|
252
|
+
user: { login: 'dfosco' },
|
|
253
|
+
}))
|
|
254
|
+
|
|
255
|
+
const draftSnap = fetchGitHubEmbedSnapshot('https://github.com/dfosco/storyboard/pull/5')
|
|
256
|
+
expect(draftSnap.state).toBe('draft')
|
|
257
|
+
expect(draftSnap.draft).toBe(true)
|
|
258
|
+
expect(draftSnap.merged).toBe(false)
|
|
259
|
+
|
|
260
|
+
ghExec
|
|
261
|
+
.mockReturnValueOnce('gh version 2.58.0')
|
|
262
|
+
.mockReturnValueOnce(JSON.stringify({
|
|
263
|
+
number: 6,
|
|
264
|
+
state: 'closed',
|
|
265
|
+
merged: false,
|
|
266
|
+
merged_at: null,
|
|
267
|
+
user: { login: 'dfosco' },
|
|
268
|
+
}))
|
|
269
|
+
|
|
270
|
+
const closedSnap = fetchGitHubEmbedSnapshot('https://github.com/dfosco/storyboard/pull/6')
|
|
271
|
+
expect(closedSnap.state).toBe('closed')
|
|
272
|
+
expect(closedSnap.merged).toBe(false)
|
|
273
|
+
})
|
|
274
|
+
|
|
205
275
|
it('rejects issue comment URLs when comment parent does not match', () => {
|
|
206
276
|
ghExec
|
|
207
277
|
.mockReturnValueOnce('gh version 2.58.0')
|
|
@@ -270,8 +340,7 @@ describe('fetchGitHubEmbedSnapshot', () => {
|
|
|
270
340
|
})
|
|
271
341
|
})
|
|
272
342
|
|
|
273
|
-
it('hydrates PR top-level comment snapshot metadata', () => {
|
|
274
|
-
ghExec
|
|
343
|
+
it('hydrates PR top-level comment snapshot metadata', () => { ghExec
|
|
275
344
|
.mockReturnValueOnce('gh version 2.58.0')
|
|
276
345
|
.mockReturnValueOnce(JSON.stringify({
|
|
277
346
|
body: 'Looks good!',
|
|
@@ -551,8 +551,8 @@ export class HotPool {
|
|
|
551
551
|
const paneContent = execSync(
|
|
552
552
|
// H1: include scrollback so the readiness echo can still be
|
|
553
553
|
// matched after the agent's TUI repaints over it.
|
|
554
|
-
`tmux capture-pane -t "${tmuxName}" -p -S -200`,
|
|
555
|
-
{ encoding: 'utf8', timeout: 1000 }
|
|
554
|
+
`tmux capture-pane -t "${tmuxName}" -p -S -200 2>/dev/null`,
|
|
555
|
+
{ encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
556
556
|
)
|
|
557
557
|
// Strip ANSI escape sequences — agent CLIs use heavy formatting
|
|
558
558
|
// eslint-disable-next-line no-control-regex
|
|
@@ -580,14 +580,13 @@ export class HotPool {
|
|
|
580
580
|
return this.#tmuxSessionExists(tmuxName)
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
-
//
|
|
583
|
+
// postStartup is NOT sent here intentionally. It's deferred to widget
|
|
584
|
+
// handoff (see terminal-server.js warm path) so it runs *after* the
|
|
585
|
+
// identity/role injection — mode-toggling slash commands like
|
|
586
|
+
// `/autopilot` must come last so canvas context lands in conversation
|
|
587
|
+
// history before the agent enters autopilot mode.
|
|
584
588
|
if (postStartup) {
|
|
585
|
-
|
|
586
|
-
await new Promise(r => setTimeout(r, 500))
|
|
587
|
-
execSync(`tmux send-keys -t "${tmuxName}" -l ${JSON.stringify(postStartup)}`, { stdio: 'ignore' })
|
|
588
|
-
execSync(`tmux send-keys -t "${tmuxName}" Enter`, { stdio: 'ignore' })
|
|
589
|
-
this.#log(`⊕ AGENT ${sessionId} postStartup sent: ${postStartup}`)
|
|
590
|
-
} catch { /* empty */ }
|
|
589
|
+
this.#log(`⊕ AGENT ${sessionId} postStartup "${postStartup}" deferred to widget handoff`)
|
|
591
590
|
}
|
|
592
591
|
|
|
593
592
|
this.#log(`⊕ AGENT ${sessionId} ready (${signalFilePath ? 'file' : 'signal'}: "${readinessSignal || 'file'}")`)
|
|
@@ -615,8 +614,8 @@ export class HotPool {
|
|
|
615
614
|
if (this.#agentConfig?.startupCommand) {
|
|
616
615
|
try {
|
|
617
616
|
const cmd = execSync(
|
|
618
|
-
`tmux display-message -t "${session.tmuxName}" -p "#{pane_current_command}"`,
|
|
619
|
-
{ encoding: 'utf8', timeout: 1000 }
|
|
617
|
+
`tmux display-message -t "${session.tmuxName}" -p "#{pane_current_command}" 2>/dev/null`,
|
|
618
|
+
{ encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
620
619
|
).trim()
|
|
621
620
|
// Agent exited if the pane is back to a shell
|
|
622
621
|
const shells = ['zsh', 'bash', 'sh', 'fish']
|
|
@@ -7,17 +7,36 @@
|
|
|
7
7
|
* Event types:
|
|
8
8
|
* canvas_created — full initial state (first line)
|
|
9
9
|
* widget_added — append a widget
|
|
10
|
-
* widget_updated — patch widget props
|
|
11
|
-
* widget_moved — update widget position
|
|
12
|
-
* widget_removed — remove a widget by id
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* widget_updated — patch widget props (may carry `prevProps` for undo)
|
|
11
|
+
* widget_moved — update widget position (may carry `prevPosition` for undo)
|
|
12
|
+
* widget_removed — remove a widget by id (carries `widget` for undo;
|
|
13
|
+
* legacy events only had `widgetId`)
|
|
14
|
+
* settings_updated — patch canvas-level settings (may carry `prevSettings` for undo)
|
|
15
|
+
* source_updated — replace the sources array (may carry `prevSources` for undo)
|
|
16
|
+
* widgets_replaced — replace the entire widgets array (compaction only, not undoable)
|
|
16
17
|
* connector_added — append a connector between two widgets
|
|
17
|
-
* connector_removed — remove a connector by id
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* connector_removed — remove a connector by id (carries `connector` for undo;
|
|
19
|
+
* legacy events only had `connectorId`)
|
|
20
|
+
* connectors_replaced — replace the entire connectors array (compaction only, not undoable)
|
|
21
|
+
* connector_waypoints_set — set/replace manual routing waypoints (may carry `prevWaypoints`)
|
|
22
|
+
* connector_waypoints_cleared — drop manual routing (may carry `prevWaypoints`)
|
|
23
|
+
* connector_updated — patch connector anchors/meta (may carry `prevUpdates` for undo)
|
|
24
|
+
*
|
|
25
|
+
* Event metadata (all events):
|
|
26
|
+
* id — short random id stamped by the server, used by undo/redo
|
|
27
|
+
* to target a specific event. Legacy events without an id are
|
|
28
|
+
* non-undoable but materialize normally.
|
|
29
|
+
* meta.kind — present on undo/redo emissions ('undo' | 'redo'). The
|
|
30
|
+
* materializer ignores meta; it's informational for tools
|
|
31
|
+
* and a future timeline UI.
|
|
32
|
+
* meta.of — the id of the event this undo/redo targets.
|
|
33
|
+
*
|
|
34
|
+
* Undo model: the materializer never "skips" events. Undo is a normal
|
|
35
|
+
* forward event (a real `widget_added`/`widget_removed`/`widget_updated`)
|
|
36
|
+
* that happens to carry meta.kind === 'undo'. Folding is one pass, no
|
|
37
|
+
* special cases. Inverse payloads (`prevProps`, `prevPosition`, full
|
|
38
|
+
* `widget`/`connector` on removes, etc.) are read only by the server's
|
|
39
|
+
* POST /undo and POST /redo endpoints when computing the inverse event.
|
|
21
40
|
*/
|
|
22
41
|
|
|
23
42
|
/**
|
|
@@ -134,13 +153,16 @@ export function materialize(events) {
|
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
case 'widget_removed': {
|
|
156
|
+
// Back-compat: legacy events only had `widgetId`; new events carry
|
|
157
|
+
// the full `widget` object so undo can re-add without history walk.
|
|
158
|
+
const removedId = evt.widget?.id ?? evt.widgetId
|
|
137
159
|
state.widgets = (state.widgets || []).filter(
|
|
138
|
-
(w) => w.id !==
|
|
160
|
+
(w) => w.id !== removedId,
|
|
139
161
|
)
|
|
140
162
|
// Cascade: remove connectors referencing the deleted widget
|
|
141
163
|
if (state.connectors?.length) {
|
|
142
164
|
state.connectors = state.connectors.filter(
|
|
143
|
-
(c) => c.start.widgetId !==
|
|
165
|
+
(c) => c.start.widgetId !== removedId && c.end.widgetId !== removedId,
|
|
144
166
|
)
|
|
145
167
|
}
|
|
146
168
|
break
|
|
@@ -179,8 +201,12 @@ export function materialize(events) {
|
|
|
179
201
|
}
|
|
180
202
|
|
|
181
203
|
case 'connector_removed': {
|
|
204
|
+
// Back-compat: legacy events only had `connectorId`; new events
|
|
205
|
+
// carry the full `connector` object so undo can re-add without
|
|
206
|
+
// history walk.
|
|
207
|
+
const removedId = evt.connector?.id ?? evt.connectorId
|
|
182
208
|
state.connectors = (state.connectors || []).filter(
|
|
183
|
-
(c) => c.id !==
|
|
209
|
+
(c) => c.id !== removedId,
|
|
184
210
|
)
|
|
185
211
|
break
|
|
186
212
|
}
|