@dfosco/storyboard 0.7.0 → 0.7.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/package.json +2 -1
- package/src/core/canvas/githubEmbeds.js +12 -0
- package/src/core/canvas/githubEmbeds.test.js +71 -2
- package/src/core/vite/server-plugin.js +23 -0
- package/src/internals/canvas/widgets/LinkPreview.jsx +45 -1
- package/src/internals/canvas/widgets/LinkPreview.module.css +71 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg width="1040" height="1040" viewBox="0 0 1040 1040" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_281_5346)">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M1040 520C1040 607.255 1040.14 680.911 1033.5 740.187C1026.63 801.432 1011.5 859.346 972.737 910.26C954.816 933.8 933.8 954.816 910.26 972.737C859.346 1011.5 801.432 1026.63 740.187 1033.5C680.911 1040.14 607.255 1040 520 1040C432.747 1040 359.09 1040.14 299.813 1033.5C238.568 1026.63 180.656 1011.5 129.74 972.737C106.201 954.816 85.1857 933.8 67.263 910.26C28.496 859.346 13.3667 801.432 6.50058 740.187C-0.144753 680.911 0.000253571 607.255 0.000253571 520C0.000253571 432.747 -0.144754 359.09 6.50058 299.813C13.3667 238.568 28.496 180.656 67.263 129.74C85.1857 106.201 106.201 85.1857 129.74 67.263C180.656 28.496 238.568 13.3667 299.813 6.50058C359.09 -0.144754 432.747 0.000253571 520 0.000253571C607.255 0.000253571 680.911 -0.144753 740.187 6.50058C801.432 13.3667 859.346 28.496 910.26 67.263C933.8 85.1857 954.816 106.201 972.737 129.74C1011.5 180.656 1026.63 238.568 1033.5 299.813C1040.14 359.09 1040 432.747 1040 520Z" fill="#170C1F"/>
|
|
4
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M984 520.5C984 325.013 984 226.507 933.401 158.522L932.197 156.924C918.393 138.793 902.207 122.608 884.078 108.804C816.039 56.9996 717.525 57 520.5 57C323.474 57 224.961 56.9996 156.924 108.804L155.229 110.105C137.784 123.621 122.176 139.36 108.804 156.924L107.6 158.522C57 226.507 57 325.013 57 520.5C57 717.525 56.9997 816.039 108.804 884.078C122.608 902.207 138.793 918.393 156.924 932.197C224.961 984 323.474 984 520.5 984C715.986 984 814.495 984 882.477 933.401L884.078 932.197C901.64 918.824 917.379 903.215 930.895 885.771L932.197 884.078C984 816.039 984 717.525 984 520.5Z" fill="#170C1F"/>
|
|
5
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M208.988 378.291C208.988 407.969 220.778 436.431 241.763 457.416C262.748 478.401 291.21 490.19 320.887 490.19C350.564 490.19 379.026 478.401 400.011 457.416C420.997 436.431 432.786 407.969 432.786 378.291C432.786 348.614 420.997 320.152 400.011 299.167C379.026 278.182 350.564 266.393 320.887 266.393C291.21 266.393 262.748 278.182 241.763 299.167C220.778 320.152 208.988 348.614 208.988 378.291ZM605.719 378.291C605.719 392.986 608.613 407.537 614.237 421.113C619.86 434.689 628.102 447.025 638.493 457.416C648.884 467.806 661.219 476.049 674.796 481.672C688.372 487.296 702.923 490.19 717.617 490.19C732.312 490.19 746.863 487.296 760.439 481.672C774.015 476.049 786.351 467.806 796.742 457.416C807.133 447.025 815.375 434.689 820.998 421.113C826.622 407.537 829.516 392.986 829.516 378.291C829.516 363.597 826.622 349.046 820.998 335.47C815.375 321.893 807.133 309.558 796.742 299.167C786.351 288.776 774.015 280.534 760.439 274.91C746.863 269.287 732.312 266.393 717.617 266.393C702.923 266.393 688.372 269.287 674.796 274.91C661.219 280.534 648.884 288.776 638.493 299.167C628.102 309.558 619.86 321.893 614.237 335.47C608.613 349.046 605.719 363.597 605.719 378.291ZM631.928 541.203C646.198 541.203 658.91 553.016 656.488 568.2C645.989 634.034 589.271 684.367 520.824 684.367C452.376 684.367 395.658 634.034 385.16 568.2C382.737 553.016 395.449 541.203 409.72 541.203C424.454 541.203 435.266 553.171 438.371 566.171C447.328 603.669 480.868 631.497 520.824 631.497C560.78 631.497 594.32 603.669 603.277 566.171C606.382 553.171 617.193 541.203 631.928 541.203Z" fill="#EBD7EF"/>
|
|
6
|
+
</g>
|
|
7
|
+
<defs>
|
|
8
|
+
<clipPath id="clip0_281_5346">
|
|
9
|
+
<rect width="1040" height="1040" fill="white"/>
|
|
10
|
+
</clipPath>
|
|
11
|
+
</defs>
|
|
12
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dfosco/storyboard",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Storyboard prototyping framework — core engine, React integration, and canvas",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"src",
|
|
19
19
|
"dist",
|
|
20
|
+
"assets",
|
|
20
21
|
"scaffold",
|
|
21
22
|
"mascot",
|
|
22
23
|
"toolbar.config.json",
|
|
@@ -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!',
|
|
@@ -110,6 +110,18 @@ export default function storyboardServer() {
|
|
|
110
110
|
const routeHandlers = new Map()
|
|
111
111
|
const clientScripts = []
|
|
112
112
|
|
|
113
|
+
// Load packaged favicon once and cache as data URI so every consumer
|
|
114
|
+
// gets the storyboard brand favicon without shipping/copying any asset.
|
|
115
|
+
let faviconDataUri = ''
|
|
116
|
+
try {
|
|
117
|
+
const faviconPath = path.resolve(
|
|
118
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
119
|
+
'../../../assets/favicon.svg'
|
|
120
|
+
)
|
|
121
|
+
const svg = fs.readFileSync(faviconPath, 'utf8')
|
|
122
|
+
faviconDataUri = 'data:image/svg+xml;base64,' + Buffer.from(svg, 'utf8').toString('base64')
|
|
123
|
+
} catch { /* favicon missing — skip injection */ }
|
|
124
|
+
|
|
113
125
|
return {
|
|
114
126
|
name: 'storyboard-server',
|
|
115
127
|
|
|
@@ -872,6 +884,17 @@ export default function storyboardServer() {
|
|
|
872
884
|
})
|
|
873
885
|
}
|
|
874
886
|
|
|
887
|
+
// Inject the storyboard brand favicon as a data URI. Appending to
|
|
888
|
+
// head means it wins over any earlier <link rel="icon"> the consumer
|
|
889
|
+
// declared (browsers use the last declaration).
|
|
890
|
+
if (faviconDataUri) {
|
|
891
|
+
tags.push({
|
|
892
|
+
tag: 'link',
|
|
893
|
+
attrs: { rel: 'icon', type: 'image/svg+xml', href: faviconDataUri },
|
|
894
|
+
injectTo: 'head',
|
|
895
|
+
})
|
|
896
|
+
}
|
|
897
|
+
|
|
875
898
|
return tags
|
|
876
899
|
},
|
|
877
900
|
|
|
@@ -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>
|
|
@@ -583,3 +583,74 @@
|
|
|
583
583
|
.expandedUrl:hover {
|
|
584
584
|
text-decoration: underline;
|
|
585
585
|
}
|
|
586
|
+
|
|
587
|
+
/* ── PR metadata (state badge + branch refs + diff stats) ──────────── */
|
|
588
|
+
|
|
589
|
+
.prMeta {
|
|
590
|
+
display: flex;
|
|
591
|
+
align-items: center;
|
|
592
|
+
gap: 8px;
|
|
593
|
+
flex-wrap: wrap;
|
|
594
|
+
font-size: 12px;
|
|
595
|
+
color: var(--fgColor-muted, #656d76);
|
|
596
|
+
min-width: 0;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.prStateBadge {
|
|
600
|
+
display: inline-flex;
|
|
601
|
+
align-items: center;
|
|
602
|
+
gap: 4px;
|
|
603
|
+
padding: 3px 8px;
|
|
604
|
+
border-radius: 2em;
|
|
605
|
+
font-size: 12px;
|
|
606
|
+
font-weight: 500;
|
|
607
|
+
line-height: 1;
|
|
608
|
+
color: var(--fgColor-onEmphasis, #ffffff);
|
|
609
|
+
white-space: nowrap;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.prStateOpen { background: var(--bgColor-open-emphasis, #1f883d); }
|
|
613
|
+
.prStateMerged { background: var(--bgColor-done-emphasis, #8250df); }
|
|
614
|
+
.prStateClosed { background: var(--bgColor-closed-emphasis, #cf222e); }
|
|
615
|
+
.prStateDraft {
|
|
616
|
+
background: var(--bgColor-neutral-emphasis, #59636e);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.prBranchRef {
|
|
620
|
+
display: inline-flex;
|
|
621
|
+
align-items: center;
|
|
622
|
+
gap: 4px;
|
|
623
|
+
padding: 2px 6px;
|
|
624
|
+
border-radius: 4px;
|
|
625
|
+
background: var(--bgColor-muted, #f6f8fa);
|
|
626
|
+
color: var(--fgColor-default, #1f2328);
|
|
627
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
628
|
+
font-size: 11px;
|
|
629
|
+
max-width: 240px;
|
|
630
|
+
overflow: hidden;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.prBranchName {
|
|
634
|
+
overflow: hidden;
|
|
635
|
+
text-overflow: ellipsis;
|
|
636
|
+
white-space: nowrap;
|
|
637
|
+
max-width: 96px;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.prBranchArrow {
|
|
641
|
+
color: var(--fgColor-muted, #656d76);
|
|
642
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
643
|
+
flex-shrink: 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.prDiffStat {
|
|
647
|
+
display: inline-flex;
|
|
648
|
+
align-items: center;
|
|
649
|
+
gap: 4px;
|
|
650
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
651
|
+
font-size: 12px;
|
|
652
|
+
font-weight: 600;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.prDiffAdd { color: var(--fgColor-success, #1a7f37); }
|
|
656
|
+
.prDiffDel { color: var(--fgColor-danger, #cf222e); }
|