@dfosco/storyboard 0.5.0-beta.34 → 0.5.0-beta.36
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
|
@@ -659,6 +659,21 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
659
659
|
// Local mutable copy of widgets for instant UI updates
|
|
660
660
|
const [localWidgets, setLocalWidgets] = useState(canvas?.widgets ?? null)
|
|
661
661
|
const [localConnectors, setLocalConnectors] = useState(canvas?.connectors ?? [])
|
|
662
|
+
// Track widget/connector IDs the user has just deleted locally so the HMR
|
|
663
|
+
// reconcile in the canvas-changed handler doesn't re-add them. Each ID is
|
|
664
|
+
// pruned once a server push confirms it's gone OR after a 5s safety timeout.
|
|
665
|
+
const pendingWidgetDeletionsRef = useRef(new Set())
|
|
666
|
+
const pendingConnectorDeletionsRef = useRef(new Set())
|
|
667
|
+
const markWidgetDeleted = useCallback((widgetId) => {
|
|
668
|
+
if (!widgetId) return
|
|
669
|
+
pendingWidgetDeletionsRef.current.add(widgetId)
|
|
670
|
+
setTimeout(() => pendingWidgetDeletionsRef.current.delete(widgetId), 5000)
|
|
671
|
+
}, [])
|
|
672
|
+
const markConnectorDeleted = useCallback((connectorId) => {
|
|
673
|
+
if (!connectorId) return
|
|
674
|
+
pendingConnectorDeletionsRef.current.add(connectorId)
|
|
675
|
+
setTimeout(() => pendingConnectorDeletionsRef.current.delete(connectorId), 5000)
|
|
676
|
+
}, [])
|
|
662
677
|
const [trackedCanvas, setTrackedCanvas] = useState(canvas)
|
|
663
678
|
const [selectedWidgetIds, setSelectedWidgetIds] = useState(() => new Set())
|
|
664
679
|
const initialViewport = loadViewportState(canvasId)
|
|
@@ -899,13 +914,15 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
899
914
|
setLocalWidgets((prev) => {
|
|
900
915
|
if (!prev) return serverWidgets
|
|
901
916
|
const localIds = new Set(prev.map((w) => w.id))
|
|
902
|
-
const
|
|
917
|
+
const pending = pendingWidgetDeletionsRef.current
|
|
918
|
+
const additions = serverWidgets.filter((w) => !localIds.has(w.id) && !pending.has(w.id))
|
|
903
919
|
return additions.length > 0 ? [...prev, ...additions] : prev
|
|
904
920
|
})
|
|
905
921
|
setLocalConnectors((prev) => {
|
|
906
|
-
if (!prev || prev.length === 0) return serverConnectors
|
|
922
|
+
if (!prev || prev.length === 0) return serverConnectors.filter((c) => !pendingConnectorDeletionsRef.current.has(c.id))
|
|
907
923
|
const localIds = new Set(prev.map((c) => c.id))
|
|
908
|
-
const
|
|
924
|
+
const pending = pendingConnectorDeletionsRef.current
|
|
925
|
+
const additions = serverConnectors.filter((c) => !localIds.has(c.id) && !pending.has(c.id))
|
|
909
926
|
return additions.length > 0 ? [...prev, ...additions] : prev
|
|
910
927
|
})
|
|
911
928
|
}
|
|
@@ -1008,12 +1025,14 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
1008
1025
|
debouncedSave.cancel()
|
|
1009
1026
|
|
|
1010
1027
|
undoRedo.snapshot(stateRef.current, 'remove', widgetId)
|
|
1028
|
+
markWidgetDeleted(widgetId)
|
|
1011
1029
|
setLocalWidgets((prev) => prev ? prev.filter((w) => w.id !== widgetId) : prev)
|
|
1012
1030
|
// Cascade: remove connectors referencing this widget
|
|
1013
1031
|
setLocalConnectors((prev) => {
|
|
1014
1032
|
const orphaned = prev.filter((c) => c.start.widgetId === widgetId || c.end.widgetId === widgetId)
|
|
1015
1033
|
if (orphaned.length === 0) return prev
|
|
1016
1034
|
for (const c of orphaned) {
|
|
1035
|
+
markConnectorDeleted(c.id)
|
|
1017
1036
|
queueWrite(() =>
|
|
1018
1037
|
removeConnectorApi(canvasId, c.id).catch((err) =>
|
|
1019
1038
|
console.error('[canvas] Failed to remove orphaned connector:', err)
|
|
@@ -1027,7 +1046,7 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
1027
1046
|
removeWidgetApi(canvasId, widgetId)
|
|
1028
1047
|
.catch((err) => console.error('[canvas] Failed to remove widget:', err))
|
|
1029
1048
|
)
|
|
1030
|
-
}, [canvasId, undoRedo, debouncedSave])
|
|
1049
|
+
}, [canvasId, undoRedo, debouncedSave, markWidgetDeleted, markConnectorDeleted])
|
|
1031
1050
|
|
|
1032
1051
|
const handleConnectorAdd = useCallback(async ({ startWidgetId, startAnchor, endWidgetId, endAnchor }) => {
|
|
1033
1052
|
try {
|
|
@@ -1075,6 +1094,7 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
1075
1094
|
|
|
1076
1095
|
const handleConnectorRemove = useCallback((connectorId) => {
|
|
1077
1096
|
undoRedo.snapshot(stateRef.current, 'connector-remove')
|
|
1097
|
+
markConnectorDeleted(connectorId)
|
|
1078
1098
|
setLocalConnectors((prev) => prev.filter((c) => c.id !== connectorId))
|
|
1079
1099
|
dirtyRef.current = true
|
|
1080
1100
|
queueWrite(() =>
|
|
@@ -1082,7 +1102,7 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
1082
1102
|
console.error('[canvas] Failed to remove connector:', err)
|
|
1083
1103
|
)
|
|
1084
1104
|
)
|
|
1085
|
-
}, [canvasId, undoRedo])
|
|
1105
|
+
}, [canvasId, undoRedo, markConnectorDeleted])
|
|
1086
1106
|
|
|
1087
1107
|
// Connector drag state
|
|
1088
1108
|
const [connectorDrag, setConnectorDrag] = useState(null)
|
|
@@ -2677,11 +2697,15 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
2677
2697
|
undoRedo.snapshot(stateRef.current, 'multi-remove')
|
|
2678
2698
|
debouncedSave.cancel()
|
|
2679
2699
|
const idsToRemove = new Set(selectedWidgetIds)
|
|
2700
|
+
// Mark deletions before mutating local state so the HMR merge
|
|
2701
|
+
// doesn't re-add them mid-flight.
|
|
2702
|
+
for (const id of idsToRemove) markWidgetDeleted(id)
|
|
2680
2703
|
// Remove from local state immediately
|
|
2681
2704
|
setLocalWidgets((prev) => prev ? prev.filter(w => !idsToRemove.has(w.id)) : prev)
|
|
2682
2705
|
setLocalConnectors((prev) => {
|
|
2683
2706
|
const orphaned = prev.filter((c) => idsToRemove.has(c.start.widgetId) || idsToRemove.has(c.end.widgetId))
|
|
2684
2707
|
for (const c of orphaned) {
|
|
2708
|
+
markConnectorDeleted(c.id)
|
|
2685
2709
|
queueWrite(() =>
|
|
2686
2710
|
removeConnectorApi(canvasId, c.id).catch((err) =>
|
|
2687
2711
|
console.error('[canvas] Failed to remove orphaned connector:', err)
|
|
@@ -2708,7 +2732,7 @@ export default function CanvasPage({ canvasId: canvasIdProp, name, siblingPages
|
|
|
2708
2732
|
}
|
|
2709
2733
|
document.addEventListener('keydown', handleKeyDown)
|
|
2710
2734
|
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
2711
|
-
}, [selectedWidgetIds, localWidgets, handleWidgetRemove, undoRedo, canvasId, debouncedSave])
|
|
2735
|
+
}, [selectedWidgetIds, localWidgets, handleWidgetRemove, undoRedo, canvasId, debouncedSave, markWidgetDeleted, markConnectorDeleted])
|
|
2712
2736
|
|
|
2713
2737
|
// Ref to store processImageFile for use by drop effect
|
|
2714
2738
|
const processImageFileRef = useRef(null)
|
|
@@ -63,6 +63,14 @@ const DEFAULT_THEME = {
|
|
|
63
63
|
selectionBackground: '#264f78',
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function normalizeStatus(s) {
|
|
67
|
+
if (s === 'completed') return 'done'
|
|
68
|
+
if (s === 'running' || s === 'working' || s === 'pending') return 'pending'
|
|
69
|
+
if (s === 'error') return 'error'
|
|
70
|
+
if (s === 'done') return 'done'
|
|
71
|
+
return 'idle'
|
|
72
|
+
}
|
|
73
|
+
|
|
66
74
|
function calcMiniDimensions(widthPx, heightPx) {
|
|
67
75
|
const scale = MINI_FONT_SIZE / 13
|
|
68
76
|
const cellWidth = 7.8 * scale
|
|
@@ -81,7 +89,7 @@ const PromptWidget = forwardRef(function PromptWidget({ id, props, onUpdate, res
|
|
|
81
89
|
const width = readProp(props, 'width', promptSchema)
|
|
82
90
|
const height = readProp(props, 'height', promptSchema)
|
|
83
91
|
const [draftText, setDraftText] = useState('')
|
|
84
|
-
const [execStatus, setExecStatus] = useState(persistedStatus
|
|
92
|
+
const [execStatus, setExecStatus] = useState(normalizeStatus(persistedStatus))
|
|
85
93
|
const [execError, setExecError] = useState(errorMessage || '')
|
|
86
94
|
const [showOutput, setShowOutput] = useState(false)
|
|
87
95
|
const canEdit = typeof onUpdate === 'function'
|
|
@@ -125,10 +133,10 @@ const PromptWidget = forwardRef(function PromptWidget({ id, props, onUpdate, res
|
|
|
125
133
|
onUpdateRef.current?.({ status: 'idle', sessionId: '', errorMessage: '' })
|
|
126
134
|
} else if (data.status === 'working') {
|
|
127
135
|
setExecStatus('pending')
|
|
128
|
-
onUpdateRef.current?.({ status: '
|
|
136
|
+
onUpdateRef.current?.({ status: 'pending' })
|
|
129
137
|
} else if (data.status === 'running' || data.status === 'pending') {
|
|
130
138
|
setExecStatus('pending')
|
|
131
|
-
onUpdateRef.current?.({ status: '
|
|
139
|
+
onUpdateRef.current?.({ status: 'pending' })
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -284,9 +284,21 @@ export default forwardRef(function TerminalWidget({ id, props, onUpdate, multiSe
|
|
|
284
284
|
return
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
const dims = calcDimensions(width, height, fontSize)
|
|
288
287
|
const cfg = getTerminalConfig()
|
|
289
288
|
|
|
289
|
+
// Wait for web fonts to load before initializing ghostty's glyph atlas.
|
|
290
|
+
// Without this, ghostty rasterizes glyphs against fallback font metrics,
|
|
291
|
+
// then when the real font swaps in, glyphs render misaligned against the
|
|
292
|
+
// cell grid (visible as broken box-drawing characters and overstrike on
|
|
293
|
+
// letters). Especially visible on consumer installs where fonts come
|
|
294
|
+
// over the network instead of from cache.
|
|
295
|
+
if (typeof document !== 'undefined' && document.fonts?.ready) {
|
|
296
|
+
try { await document.fonts.ready } catch {}
|
|
297
|
+
if (disposed) return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const dims = calcDimensions(width, height, fontSize)
|
|
301
|
+
|
|
290
302
|
term = new ghostty.Terminal({
|
|
291
303
|
fontSize: cfg.fontSize ?? 13,
|
|
292
304
|
fontFamily: cfg.fontFamily ?? "'Ghostty', 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace",
|