@dfosco/storyboard 0.6.0-beta.18 → 0.6.0-beta.19
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/package.json
CHANGED
|
@@ -1376,41 +1376,53 @@ function handleConnection(ws, widgetId, canvasId, prettyName, widgetStartupComma
|
|
|
1376
1376
|
} catch { /* empty */ }
|
|
1377
1377
|
|
|
1378
1378
|
if (readinessSignal || useReadyFile) {
|
|
1379
|
-
// Poll for readiness, then send postStartup command and deliver messages
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1379
|
+
// Poll for readiness, then send postStartup command and deliver messages.
|
|
1380
|
+
// The .ready marker is the accurate signal (Copilot/Claude/Codex
|
|
1381
|
+
// sessionStart hook touches it once the TUI is interactive). On
|
|
1382
|
+
// consumer repos with cold caches + resume mode, that can easily
|
|
1383
|
+
// take 30–60s — well past any reasonable hard timeout. So we:
|
|
1384
|
+
// 1. Keep polling indefinitely at 3s intervals for .ready.
|
|
1385
|
+
// 2. At 30s, do a "soft commit": inject identity and bind to
|
|
1386
|
+
// the messaging bus so the widget is at least addressable
|
|
1387
|
+
// for hub broadcast and live messages.
|
|
1388
|
+
// 3. When .ready finally appears, run the real postStartup.
|
|
1389
|
+
let softBound = false
|
|
1390
|
+
let postStartupSent = false
|
|
1391
|
+
const softBind = () => {
|
|
1392
|
+
if (softBound) return
|
|
1393
|
+
softBound = true
|
|
1394
|
+
injectIdentityMessage(tmuxName, { widgetId, displayName: prettyName, canvasId, branch, serverUrl })
|
|
1395
|
+
injectRoleMessageForWidget(tmuxName, widgetId)
|
|
1396
|
+
setTimeout(() => {
|
|
1397
|
+
migratePendingMessages(widgetId, branch, canvasId).then(() => {
|
|
1398
|
+
bindWidget({ widgetId, tmuxName, branch, canvasId, displayName: prettyName }).catch(() => {})
|
|
1399
|
+
}).catch(() => {
|
|
1400
|
+
bindWidget({ widgetId, tmuxName, branch, canvasId, displayName: prettyName }).catch(() => {})
|
|
1401
|
+
})
|
|
1402
|
+
joinPresence({ widgetId, senderName: prettyName || widgetId, branch, canvasId }).catch(() => {})
|
|
1403
|
+
}, 2000)
|
|
1404
|
+
}
|
|
1405
|
+
const runPostStartup = (reason) => {
|
|
1406
|
+
if (postStartupSent) return
|
|
1407
|
+
postStartupSent = true
|
|
1384
1408
|
clearInterval(pollInterval)
|
|
1385
1409
|
try { rmSync(readyFilePath, { force: true }) } catch { /* empty */ }
|
|
1386
1410
|
setTimeout(() => {
|
|
1387
1411
|
if (postStartup) {
|
|
1388
1412
|
tmuxSubmit(tmuxName, postStartup)
|
|
1389
1413
|
}
|
|
1390
|
-
//
|
|
1391
|
-
|
|
1392
|
-
// widget's terminal-config preserves hubs/role/connectedWidgets,
|
|
1393
|
-
// and bindWidget reattaches live message delivery + backfill.
|
|
1394
|
-
injectIdentityMessage(tmuxName, { widgetId, displayName: prettyName, canvasId, branch, serverUrl })
|
|
1395
|
-
injectRoleMessageForWidget(tmuxName, widgetId)
|
|
1396
|
-
setTimeout(() => {
|
|
1397
|
-
migratePendingMessages(widgetId, branch, canvasId).then(() => {
|
|
1398
|
-
bindWidget({ widgetId, tmuxName, branch, canvasId, displayName: prettyName }).catch(() => {})
|
|
1399
|
-
}).catch(() => {
|
|
1400
|
-
bindWidget({ widgetId, tmuxName, branch, canvasId, displayName: prettyName }).catch(() => {})
|
|
1401
|
-
})
|
|
1402
|
-
joinPresence({ widgetId, senderName: prettyName || widgetId, branch, canvasId }).catch(() => {})
|
|
1403
|
-
}, 2000)
|
|
1414
|
+
// Ensure identity/bind happened (no-op if already soft-bound)
|
|
1415
|
+
softBind()
|
|
1404
1416
|
}, 500)
|
|
1405
|
-
if (reason === 'timeout') {
|
|
1406
|
-
devLog(`[terminal-server] readiness
|
|
1417
|
+
if (reason === 'soft-bind-timeout') {
|
|
1418
|
+
devLog(`[terminal-server] readiness slow for ${widgetId} (${tmuxName}); soft-binding now, will run postStartup when .ready arrives`)
|
|
1407
1419
|
}
|
|
1408
1420
|
}
|
|
1409
1421
|
const pollInterval = setInterval(() => {
|
|
1410
|
-
if (
|
|
1422
|
+
if (postStartupSent) { clearInterval(pollInterval); return }
|
|
1411
1423
|
// Primary: sessionStart hook touches the marker once the
|
|
1412
1424
|
// agent is fully loaded and interactive.
|
|
1413
|
-
if (useReadyFile && existsSync(readyFilePath)) {
|
|
1425
|
+
if (useReadyFile && existsSync(readyFilePath)) { runPostStartup('file'); return }
|
|
1414
1426
|
if (!readinessSignal) return
|
|
1415
1427
|
try {
|
|
1416
1428
|
// Fallback for agents without a hook: scan pane + 200
|
|
@@ -1419,14 +1431,13 @@ function handleConnection(ws, widgetId, canvasId, prettyName, widgetStartupComma
|
|
|
1419
1431
|
`tmux capture-pane -t "${tmuxName}" -p -S -200`,
|
|
1420
1432
|
{ encoding: 'utf8', timeout: 1000 }
|
|
1421
1433
|
)
|
|
1422
|
-
if (paneContent.includes(readinessSignal))
|
|
1434
|
+
if (paneContent.includes(readinessSignal)) runPostStartup('signal')
|
|
1423
1435
|
} catch { /* empty */ }
|
|
1424
|
-
},
|
|
1425
|
-
//
|
|
1426
|
-
//
|
|
1427
|
-
//
|
|
1428
|
-
|
|
1429
|
-
setTimeout(() => finalize('timeout'), 30000)
|
|
1436
|
+
}, 500)
|
|
1437
|
+
// Soft-bind at 30s so the widget is addressable for hub/live
|
|
1438
|
+
// messages even if Copilot's TUI is still warming up. The poller
|
|
1439
|
+
// keeps running and will fire postStartup once .ready appears.
|
|
1440
|
+
setTimeout(softBind, 30000)
|
|
1430
1441
|
} else {
|
|
1431
1442
|
// No readiness signal — inject identity and bind to bus after a delay
|
|
1432
1443
|
setTimeout(() => {
|
|
@@ -32,12 +32,18 @@ function resolveStorySetUrl(storyId, layout, selected, density, theme) {
|
|
|
32
32
|
if (!story?._storyModule) return ''
|
|
33
33
|
const base = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
|
|
34
34
|
const params = new URLSearchParams()
|
|
35
|
-
|
|
35
|
+
// Route via the real story page (works in dev AND prod). The dev-only
|
|
36
|
+
// `_storyboard/canvas/isolate-set` middleware doesn't exist in deployed
|
|
37
|
+
// builds, so we mount ComponentSetPage at the story's route with
|
|
38
|
+
// `_sb_component_set` instead. `_sb_embed` keeps the canvas chrome off.
|
|
39
|
+
params.set('_sb_embed', '')
|
|
40
|
+
params.set('_sb_component_set', '')
|
|
36
41
|
if (layout) params.set('layout', layout)
|
|
37
42
|
if (selected) params.set('selected', selected)
|
|
38
43
|
if (density) params.set('density', density)
|
|
39
44
|
if (theme) params.set('theme', theme)
|
|
40
|
-
|
|
45
|
+
const route = story._route || `/components/${storyId}`
|
|
46
|
+
return `${base}${route}?${params}`
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdate, resizable }, ref) {
|
|
@@ -103,7 +109,7 @@ export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdat
|
|
|
103
109
|
if (typeof width === 'number' && typeof height === 'number') return
|
|
104
110
|
const headerH = 37
|
|
105
111
|
const newW = typeof width === 'number' ? width : Math.max(200, Math.ceil(e.data.width))
|
|
106
|
-
const newH = typeof height === 'number' ? height : Math.max(120, Math.ceil(e.data.height) + headerH)
|
|
112
|
+
const newH = typeof height === 'number' ? height : Math.max(120, Math.ceil(e.data.height) + headerH + 8)
|
|
107
113
|
onUpdate?.({ width: newW, height: newH })
|
|
108
114
|
} else if (e.data?.type === 'storyboard:component-set:content-size') {
|
|
109
115
|
contentSizeRef.current = {
|
|
@@ -128,7 +134,7 @@ export default forwardRef(function StorySetWidget({ id: widgetId, props, onUpdat
|
|
|
128
134
|
const contentH = contentSizeRef.current.height
|
|
129
135
|
const contentW = contentSizeRef.current.width
|
|
130
136
|
if (!contentH && !contentW) return
|
|
131
|
-
const fitH = contentH ? contentH + headerH : h
|
|
137
|
+
const fitH = contentH ? contentH + headerH + 8 : h
|
|
132
138
|
const fitW = contentW || w
|
|
133
139
|
const shouldSnapH = contentH && h > fitH + 2
|
|
134
140
|
const shouldSnapW = contentW && w > fitW + 2
|
|
@@ -513,12 +513,14 @@ export function buildSecondaryIframeUrl(widget) {
|
|
|
513
513
|
const storyData = getStoryData(storyId)
|
|
514
514
|
if (storyData?._storyModule) {
|
|
515
515
|
const params = new URLSearchParams()
|
|
516
|
-
params.set('
|
|
516
|
+
params.set('_sb_embed', '')
|
|
517
|
+
params.set('_sb_component_set', '')
|
|
517
518
|
const layout = widget.props?.layout
|
|
518
519
|
if (layout) params.set('layout', layout)
|
|
519
520
|
const selected = widget.props?.selected
|
|
520
521
|
if (selected) params.set('selected', selected)
|
|
521
|
-
|
|
522
|
+
const route = storyData._route || `/components/${storyId}`
|
|
523
|
+
return `${baseClean}${route}?${params}`
|
|
522
524
|
}
|
|
523
525
|
return null
|
|
524
526
|
}
|