Skip to content

experiment: eager story prep at configure() time#96

Open
EmilioBejasa wants to merge 3 commits intomainfrom
experiment/eager-story-prep
Open

experiment: eager story prep at configure() time#96
EmilioBejasa wants to merge 3 commits intomainfrom
experiment/eager-story-prep

Conversation

@EmilioBejasa
Copy link
Copy Markdown
Collaborator

What

Third experimental approach for the JS-driven story loop.

Key insight: configure(view) is called synchronously during app startup, when _preview.storyStoreValue is already set. importFn is async path => importMap[path] — the map is eagerly loaded, so each loadStory() call resolves in one microtask. We can fan-out all loadStory() calls in configure() and store the resulting Promise.

By the time StoryRenderer mounts (much later, after the full app has initialised), eagerPrepPromise is already resolved. The await in the init effect is effectively a no-op.

Approach

PR #94 (sync blocking) PR #95 (js-driven) This PR
Prep method createPreparedStoryMapping() per story buildPreparedStories() in init effect loadStory() fan-out in configure()
Render effect async (awaits prep) sync (prep done in init) sync (prep done before mount)
Native sync blocking method (broken in new arch) Promise handshake Promise handshake (same as #95)

The native side (StorybookRegistry.kt, BaseStoryScreenshotTest.kt) is the same Promise handshake protocol as #95:

  1. Test calls prepareForNextStory() → arms latch
  2. JS calls notifyStoryReady(id, promise) → stores promise, counts down latch
  3. Test screenshots, calls resolveCurrentStory() → resolves JS Promise
  4. JS advances; when done calls allStoriesDone() → test loop exits

Test plan

🤖 Generated with Claude Code

EmilioBejasa and others added 3 commits March 27, 2026 16:08
Kicks off loadStory() for all stories immediately in configure(), using
_preview.storyStoreValue directly (bypasses _preview.ready() which only
resolves when the Storybook UI renders — never in a test run).

By the time StoryRenderer mounts, _idToPrepared is already fully
populated, so the render effect is synchronous (no await in the hot
path). Combined with the JS-driven Promise handshake from the previous
experiment, this removes all async work between "set story" and
"notify native ready".

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>
view.draw(canvas) skips Fabric child views that have hardware display
lists, leaving only the root container's white background. Walking the
entire view tree and setting LAYER_TYPE_SOFTWARE on every node ensures
all content is visible to the software canvas used by Screenshot.snap().

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