@dfosco/storyboard-react 4.0.0-beta.33 → 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.33",
3
+ "version": "4.0.0-beta.34",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "4.0.0-beta.33",
7
- "@dfosco/tiny-canvas": "4.0.0-beta.33",
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",
@@ -91,7 +91,7 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
91
91
  const [canvasTheme, _setCanvasTheme] = useState('light')
92
92
  const [brokenSnaps, _setBrokenSnaps] = useState({})
93
93
 
94
- // ── Debug logging wrappers ──
94
+ // ── Debug logging wrappers (temporary) ──
95
95
  const setInteractive = (v) => { console.log(`[embed:${widgetId}] setInteractive →`, v); _setInteractive(v) }
96
96
  const setShowIframe = (v) => { if (v) console.trace(`[embed:${widgetId}] setShowIframe → TRUE`); else console.log(`[embed:${widgetId}] setShowIframe → false`); _setShowIframe(v) }
97
97
  const setIframeLoaded = (v) => { console.log(`[embed:${widgetId}] iframeLoaded →`, v); _setIframeLoaded(v) }
@@ -251,33 +251,27 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
251
251
  }, [editing, hasPicker])
252
252
 
253
253
  useEffect(() => {
254
- console.log(`[embed:${widgetId}] effect:showIframe →`, showIframe)
255
254
  if (!showIframe) setIframeLoaded(false)
256
255
  }, [showIframe])
257
256
 
258
257
  // Exit interactive mode when clicking outside the embed.
259
- // Hides iframe immediately for a responsive feel, then captures
260
- // snapshots in the background with the iframe hidden but still mounted.
258
+ // Captures a snapshot in the background, then unmounts the iframe.
261
259
  useEffect(() => {
262
260
  if (!interactive || expanded) return
263
- console.log(`[embed:${widgetId}] effect:exit-interactive listener attached`)
264
261
  function handlePointerDown(e) {
265
262
  if (embedRef.current && !embedRef.current.contains(e.target)) {
266
263
  const chromeEl = e.target.closest(`[data-widget-id="${widgetId}"]`)
267
264
  if (chromeEl) return
268
265
 
269
- console.log(`[embed:${widgetId}] exit-interactive: pointerdown outside, iframeLoaded=${iframeLoaded}, hasContentWindow=${!!iframeRef.current?.contentWindow}`)
270
266
  setInteractive(false)
271
267
  if (onUpdate && !isExternal && iframeLoaded && iframeRef.current?.contentWindow) {
272
268
  if (iframeRef.current) iframeRef.current.style.visibility = 'hidden'
273
269
  const session = ++exitSessionRef.current
274
- console.log(`[embed:${widgetId}] exit-interactive: starting capture, session=${session}`)
275
270
  setTimeout(() => {
276
- if (exitSessionRef.current !== session) { console.log(`[embed:${widgetId}] exit-interactive: stale session ${session}, current=${exitSessionRef.current}`); return }
271
+ if (exitSessionRef.current !== session) return
277
272
  requestCapture({ force: true }).then((updates) => {
278
- if (exitSessionRef.current !== session) { console.log(`[embed:${widgetId}] exit-interactive: stale session after capture`); return }
273
+ if (exitSessionRef.current !== session) return
279
274
  const snap = updates?.snapshot
280
- console.log(`[embed:${widgetId}] exit-interactive: capture done, snap=${snap ? 'yes(' + snap.length + ')' : 'null'}`)
281
275
  if (snap) {
282
276
  const img = new Image()
283
277
  const done = () => {
@@ -293,8 +287,6 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
293
287
  })
294
288
  }, 0)
295
289
  } else if (isExternal && showIframe) {
296
- // External embeds (e.g. Figma) are slow to reload — keep the
297
- // iframe mounted for 2 min so re-entering is instant.
298
290
  const session = ++exitSessionRef.current
299
291
  clearTimeout(teardownTimerRef.current)
300
292
  teardownTimerRef.current = setTimeout(() => {
@@ -316,25 +308,27 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
316
308
  }), [])
317
309
 
318
310
  // On canvas theme change, enqueue a background snapshot refresh.
319
- // Skips the initial render (canvasThemeInitRef tracks first value).
320
- // Uses a ref to check hasSnap at callback time (not closure time).
321
- 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())
322
315
  const refreshMetaRef = useRef(null)
323
316
  const hasSnapRef = useRef(hasSnap)
324
317
  hasSnapRef.current = hasSnap
325
318
  useEffect(() => {
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}`)
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}`)
329
325
  const rect = embedRef.current?.getBoundingClientRect()
330
326
  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) }
327
+ if (!hasSnapRef.current) return Promise.resolve(false)
333
328
  return new Promise((resolve) => {
334
329
  refreshMetaRef.current = { revealOrder, batchStart, resolve }
335
330
  captureOnReadyRef.current = true
336
331
  setShowIframe(true)
337
- // Safety timeout — report failure so retry pass picks it up
338
332
  setTimeout(() => { refreshMetaRef.current = null; resolve(false) }, 10000)
339
333
  })
340
334
  }, rect ? { x: rect.left, y: rect.top } : undefined)
@@ -342,10 +336,8 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
342
336
 
343
337
  // Capture snapshot on first iframe ready (when no existing snapshot)
344
338
  useEffect(() => {
345
- console.log(`[embed:${widgetId}] effect:iframeReady → ${iframeReady}, onUpdate=${!!onUpdate}, isExternal=${isExternal}, hasSnap=${hasSnap}`)
346
339
  if (!iframeReady || !onUpdate || isExternal) return
347
340
  if (!hasSnap) {
348
- console.log(`[embed:${widgetId}] first-ready: requestCapture (no snap)`)
349
341
  requestCapture()
350
342
  }
351
343
  }, [iframeReady]) // eslint-disable-line react-hooks/exhaustive-deps
@@ -354,10 +346,8 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
354
346
  useEffect(() => {
355
347
  if (iframeReady && captureOnReadyRef.current) {
356
348
  captureOnReadyRef.current = false
357
- console.log(`[embed:${widgetId}] captureOnReady: requestCapture`)
358
349
  requestCapture().then((updates) => {
359
350
  const meta = refreshMetaRef.current
360
- console.log(`[embed:${widgetId}] captureOnReady: done, snap=${updates?.snapshot ? 'yes' : 'null'}, hasMeta=${!!meta}`)
361
351
  if (meta) {
362
352
  refreshMetaRef.current = null
363
353
  const snap = updates?.snapshot
@@ -374,7 +364,6 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
374
364
  }
375
365
  meta.resolve(!!snap)
376
366
  }
377
- // Wait for our reveal slot in the wave
378
367
  const elapsed = Date.now() - meta.batchStart
379
368
  const targetTime = meta.revealOrder * REVEAL_INTERVAL
380
369
  const wait = Math.max(0, targetTime - elapsed)
@@ -616,7 +605,12 @@ export default forwardRef(function PrototypeEmbed({ id: widgetId, props, onUpdat
616
605
  className={styles.snapshotImage}
617
606
  alt={`${prototypeTitle} snapshot`}
618
607
  draggable={false}
619
- onError={() => { console.log(`[embed:${widgetId}] snapshot img onError: ${snapshot?.slice(0, 60)}`); 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
+ }}
620
614
  />
621
615
  )}
622
616