@dfosco/storyboard-react 4.0.0-beta.32 → 4.0.0-beta.34

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.32",
3
+ "version": "4.0.0-beta.34",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "4.0.0-beta.32",
7
- "@dfosco/tiny-canvas": "4.0.0-beta.32",
6
+ "@dfosco/storyboard-core": "4.0.0-beta.34",
7
+ "@dfosco/tiny-canvas": "4.0.0-beta.34",
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 (temporary) ──
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 ''
@@ -247,8 +255,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
247
255
  }, [showIframe])
248
256
 
249
257
  // Exit interactive mode when clicking outside the embed.
250
- // Hides iframe immediately for a responsive feel, then captures
251
- // snapshots in the background with the iframe hidden but still mounted.
258
+ // Captures a snapshot in the background, then unmounts the iframe.
252
259
  useEffect(() => {
253
260
  if (!interactive || expanded) return
254
261
  function handlePointerDown(e) {
@@ -280,8 +287,6 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
280
287
  })
281
288
  }, 0)
282
289
  } else if (isExternal && showIframe) {
283
- // External embeds (e.g. Figma) are slow to reload — keep the
284
- // iframe mounted for 2 min so re-entering is instant.
285
290
  const session = ++exitSessionRef.current
286
291
  clearTimeout(teardownTimerRef.current)
287
292
  teardownTimerRef.current = setTimeout(() => {
@@ -303,25 +308,27 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
303
308
  }), [])
304
309
 
305
310
  // On canvas theme change, enqueue a background snapshot refresh.
306
- // Skips the initial render (canvasThemeInitRef tracks first value).
307
- // Uses a ref to check hasSnap at callback time (not closure time).
308
- const canvasThemeInitRef = useRef(true)
311
+ // Only fires for true user-initiated theme changes (not page load).
312
+ // Uses mountTime to ignore the initial theme resolution that happens
313
+ // within the first 3 seconds of mount.
314
+ const mountTimeRef = useRef(Date.now())
309
315
  const refreshMetaRef = useRef(null)
310
316
  const hasSnapRef = useRef(hasSnap)
311
317
  hasSnapRef.current = hasSnap
312
318
  useEffect(() => {
313
- if (canvasThemeInitRef.current) { canvasThemeInitRef.current = false; return }
314
- if (isExternal || !onUpdate || interactive || !hasSnap) return
319
+ // Ignore theme changes during initial page load (first 3 seconds)
320
+ const elapsed = Date.now() - mountTimeRef.current
321
+ if (elapsed < 3000) { console.log(`[embed:${widgetId}] theme-effect: skip page-load (${elapsed}ms)`); return }
322
+ if (isExternal || !onUpdate || interactive) { console.log(`[embed:${widgetId}] theme-effect: skip (ext=${isExternal}, noUpdate=${!onUpdate}, interactive=${interactive})`); return }
323
+ if (!hasSnapRef.current) { console.log(`[embed:${widgetId}] theme-effect: skip (no snap)`); return }
324
+ console.log(`[embed:${widgetId}] theme-effect: enqueue refresh, hasSnapRef=${hasSnapRef.current}`)
315
325
  const rect = embedRef.current?.getBoundingClientRect()
316
326
  enqueueRefresh(widgetId, ({ revealOrder, batchStart }) => {
317
- // Re-check hasSnap at callback time — snapshot may have been
318
- // marked broken (404) between enqueue and execution.
319
327
  if (!hasSnapRef.current) return Promise.resolve(false)
320
328
  return new Promise((resolve) => {
321
329
  refreshMetaRef.current = { revealOrder, batchStart, resolve }
322
330
  captureOnReadyRef.current = true
323
331
  setShowIframe(true)
324
- // Safety timeout — report failure so retry pass picks it up
325
332
  setTimeout(() => { refreshMetaRef.current = null; resolve(false) }, 10000)
326
333
  })
327
334
  }, rect ? { x: rect.left, y: rect.top } : undefined)
@@ -357,7 +364,6 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
357
364
  }
358
365
  meta.resolve(!!snap)
359
366
  }
360
- // Wait for our reveal slot in the wave
361
367
  const elapsed = Date.now() - meta.batchStart
362
368
  const targetTime = meta.revealOrder * REVEAL_INTERVAL
363
369
  const wait = Math.max(0, targetTime - elapsed)
@@ -444,6 +450,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
444
450
  const chromeVars = useMemo(() => getEmbedChromeVars(canvasTheme), [canvasTheme])
445
451
 
446
452
  const enterInteractive = useCallback(() => {
453
+ console.log(`[embed:${widgetId}] enterInteractive`)
447
454
  exitSessionRef.current++
448
455
  clearTimeout(teardownTimerRef.current)
449
456
  cancelRefresh(widgetId)
@@ -598,7 +605,12 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
598
605
  className={styles.snapshotImage}
599
606
  alt={`${prototypeTitle} snapshot`}
600
607
  draggable={false}
601
- onError={() => setBrokenSnaps(prev => ({ ...prev, [snapshot]: true }))}
608
+ onError={() => {
609
+ console.log(`[embed:${widgetId}] snapshot img onError: ${snapshot?.slice(0, 60)}`)
610
+ setBrokenSnaps(prev => ({ ...prev, [snapshot]: true }))
611
+ // Clear the broken snapshot from widget data so we stop trying
612
+ onUpdate?.({ snapshot: '' })
613
+ }}
602
614
  />
603
615
  )}
604
616
 
@@ -616,7 +628,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
616
628
  transition: 'opacity 150ms ease',
617
629
  ...(iframeLoaded ? {} : { opacity: 0 }),
618
630
  }}
619
- onLoad={() => setIframeLoaded(true)}
631
+ onLoad={() => { console.log(`[embed:${widgetId}] iframe onLoad`); setIframeLoaded(true) }}
620
632
  title={`${prototypeTitle} prototype`}
621
633
  sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
622
634
  />
@@ -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
  }