@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
|
@@ -1,86 +1,101 @@
|
|
|
1
1
|
import { useRef, useState, useCallback } from 'react'
|
|
2
2
|
|
|
3
3
|
const MAX_HISTORY = 100
|
|
4
|
-
const COALESCE_MS = 2000
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
6
|
+
* Per-tab undo/redo history for canvas events.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Maintains two stacks of event IDs:
|
|
9
|
+
* undoStack — ids of user-initiated forward events, newest last.
|
|
10
|
+
* redoStack — ids of undo events (the inverse events appended by the
|
|
11
|
+
* server's POST /undo), newest last.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Workflow:
|
|
14
|
+
* • Every user-initiated mutation that succeeds calls `track(eventId)` —
|
|
15
|
+
* pushes onto undoStack and clears redoStack.
|
|
16
|
+
* • `popUndo()` returns the next id to undo. Caller POSTs /undo and on
|
|
17
|
+
* success calls `pushRedo(inverseId)`.
|
|
18
|
+
* • `popRedo()` returns the next id to redo. Caller POSTs /redo and on
|
|
19
|
+
* success calls `pushUndo(inverseId)` (so the redo is itself undoable).
|
|
20
|
+
*
|
|
21
|
+
* This hook holds *no* widget data — undo state lives in the JSONL. The
|
|
22
|
+
* stacks are per-tab and ephemeral; refreshing the page resets them.
|
|
14
23
|
*/
|
|
15
24
|
export default function useUndoRedo() {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
25
|
+
const undoStackRef = useRef([])
|
|
26
|
+
const redoStackRef = useRef([])
|
|
27
|
+
const [counts, setCounts] = useState({ undo: 0, redo: 0 })
|
|
28
|
+
|
|
29
|
+
const syncCounts = useCallback(() => {
|
|
30
|
+
setCounts({ undo: undoStackRef.current.length, redo: redoStackRef.current.length })
|
|
31
|
+
}, [])
|
|
21
32
|
|
|
22
|
-
const
|
|
23
|
-
|
|
33
|
+
const track = useCallback((eventId) => {
|
|
34
|
+
if (!eventId || typeof eventId !== 'string') return
|
|
35
|
+
undoStackRef.current.push(eventId)
|
|
36
|
+
if (undoStackRef.current.length > MAX_HISTORY) undoStackRef.current.shift()
|
|
37
|
+
// New mutation invalidates the redo chain
|
|
38
|
+
redoStackRef.current = []
|
|
39
|
+
syncCounts()
|
|
40
|
+
}, [syncCounts])
|
|
24
41
|
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
last.widgetId === widgetId &&
|
|
32
|
-
now - last.time < COALESCE_MS
|
|
33
|
-
) {
|
|
34
|
-
lastActionRef.current = { type: 'edit', widgetId, time: now }
|
|
35
|
-
return
|
|
42
|
+
const trackMany = useCallback((eventIds) => {
|
|
43
|
+
if (!Array.isArray(eventIds)) return
|
|
44
|
+
for (const id of eventIds) {
|
|
45
|
+
if (id && typeof id === 'string') {
|
|
46
|
+
undoStackRef.current.push(id)
|
|
47
|
+
if (undoStackRef.current.length > MAX_HISTORY) undoStackRef.current.shift()
|
|
36
48
|
}
|
|
37
49
|
}
|
|
50
|
+
redoStackRef.current = []
|
|
51
|
+
syncCounts()
|
|
52
|
+
}, [syncCounts])
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
widgetId: widgetId || null,
|
|
45
|
-
time: Date.now(),
|
|
46
|
-
}
|
|
47
|
-
setCounts({ past: pastRef.current.length, future: 0 })
|
|
48
|
-
}, [])
|
|
54
|
+
const popUndo = useCallback(() => {
|
|
55
|
+
const id = undoStackRef.current.pop()
|
|
56
|
+
syncCounts()
|
|
57
|
+
return id ?? null
|
|
58
|
+
}, [syncCounts])
|
|
49
59
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
setCounts({ past: pastRef.current.length, future: futureRef.current.length })
|
|
56
|
-
return previous
|
|
57
|
-
}, [])
|
|
60
|
+
const popRedo = useCallback(() => {
|
|
61
|
+
const id = redoStackRef.current.pop()
|
|
62
|
+
syncCounts()
|
|
63
|
+
return id ?? null
|
|
64
|
+
}, [syncCounts])
|
|
58
65
|
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const pushRedo = useCallback((eventId) => {
|
|
67
|
+
if (!eventId || typeof eventId !== 'string') return
|
|
68
|
+
redoStackRef.current.push(eventId)
|
|
69
|
+
if (redoStackRef.current.length > MAX_HISTORY) redoStackRef.current.shift()
|
|
70
|
+
syncCounts()
|
|
71
|
+
}, [syncCounts])
|
|
72
|
+
|
|
73
|
+
const pushUndo = useCallback((eventId) => {
|
|
74
|
+
if (!eventId || typeof eventId !== 'string') return
|
|
75
|
+
undoStackRef.current.push(eventId)
|
|
76
|
+
if (undoStackRef.current.length > MAX_HISTORY) undoStackRef.current.shift()
|
|
77
|
+
syncCounts()
|
|
78
|
+
}, [syncCounts])
|
|
67
79
|
|
|
68
80
|
const reset = useCallback(() => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
lastActionRef.current = { type: null, widgetId: null, time: 0 }
|
|
81
|
+
undoStackRef.current = []
|
|
82
|
+
redoStackRef.current = []
|
|
72
83
|
setCounts((prev) => {
|
|
73
|
-
if (prev.
|
|
74
|
-
return {
|
|
84
|
+
if (prev.undo === 0 && prev.redo === 0) return prev
|
|
85
|
+
return { undo: 0, redo: 0 }
|
|
75
86
|
})
|
|
76
87
|
}, [])
|
|
77
88
|
|
|
78
89
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
track,
|
|
91
|
+
trackMany,
|
|
92
|
+
popUndo,
|
|
93
|
+
popRedo,
|
|
94
|
+
pushRedo,
|
|
95
|
+
pushUndo,
|
|
82
96
|
reset,
|
|
83
|
-
canUndo: counts.
|
|
84
|
-
canRedo: counts.
|
|
97
|
+
canUndo: counts.undo > 0,
|
|
98
|
+
canRedo: counts.redo > 0,
|
|
85
99
|
}
|
|
86
100
|
}
|
|
101
|
+
|
|
@@ -1,231 +1,99 @@
|
|
|
1
1
|
import { renderHook, act } from '@testing-library/react'
|
|
2
|
+
import { describe, it, expect } from 'vitest'
|
|
2
3
|
import useUndoRedo from './useUndoRedo.js'
|
|
3
4
|
|
|
4
|
-
describe('useUndoRedo', () => {
|
|
5
|
+
describe('useUndoRedo (canvas)', () => {
|
|
5
6
|
it('starts with canUndo and canRedo as false', () => {
|
|
6
7
|
const { result } = renderHook(() => useUndoRedo())
|
|
7
8
|
expect(result.current.canUndo).toBe(false)
|
|
8
9
|
expect(result.current.canRedo).toBe(false)
|
|
9
10
|
})
|
|
10
11
|
|
|
11
|
-
it('
|
|
12
|
+
it('track pushes an event id and enables undo', () => {
|
|
12
13
|
const { result } = renderHook(() => useUndoRedo())
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
act(() => result.current.snapshot(widgets, 'add'))
|
|
16
|
-
|
|
14
|
+
act(() => result.current.track('evt_a'))
|
|
17
15
|
expect(result.current.canUndo).toBe(true)
|
|
18
16
|
expect(result.current.canRedo).toBe(false)
|
|
19
17
|
})
|
|
20
18
|
|
|
21
|
-
it('
|
|
19
|
+
it('popUndo returns the most recent tracked id; pushRedo enables redo', () => {
|
|
22
20
|
const { result } = renderHook(() => useUndoRedo())
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
act(() => result.current.track('evt_a'))
|
|
22
|
+
act(() => result.current.track('evt_b'))
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
let popped
|
|
25
|
+
act(() => { popped = result.current.popUndo() })
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
expect(popped).toBe('evt_b')
|
|
28
|
+
expect(result.current.canUndo).toBe(true)
|
|
29
|
+
expect(result.current.canRedo).toBe(false)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
expect(result.current.canUndo).toBe(false)
|
|
31
|
+
act(() => result.current.pushRedo('evt_b_inv'))
|
|
33
32
|
expect(result.current.canRedo).toBe(true)
|
|
34
33
|
})
|
|
35
34
|
|
|
36
|
-
it('
|
|
35
|
+
it('popRedo returns the most recent redo id; pushUndo restores undo availability', () => {
|
|
37
36
|
const { result } = renderHook(() => useUndoRedo())
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
act(() => result.current.track('evt_a'))
|
|
38
|
+
act(() => { result.current.popUndo() })
|
|
39
|
+
act(() => result.current.pushRedo('evt_a_inv'))
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
act(() => { result.current.undo(after) })
|
|
41
|
+
expect(result.current.canRedo).toBe(true)
|
|
43
42
|
|
|
44
|
-
let
|
|
45
|
-
act(() => {
|
|
43
|
+
let popped
|
|
44
|
+
act(() => { popped = result.current.popRedo() })
|
|
45
|
+
expect(popped).toBe('evt_a_inv')
|
|
46
|
+
expect(result.current.canRedo).toBe(false)
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
act(() => result.current.pushUndo('evt_a_inv_inv'))
|
|
48
49
|
expect(result.current.canUndo).toBe(true)
|
|
49
|
-
expect(result.current.canRedo).toBe(false)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
it('new
|
|
52
|
+
it('new track call after undo clears the redo stack', () => {
|
|
53
53
|
const { result } = renderHook(() => useUndoRedo())
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
act(() => result.current.snapshot(s0, 'add'))
|
|
59
|
-
act(() => result.current.undo(s1))
|
|
54
|
+
act(() => result.current.track('evt_a'))
|
|
55
|
+
act(() => { result.current.popUndo() })
|
|
56
|
+
act(() => result.current.pushRedo('evt_a_inv'))
|
|
60
57
|
expect(result.current.canRedo).toBe(true)
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
act(() => result.current.snapshot(s0, 'add'))
|
|
59
|
+
act(() => result.current.track('evt_b'))
|
|
64
60
|
expect(result.current.canRedo).toBe(false)
|
|
65
61
|
expect(result.current.canUndo).toBe(true)
|
|
66
62
|
})
|
|
67
63
|
|
|
68
|
-
it('
|
|
64
|
+
it('trackMany pushes multiple ids and clears redo', () => {
|
|
69
65
|
const { result } = renderHook(() => useUndoRedo())
|
|
70
|
-
|
|
71
|
-
const s1 = [{ id: '1' }, { id: '2' }]
|
|
72
|
-
const s2 = [{ id: '1' }, { id: '2' }, { id: '3' }]
|
|
73
|
-
const s3 = [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }]
|
|
74
|
-
|
|
75
|
-
// Build history: s0 → s1 → s2 → s3
|
|
76
|
-
act(() => result.current.snapshot(s0, 'add'))
|
|
77
|
-
act(() => result.current.snapshot(s1, 'add'))
|
|
78
|
-
act(() => result.current.snapshot(s2, 'add'))
|
|
79
|
-
|
|
80
|
-
// present = s3, past = [s0, s1, s2]
|
|
81
|
-
// Undo to s2
|
|
82
|
-
let r
|
|
83
|
-
act(() => { r = result.current.undo(s3) })
|
|
84
|
-
expect(r).toEqual(s2)
|
|
85
|
-
|
|
86
|
-
// Undo to s1
|
|
87
|
-
act(() => { r = result.current.undo(s2) })
|
|
88
|
-
expect(r).toEqual(s1)
|
|
89
|
-
|
|
90
|
-
// Redo to s2
|
|
91
|
-
act(() => { r = result.current.redo(s1) })
|
|
92
|
-
expect(r).toEqual(s2)
|
|
93
|
-
|
|
94
|
-
// Redo to s3
|
|
95
|
-
act(() => { r = result.current.redo(s2) })
|
|
96
|
-
expect(r).toEqual(s3)
|
|
97
|
-
|
|
98
|
-
// Undo to s2 again
|
|
99
|
-
act(() => { r = result.current.undo(s3) })
|
|
100
|
-
expect(r).toEqual(s2)
|
|
101
|
-
|
|
102
|
-
// Undo to s1
|
|
103
|
-
act(() => { r = result.current.undo(s2) })
|
|
104
|
-
expect(r).toEqual(s1)
|
|
105
|
-
|
|
106
|
-
// Undo to s0
|
|
107
|
-
act(() => { r = result.current.undo(s1) })
|
|
108
|
-
expect(r).toEqual(s0)
|
|
109
|
-
|
|
110
|
-
// Can't undo further
|
|
111
|
-
expect(result.current.canUndo).toBe(false)
|
|
112
|
-
act(() => { r = result.current.undo(s0) })
|
|
113
|
-
expect(r).toBeNull()
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('coalesces edits to the same widget within timeout', () => {
|
|
117
|
-
const { result } = renderHook(() => useUndoRedo())
|
|
118
|
-
const s0 = [{ id: '1', props: { text: '' } }]
|
|
119
|
-
|
|
120
|
-
// First edit — creates snapshot
|
|
121
|
-
act(() => result.current.snapshot(s0, 'edit', '1'))
|
|
66
|
+
act(() => result.current.trackMany(['evt_a', 'evt_b', 'evt_c']))
|
|
122
67
|
expect(result.current.canUndo).toBe(true)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Still only one entry in past
|
|
127
|
-
let r
|
|
128
|
-
act(() => { r = result.current.undo([{ id: '1', props: { text: 'abc' } }]) })
|
|
129
|
-
expect(r).toEqual(s0)
|
|
130
|
-
// No more undo after that
|
|
131
|
-
expect(result.current.canUndo).toBe(false)
|
|
68
|
+
let popped
|
|
69
|
+
act(() => { popped = result.current.popUndo() })
|
|
70
|
+
expect(popped).toBe('evt_c')
|
|
132
71
|
})
|
|
133
72
|
|
|
134
|
-
it('
|
|
73
|
+
it('ignores empty / non-string ids', () => {
|
|
135
74
|
const { result } = renderHook(() => useUndoRedo())
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
act(() => result.current.
|
|
140
|
-
act(() => result.current.snapshot(s1, 'edit', '2'))
|
|
141
|
-
|
|
142
|
-
// Two entries in past
|
|
143
|
-
expect(result.current.canUndo).toBe(true)
|
|
144
|
-
act(() => { result.current.undo([]) })
|
|
145
|
-
expect(result.current.canUndo).toBe(true)
|
|
146
|
-
act(() => { result.current.undo([]) })
|
|
75
|
+
act(() => result.current.track(null))
|
|
76
|
+
act(() => result.current.track(undefined))
|
|
77
|
+
act(() => result.current.track(''))
|
|
78
|
+
act(() => result.current.track(42))
|
|
147
79
|
expect(result.current.canUndo).toBe(false)
|
|
148
80
|
})
|
|
149
81
|
|
|
150
|
-
it('
|
|
82
|
+
it('reset clears both stacks', () => {
|
|
151
83
|
const { result } = renderHook(() => useUndoRedo())
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
act(() => result.current.snapshot(s0, 'edit', '1'))
|
|
156
|
-
act(() => result.current.snapshot(s1, 'add'))
|
|
157
|
-
act(() => result.current.snapshot(s1, 'edit', '1'))
|
|
158
|
-
|
|
159
|
-
// Three entries in past (edit + add + edit — not coalesced because add broke the sequence)
|
|
160
|
-
act(() => { result.current.undo([]) })
|
|
161
|
-
act(() => { result.current.undo([]) })
|
|
162
|
-
act(() => { result.current.undo([]) })
|
|
163
|
-
expect(result.current.canUndo).toBe(false)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('reset clears all history', () => {
|
|
167
|
-
const { result } = renderHook(() => useUndoRedo())
|
|
168
|
-
act(() => result.current.snapshot([{ id: '1' }], 'add'))
|
|
169
|
-
act(() => result.current.snapshot([{ id: '1' }, { id: '2' }], 'add'))
|
|
170
|
-
expect(result.current.canUndo).toBe(true)
|
|
84
|
+
act(() => result.current.track('evt_a'))
|
|
85
|
+
act(() => { result.current.popUndo() })
|
|
86
|
+
act(() => result.current.pushRedo('evt_a_inv'))
|
|
171
87
|
|
|
172
88
|
act(() => result.current.reset())
|
|
173
89
|
expect(result.current.canUndo).toBe(false)
|
|
174
90
|
expect(result.current.canRedo).toBe(false)
|
|
175
91
|
})
|
|
176
92
|
|
|
177
|
-
it('
|
|
178
|
-
const { result } = renderHook(() => useUndoRedo())
|
|
179
|
-
let r
|
|
180
|
-
act(() => { r = result.current.undo([]) })
|
|
181
|
-
expect(r).toBeNull()
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('redo returns null when future is empty', () => {
|
|
185
|
-
const { result } = renderHook(() => useUndoRedo())
|
|
186
|
-
let r
|
|
187
|
-
act(() => { r = result.current.redo([]) })
|
|
188
|
-
expect(r).toBeNull()
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('snapshots are deep clones (mutations do not leak)', () => {
|
|
192
|
-
const { result } = renderHook(() => useUndoRedo())
|
|
193
|
-
const widgets = [{ id: '1', props: { text: 'original' } }]
|
|
194
|
-
|
|
195
|
-
act(() => result.current.snapshot(widgets, 'add'))
|
|
196
|
-
|
|
197
|
-
// Mutate the original array
|
|
198
|
-
widgets[0].props.text = 'mutated'
|
|
199
|
-
|
|
200
|
-
let restored
|
|
201
|
-
act(() => { restored = result.current.undo(widgets) })
|
|
202
|
-
expect(restored[0].props.text).toBe('original')
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('caps history at 100 entries', () => {
|
|
93
|
+
it('popUndo on empty stack returns null', () => {
|
|
206
94
|
const { result } = renderHook(() => useUndoRedo())
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Should be capped at 100 — undo 100 times, then can't undo further
|
|
213
|
-
let count = 0
|
|
214
|
-
let r = true
|
|
215
|
-
while (r !== null) {
|
|
216
|
-
act(() => { r = result.current.undo([]) })
|
|
217
|
-
if (r !== null) count++
|
|
218
|
-
}
|
|
219
|
-
expect(count).toBe(100)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('snapshots null widgets as empty array (first widget on new canvas)', () => {
|
|
223
|
-
const { result } = renderHook(() => useUndoRedo())
|
|
224
|
-
act(() => result.current.snapshot(null, 'add'))
|
|
225
|
-
expect(result.current.canUndo).toBe(true)
|
|
226
|
-
|
|
227
|
-
let restored
|
|
228
|
-
act(() => { restored = result.current.undo([{ id: '1' }]) })
|
|
229
|
-
expect(restored).toEqual([])
|
|
95
|
+
let popped
|
|
96
|
+
act(() => { popped = result.current.popUndo() })
|
|
97
|
+
expect(popped).toBeNull()
|
|
230
98
|
})
|
|
231
99
|
})
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Combined with `getState` for toggle resolution and `onAction` for dispatch.
|
|
14
14
|
*/
|
|
15
15
|
import { ScreenNormalIcon } from '@primer/octicons-react'
|
|
16
|
-
import
|
|
16
|
+
import Tooltip from '../../Tooltip.jsx'
|
|
17
17
|
import { ICON_REGISTRY } from './widgetIcons.jsx'
|
|
18
18
|
import { getWidgetMeta } from './widgetConfig.js'
|
|
19
19
|
import Icon from '../../Icon.jsx'
|
|
@@ -48,32 +48,41 @@ export default function FrozenTerminalOverlay({ widgetId, onActivate }) {
|
|
|
48
48
|
|
|
49
49
|
async function fetchSnapshot() {
|
|
50
50
|
const baseUrl = getBaseUrl()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (!res.ok) continue
|
|
51
|
+
// Try the dev-server REST endpoint first, then fall back to the static
|
|
52
|
+
// consolidated index (production builds / no server).
|
|
53
|
+
const restUrl = `${baseUrl}_storyboard/canvas/terminal-snapshot/${widgetId}`
|
|
54
|
+
const indexUrl = `${baseUrl}_storyboard/terminal-snapshots/agents.snapshot.json`
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(restUrl)
|
|
58
|
+
if (res.ok) {
|
|
60
59
|
const data = await res.json()
|
|
61
60
|
if (cancelled) return
|
|
62
61
|
const text = data.paneContent || data.content || data.output || ''
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} else {
|
|
70
|
-
setPlainText(stripAnsi(text))
|
|
62
|
+
if (text) {
|
|
63
|
+
const converter = await getConverter()
|
|
64
|
+
if (cancelled) return
|
|
65
|
+
if (converter) setHtml(converter.toHtml(text))
|
|
66
|
+
else setPlainText(stripAnsi(text))
|
|
67
|
+
return
|
|
71
68
|
}
|
|
72
|
-
return
|
|
73
|
-
} catch {
|
|
74
|
-
continue
|
|
75
69
|
}
|
|
76
|
-
}
|
|
70
|
+
} catch { /* fall through to static index */ }
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetch(indexUrl)
|
|
74
|
+
if (!res.ok) return
|
|
75
|
+
const data = await res.json()
|
|
76
|
+
if (cancelled) return
|
|
77
|
+
const entry = data?.agents?.[widgetId]
|
|
78
|
+
if (!entry) return
|
|
79
|
+
const text = entry.paneContent || entry.content || entry.output || ''
|
|
80
|
+
if (!text) return
|
|
81
|
+
const converter = await getConverter()
|
|
82
|
+
if (cancelled) return
|
|
83
|
+
if (converter) setHtml(converter.toHtml(text))
|
|
84
|
+
else setPlainText(stripAnsi(text))
|
|
85
|
+
} catch { /* empty */ }
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
fetchSnapshot()
|
|
@@ -137,7 +137,10 @@ const ImageWidget = forwardRef(function ImageWidget({ id, props, onUpdate, resiz
|
|
|
137
137
|
const blobUrl = URL.createObjectURL(blob)
|
|
138
138
|
const a = document.createElement('a')
|
|
139
139
|
a.href = blobUrl
|
|
140
|
-
|
|
140
|
+
// Strip optional drafts/ directory prefix and leading ~ from the
|
|
141
|
+
// downloaded filename — the user wants the bare basename.
|
|
142
|
+
const baseName = src.includes('/') ? src.slice(src.lastIndexOf('/') + 1) : src
|
|
143
|
+
a.download = baseName.replace(/^~/, '')
|
|
141
144
|
document.body.appendChild(a)
|
|
142
145
|
a.click()
|
|
143
146
|
document.body.removeChild(a)
|
|
@@ -156,7 +159,9 @@ const ImageWidget = forwardRef(function ImageWidget({ id, props, onUpdate, resiz
|
|
|
156
159
|
.catch((err) => console.error('[canvas] Failed to copy image:', err))
|
|
157
160
|
} else if (actionId === 'copy-file-path') {
|
|
158
161
|
if (!src) return
|
|
159
|
-
|
|
162
|
+
// `src` may include an optional `drafts/` prefix; in both cases the
|
|
163
|
+
// file lives under assets/canvas/images/.
|
|
164
|
+
navigator.clipboard.writeText(`assets/canvas/images/${src}`).catch(() => {})
|
|
160
165
|
}
|
|
161
166
|
}
|
|
162
167
|
}), [src, onUpdate])
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImper
|
|
|
2
2
|
import { remark } from 'remark'
|
|
3
3
|
import remarkGfm from 'remark-gfm'
|
|
4
4
|
import remarkHtml from 'remark-html'
|
|
5
|
-
import { MarkGithubIcon } from '@primer/octicons-react'
|
|
5
|
+
import { GitBranchIcon, GitMergeIcon, GitPullRequestClosedIcon, GitPullRequestDraftIcon, GitPullRequestIcon, MarkGithubIcon } from '@primer/octicons-react'
|
|
6
6
|
import WidgetWrapper from './WidgetWrapper.jsx'
|
|
7
7
|
import ResizeHandle from './ResizeHandle.jsx'
|
|
8
8
|
import { readProp, linkPreviewSchema } from './widgetProps.js'
|
|
@@ -119,6 +119,48 @@ function getCommentKindLabel(github) {
|
|
|
119
119
|
return 'Comment'
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
const PR_STATE_META = {
|
|
123
|
+
merged: { label: 'Merged', Icon: GitMergeIcon, className: 'prStateMerged' },
|
|
124
|
+
closed: { label: 'Closed', Icon: GitPullRequestClosedIcon, className: 'prStateClosed' },
|
|
125
|
+
draft: { label: 'Draft', Icon: GitPullRequestDraftIcon, className: 'prStateDraft' },
|
|
126
|
+
open: { label: 'Open', Icon: GitPullRequestIcon, className: 'prStateOpen' },
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function PullRequestMeta({ github }) {
|
|
130
|
+
if (github?.kind !== 'pull_request') return null
|
|
131
|
+
const stateKey = github?.state || 'open'
|
|
132
|
+
const stateMeta = PR_STATE_META[stateKey] || PR_STATE_META.open
|
|
133
|
+
const { Icon: StateIcon } = stateMeta
|
|
134
|
+
const baseRef = github?.baseRef || null
|
|
135
|
+
const headRef = github?.headRef || null
|
|
136
|
+
const additions = typeof github?.additions === 'number' ? github.additions : null
|
|
137
|
+
const deletions = typeof github?.deletions === 'number' ? github.deletions : null
|
|
138
|
+
const hasDiff = additions !== null || deletions !== null
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div className={styles.prMeta}>
|
|
142
|
+
<span className={`${styles.prStateBadge} ${styles[stateMeta.className]}`}>
|
|
143
|
+
<StateIcon size={12} />
|
|
144
|
+
{stateMeta.label}
|
|
145
|
+
</span>
|
|
146
|
+
{(baseRef || headRef) && (
|
|
147
|
+
<span className={styles.prBranchRef} title={`${headRef || '?'} → ${baseRef || '?'}`}>
|
|
148
|
+
<GitBranchIcon size={12} />
|
|
149
|
+
<span className={styles.prBranchName}>{baseRef || '?'}</span>
|
|
150
|
+
<span className={styles.prBranchArrow}>←</span>
|
|
151
|
+
<span className={styles.prBranchName}>{headRef || '?'}</span>
|
|
152
|
+
</span>
|
|
153
|
+
)}
|
|
154
|
+
{hasDiff && (
|
|
155
|
+
<span className={styles.prDiffStat}>
|
|
156
|
+
{additions !== null && <span className={styles.prDiffAdd}>+{additions}</span>}
|
|
157
|
+
{deletions !== null && <span className={styles.prDiffDel}>-{deletions}</span>}
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
122
164
|
function GitHubIssueCard({ id, url, title, github, width, collapsed, expanded, expandMode, onCloseExpand }) {
|
|
123
165
|
const authors = Array.isArray(github?.authors)
|
|
124
166
|
? github.authors.filter((a) => typeof a === 'string' && a.trim())
|
|
@@ -209,6 +251,7 @@ function GitHubIssueCard({ id, url, title, github, width, collapsed, expanded, e
|
|
|
209
251
|
{primaryAuthor && createdAgo ? ` opened ${createdAgo}` : createdAgo ? `Opened ${createdAgo}` : ''}
|
|
210
252
|
</span>
|
|
211
253
|
</div>
|
|
254
|
+
<PullRequestMeta github={github} />
|
|
212
255
|
</div>
|
|
213
256
|
|
|
214
257
|
{bodyHtml && (
|
|
@@ -243,6 +286,7 @@ function GitHubIssueCard({ id, url, title, github, width, collapsed, expanded, e
|
|
|
243
286
|
)}
|
|
244
287
|
{createdAgo && <span className={styles.expandedBylineText}>{primaryAuthor ? ` opened ${createdAgo}` : `Opened ${createdAgo}`}</span>}
|
|
245
288
|
</div>
|
|
289
|
+
<PullRequestMeta github={github} />
|
|
246
290
|
</header>
|
|
247
291
|
{bodyHtml && <div className={styles.expandedIssueBody} dangerouslySetInnerHTML={{ __html: bodyHtml }} />}
|
|
248
292
|
</div>
|