Skip to content

Experiment: JS-driven story loop with Promise handshake#95

Open
EmilioBejasa wants to merge 4 commits into
mainfrom
experiment/js-driven-story-loop
Open

Experiment: JS-driven story loop with Promise handshake#95
EmilioBejasa wants to merge 4 commits into
mainfrom
experiment/js-driven-story-loop

Conversation

@EmilioBejasa
Copy link
Copy Markdown
Collaborator

Summary

  • JS owns the entire story sequence — no events from native, no deprecated sync methods
  • StoryRenderer calls createPreparedStoryMapping() once on mount, then iterates all stories in a for...of loop
  • After each story is rendered, JS calls await StorybookRegistry.notifyStoryReady(storyId) — a standard Promise call that resolves only after the test thread takes the screenshot
  • allStoriesDone() signals the test thread to exit and unmount the surface

How it works

JS: for (story of allStories)
      await renderDone   ← useEffect fires after React commits the story
      await notifyStoryReady(storyId)   ← blocks until native resolves Promise
                                  ↑
Test: awaitStoryReady() → screenshot → resolveCurrentStory() → Promise.resolve()

The renderResolverRef pattern bridges React's async render to the for...of loop: the loop awaits a Promise whose resolve is stored in a ref and called from useEffect([storyContent, error]) — the only reliable signal that React has committed the render.

Why useEffect is necessary

setCurrentStoryId() schedules a render asynchronously. Calling notifyStoryReady() immediately after would race against React's scheduler and potentially screenshot the wrong state. useEffect fires after commit, guaranteeing the view is painted before native is notified.

Advantages over sync-story-blocking branch

  • Works with new arch (newArchEnabled=true) — uses standard @ReactMethod Promise
  • No deprecated APIs
  • Native thread is purely reactive; JS controls ordering

Test plan

  • Create draft PR to see if CI passes
  • Verify story order matches between JS iteration and screenshots

🤖 Generated with Claude Code

EmilioBejasa and others added 2 commits March 27, 2026 12:49
JS owns the entire story sequence: createPreparedStoryMapping() once,
then for (story of allStories) { render; await notifyStoryReady(id) }.
notifyStoryReady() is a Promise call — native resolves it after taking
the screenshot, letting JS advance to the next story.

No event emitter, no isBlockingSynchronousMethod, no manifest pre-load.
renderResolverRef bridges the async loop to React's useEffect commit phase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fect

Two fixes for blank screenshots:

1. buildPreparedStories() accesses _preview.storyStoreValue directly instead
   of calling createPreparedStoryMapping(), which waits on storeInitialization-
   Promise. That Promise only resolves when the Storybook UI renders — which
   never happens in our test scenario. The importFn is a sync map lookup so
   loadStory() resolves in the next microtask.

2. Remove renderResolverRef. Instead of resolving an intermediate Promise in
   useEffect and then calling notifyStoryReady from an async loop, call
   notifyStoryReady directly in useEffect([storyContent, error]). This matches
   how the main branch calls notifyStoryReady: immediately after the commit,
   with no extra microtask hop that could race against Fabric's native mutations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EmilioBejasa and others added 2 commits March 30, 2026 12:28
Without an explicit measure/layout pass, Screenshot.snap(surface.view)
captures a blank bitmap on Fabric. Mirror what the old arch path already
does with ViewHelpers so the view tree is software-rendered and properly
sized before capture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ViewHelpers only sets software layer on the root view. Fabric child views
retain hardware display lists which view.draw(canvas) cannot capture,
producing blank (white background only) screenshots. Walk the full tree
and set LAYER_TYPE_SOFTWARE on every node before snapping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant