Skip to content

Experiment: blocking sync pull via awaitNextStory()#94

Open
EmilioBejasa wants to merge 5 commits intomainfrom
experiment/sync-story-blocking
Open

Experiment: blocking sync pull via awaitNextStory()#94
EmilioBejasa wants to merge 5 commits intomainfrom
experiment/sync-story-blocking

Conversation

@EmilioBejasa
Copy link
Copy Markdown
Collaborator

Summary

  • Replaces the loadStory event emitter with a pull model: JS calls the synchronous native method awaitNextStory(), which blocks the JS thread until the test runner enqueues the next story ID via a LinkedBlockingQueue
  • Single React surface for the entire run — no remount per story
  • notifyStoryReady() (existing latch) still signals native after each render

How it works

Test thread → pushStory(storyId) → LinkedBlockingQueue
JS thread   ← awaitNextStory()   ← LinkedBlockingQueue  (JS thread blocks)
JS thread   → notifyStoryReady()
Test thread ← awaitStoryReady()  ← CountDownLatch
Test thread → takes screenshot → pushStory(nextId)

Known limitation

@ReactMethod(isBlockingSynchronousMethod = true) is deprecated in the new architecture. With newArchEnabled=true this may not block — awaitNextStory() could return undefined via the interop layer. This branch exists to test whether the approach works at all.

Test plan

  • Create draft PR to see if CI passes
  • Check logcat to verify awaitNextStory() actually blocks the JS thread

🤖 Generated with Claude Code

EmilioBejasa and others added 2 commits March 27, 2026 12:37
…ries

Instead of native emitting loadStory events, JS blocks itself via
isBlockingSynchronousMethod awaitNextStory(). The test thread pushes
story IDs into a LinkedBlockingQueue; JS unblocks, renders, calls
notifyStoryReady(), then blocks again. One surface, no event emitter.

NOTE: isBlockingSynchronousMethod is deprecated in new arch — this
branch exists to test whether the approach works via the interop layer.

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

isBlockingSynchronousMethod is silently ignored in new arch bridgeless mode —
the call returns undefined immediately, causing an infinite loop.

Fix: awaitNextStory() is now a regular @ReactMethod(promise) that blocks
on a background thread. JS uses await instead of a synchronous call. The
LinkedBlockingQueue and push/await handshake are unchanged; only the
bridge mechanism changes.

Also apply buildPreparedStories() fix from experiment/js-driven-story-loop:
access _preview.storyStoreValue directly to skip _preview.ready(), which
never resolves when the Storybook UI isn't rendered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EmilioBejasa and others added 3 commits March 30, 2026 12:16
LinkedBlockingQueue.put(null) throws NullPointerException. Replace the
nullable queue with a String queue and map null to/from a DONE_SENTINEL
so callers can still use pushStory(null) to signal completion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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