@dfosco/storyboard-react 4.0.0-beta.31 → 4.0.0-beta.33

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
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard-react",
3
- "version": "4.0.0-beta.31",
3
+ "version": "4.0.0-beta.33",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "4.0.0-beta.31",
7
- "@dfosco/tiny-canvas": "4.0.0-beta.31",
6
+ "@dfosco/storyboard-core": "4.0.0-beta.33",
7
+ "@dfosco/tiny-canvas": "4.0.0-beta.33",
8
8
  "@neodrag/react": "^2.3.1",
9
9
  "glob": "^11.0.0",
10
10
  "jsonc-parser": "^3.3.1",
@@ -83,13 +83,20 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
83
83
  const scale = zoom / 100
84
84
 
85
85
  const [editing, setEditing] = useState(false)
86
- const [interactive, setInteractive] = useState(false)
87
- const [showIframe, setShowIframe] = useState(false)
88
- const [iframeLoaded, setIframeLoaded] = useState(false)
86
+ const [interactive, _setInteractive] = useState(false)
87
+ const [showIframe, _setShowIframe] = useState(false)
88
+ const [iframeLoaded, _setIframeLoaded] = useState(false)
89
89
  const [expanded, setExpanded] = useState(false)
90
90
  const [filter, setFilter] = useState('')
91
- const [canvasTheme, setCanvasTheme] = useState('light')
92
- const [brokenSnaps, setBrokenSnaps] = useState({})
91
+ const [canvasTheme, _setCanvasTheme] = useState('light')
92
+ const [brokenSnaps, _setBrokenSnaps] = useState({})
93
+
94
+ // ── Debug logging wrappers ──
95
+ const setInteractive = (v) => { console.log(`[embed:${widgetId}] setInteractive →`, v); _setInteractive(v) }
96
+ const setShowIframe = (v) => { if (v) console.trace(`[embed:${widgetId}] setShowIframe → TRUE`); else console.log(`[embed:${widgetId}] setShowIframe → false`); _setShowIframe(v) }
97
+ const setIframeLoaded = (v) => { console.log(`[embed:${widgetId}] iframeLoaded →`, v); _setIframeLoaded(v) }
98
+ const setCanvasTheme = (v) => { console.log(`[embed:${widgetId}] canvasTheme →`, v); _setCanvasTheme(v) }
99
+ const setBrokenSnaps = (fn) => { console.log(`[embed:${widgetId}] setBrokenSnaps`); _setBrokenSnaps(fn) }
93
100
 
94
101
  const inputRef = useRef(null)
95
102
  const filterRef = useRef(null)
@@ -114,6 +121,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
114
121
 
115
122
  // Single snapshot — backward compat reads snapshotLight/snapshotDark if snapshot is missing
116
123
  const hasSnap = !isExternal && !!(snapshot && snapshot.includes(widgetId) && !brokenSnaps[snapshot])
124
+ console.log(`[embed:${widgetId}] render: hasSnap=${hasSnap}, showIframe=${showIframe}, interactive=${interactive}, canvasTheme=${canvasTheme}, snapshot=${snapshot ? snapshot.slice(0, 50) : 'null'}, broken=${!!brokenSnaps[snapshot]}`)
117
125
 
118
126
  const iframeSrc = useMemo(() => {
119
127
  if (!rawSrc) return ''
@@ -243,6 +251,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
243
251
  }, [editing, hasPicker])
244
252
 
245
253
  useEffect(() => {
254
+ console.log(`[embed:${widgetId}] effect:showIframe →`, showIframe)
246
255
  if (!showIframe) setIframeLoaded(false)
247
256
  }, [showIframe])
248
257
 
@@ -251,20 +260,24 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
251
260
  // snapshots in the background with the iframe hidden but still mounted.
252
261
  useEffect(() => {
253
262
  if (!interactive || expanded) return
263
+ console.log(`[embed:${widgetId}] effect:exit-interactive listener attached`)
254
264
  function handlePointerDown(e) {
255
265
  if (embedRef.current && !embedRef.current.contains(e.target)) {
256
266
  const chromeEl = e.target.closest(`[data-widget-id="${widgetId}"]`)
257
267
  if (chromeEl) return
258
268
 
269
+ console.log(`[embed:${widgetId}] exit-interactive: pointerdown outside, iframeLoaded=${iframeLoaded}, hasContentWindow=${!!iframeRef.current?.contentWindow}`)
259
270
  setInteractive(false)
260
271
  if (onUpdate && !isExternal && iframeLoaded && iframeRef.current?.contentWindow) {
261
272
  if (iframeRef.current) iframeRef.current.style.visibility = 'hidden'
262
273
  const session = ++exitSessionRef.current
274
+ console.log(`[embed:${widgetId}] exit-interactive: starting capture, session=${session}`)
263
275
  setTimeout(() => {
264
- if (exitSessionRef.current !== session) return
276
+ if (exitSessionRef.current !== session) { console.log(`[embed:${widgetId}] exit-interactive: stale session ${session}, current=${exitSessionRef.current}`); return }
265
277
  requestCapture({ force: true }).then((updates) => {
266
- if (exitSessionRef.current !== session) return
278
+ if (exitSessionRef.current !== session) { console.log(`[embed:${widgetId}] exit-interactive: stale session after capture`); return }
267
279
  const snap = updates?.snapshot
280
+ console.log(`[embed:${widgetId}] exit-interactive: capture done, snap=${snap ? 'yes(' + snap.length + ')' : 'null'}`)
268
281
  if (snap) {
269
282
  const img = new Image()
270
283
  const done = () => {
@@ -304,13 +317,19 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
304
317
 
305
318
  // On canvas theme change, enqueue a background snapshot refresh.
306
319
  // Skips the initial render (canvasThemeInitRef tracks first value).
320
+ // Uses a ref to check hasSnap at callback time (not closure time).
307
321
  const canvasThemeInitRef = useRef(true)
308
322
  const refreshMetaRef = useRef(null)
323
+ const hasSnapRef = useRef(hasSnap)
324
+ hasSnapRef.current = hasSnap
309
325
  useEffect(() => {
310
- if (canvasThemeInitRef.current) { canvasThemeInitRef.current = false; return }
311
- if (isExternal || !onUpdate || interactive || !hasSnap) return
326
+ if (canvasThemeInitRef.current) { canvasThemeInitRef.current = false; console.log(`[embed:${widgetId}] theme-effect: skip init`); return }
327
+ if (isExternal || !onUpdate || interactive || !hasSnap) { console.log(`[embed:${widgetId}] theme-effect: skip (ext=${isExternal}, noUpdate=${!onUpdate}, interactive=${interactive}, hasSnap=${hasSnap})`); return }
328
+ console.log(`[embed:${widgetId}] theme-effect: enqueue refresh, hasSnap=${hasSnap}, hasSnapRef=${hasSnapRef.current}`)
312
329
  const rect = embedRef.current?.getBoundingClientRect()
313
330
  enqueueRefresh(widgetId, ({ revealOrder, batchStart }) => {
331
+ console.log(`[embed:${widgetId}] refresh-callback: hasSnapRef=${hasSnapRef.current}`)
332
+ if (!hasSnapRef.current) { console.log(`[embed:${widgetId}] refresh-callback: ABORT, no snap`); return Promise.resolve(false) }
314
333
  return new Promise((resolve) => {
315
334
  refreshMetaRef.current = { revealOrder, batchStart, resolve }
316
335
  captureOnReadyRef.current = true
@@ -323,8 +342,10 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
323
342
 
324
343
  // Capture snapshot on first iframe ready (when no existing snapshot)
325
344
  useEffect(() => {
345
+ console.log(`[embed:${widgetId}] effect:iframeReady → ${iframeReady}, onUpdate=${!!onUpdate}, isExternal=${isExternal}, hasSnap=${hasSnap}`)
326
346
  if (!iframeReady || !onUpdate || isExternal) return
327
347
  if (!hasSnap) {
348
+ console.log(`[embed:${widgetId}] first-ready: requestCapture (no snap)`)
328
349
  requestCapture()
329
350
  }
330
351
  }, [iframeReady]) // eslint-disable-line react-hooks/exhaustive-deps
@@ -333,8 +354,10 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
333
354
  useEffect(() => {
334
355
  if (iframeReady && captureOnReadyRef.current) {
335
356
  captureOnReadyRef.current = false
357
+ console.log(`[embed:${widgetId}] captureOnReady: requestCapture`)
336
358
  requestCapture().then((updates) => {
337
359
  const meta = refreshMetaRef.current
360
+ console.log(`[embed:${widgetId}] captureOnReady: done, snap=${updates?.snapshot ? 'yes' : 'null'}, hasMeta=${!!meta}`)
338
361
  if (meta) {
339
362
  refreshMetaRef.current = null
340
363
  const snap = updates?.snapshot
@@ -438,6 +461,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
438
461
  const chromeVars = useMemo(() => getEmbedChromeVars(canvasTheme), [canvasTheme])
439
462
 
440
463
  const enterInteractive = useCallback(() => {
464
+ console.log(`[embed:${widgetId}] enterInteractive`)
441
465
  exitSessionRef.current++
442
466
  clearTimeout(teardownTimerRef.current)
443
467
  cancelRefresh(widgetId)
@@ -592,7 +616,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
592
616
  className={styles.snapshotImage}
593
617
  alt={`${prototypeTitle} snapshot`}
594
618
  draggable={false}
595
- onError={() => setBrokenSnaps(prev => ({ ...prev, [snapshot]: true }))}
619
+ onError={() => { console.log(`[embed:${widgetId}] snapshot img onError: ${snapshot?.slice(0, 60)}`); setBrokenSnaps(prev => ({ ...prev, [snapshot]: true })) }}
596
620
  />
597
621
  )}
598
622
 
@@ -610,7 +634,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
610
634
  transition: 'opacity 150ms ease',
611
635
  ...(iframeLoaded ? {} : { opacity: 0 }),
612
636
  }}
613
- onLoad={() => setIframeLoaded(true)}
637
+ onLoad={() => { console.log(`[embed:${widgetId}] iframe onLoad`); setIframeLoaded(true) }}
614
638
  title={`${prototypeTitle} prototype`}
615
639
  sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
616
640
  />
@@ -31,6 +31,7 @@ export const REVEAL_INTERVAL = 200
31
31
  * @param {{ x: number, y: number }} [pos] — spatial position for wave ordering
32
32
  */
33
33
  export function enqueueRefresh(widgetId, fn, pos) {
34
+ console.log(`[refreshQueue] enqueue: ${widgetId}, queueLen=${queue.length}`)
34
35
  const existing = queue.findIndex(item => item.widgetId === widgetId)
35
36
  if (existing !== -1) queue.splice(existing, 1)
36
37
 
@@ -69,11 +70,13 @@ function scheduleDrain() {
69
70
 
70
71
  function onTaskDone(success, item) {
71
72
  batchDone++
73
+ console.log(`[refreshQueue] taskDone: ${item.widgetId}, success=${success}, done=${batchDone}/${batchTotal}, retry=${item.isRetry}`)
72
74
  if (!success && !item.isRetry) {
73
75
  batchFailed.push(item)
74
76
  }
75
77
  // When batch is complete, re-enqueue failures for one retry
76
78
  if (batchDone >= batchTotal && batchFailed.length > 0) {
79
+ console.log(`[refreshQueue] batch complete, retrying ${batchFailed.length} failed`)
77
80
  const retries = batchFailed.splice(0)
78
81
  for (const failed of retries) {
79
82
  failed.isRetry = true
@@ -95,6 +95,7 @@ export function useSnapshotCapture({
95
95
  }
96
96
 
97
97
  if (e.data?.type === 'storyboard:embed:snapshot-ready') {
98
+ console.log(`[snapshot:${widgetId}] iframe ready`)
98
99
  setIframeReady(true)
99
100
  iframeReadyRef.current = true
100
101
  }
@@ -115,10 +116,11 @@ export function useSnapshotCapture({
115
116
  * Uploads and saves as `snapshot` prop, overwriting any previous value.
116
117
  */
117
118
  const requestCapture = useCallback(async ({ force = false } = {}) => {
119
+ console.log(`[snapshot:${widgetId}] requestCapture: force=${force}, hasContentWindow=${!!iframeRef.current?.contentWindow}, capturing=${capturingRef.current}, ready=${iframeReadyRef.current}`)
118
120
  if (!onUpdate) return {}
119
- if (!iframeRef.current?.contentWindow) return {}
120
- if (capturingRef.current) return {}
121
- if (!force && !iframeReadyRef.current) return {}
121
+ if (!iframeRef.current?.contentWindow) { console.log(`[snapshot:${widgetId}] requestCapture: no contentWindow`); return {} }
122
+ if (capturingRef.current) { console.log(`[snapshot:${widgetId}] requestCapture: already capturing`); return {} }
123
+ if (!force && !iframeReadyRef.current) { console.log(`[snapshot:${widgetId}] requestCapture: not ready`); return {} }
122
124
 
123
125
  capturingRef.current = true
124
126
  const gen = ++captureGeneration.current
@@ -129,18 +131,20 @@ export function useSnapshotCapture({
129
131
  const reqId = ++requestIdCounter.current
130
132
  const dataUrl = await captureOnce(cw, reqId, responseHandlers.current)
131
133
 
132
- if (gen !== captureGeneration.current) return {}
133
- if (!dataUrl) return {}
134
+ if (gen !== captureGeneration.current) { console.log(`[snapshot:${widgetId}] stale gen after capture`); return {} }
135
+ if (!dataUrl) { console.log(`[snapshot:${widgetId}] captureOnce returned null`); return {} }
134
136
 
135
137
  const filename = `snapshot-${widgetId}.webp`
138
+ console.log(`[snapshot:${widgetId}] uploading ${filename}`)
136
139
  const result = await uploadImage(dataUrl, `snapshot-${widgetId}`, filename)
137
140
 
138
- if (gen !== captureGeneration.current) return {}
141
+ if (gen !== captureGeneration.current) { console.log(`[snapshot:${widgetId}] stale gen after upload`); return {} }
139
142
 
140
143
  if (result?.filename) {
141
144
  const cacheBust = `?v=${Date.now()}`
142
145
  const url = `${base}/_storyboard/canvas/images/${result.filename}${cacheBust}`
143
146
  const updates = { snapshot: url }
147
+ console.log(`[snapshot:${widgetId}] saved: ${url.slice(0, 60)}`)
144
148
  onUpdate?.(updates)
145
149
  return updates
146
150
  }