Skip to content

feat(react): support activity component/data preload outside React runtime context#716

Draft
ENvironmentSet wants to merge 6 commits into
mainfrom
feature/fep-2357
Draft

feat(react): support activity component/data preload outside React runtime context#716
ENvironmentSet wants to merge 6 commits into
mainfrom
feature/fep-2357

Conversation

@ENvironmentSet
Copy link
Copy Markdown
Collaborator

@ENvironmentSet ENvironmentSet commented Jun 4, 2026

Problem

Preloading an activity's resources before entering it — the lazy component chunk and the data described by the activity's loader — is currently only possible through the usePrepare hook.

Because usePrepare is a React hook, it depends on React Context (useConfig, useDataLoader, useActivityComponentMap) and can therefore only be called inside a mounted React tree. This makes it impossible to warm up chunks/data at the moments that matter most for initial-entry performance:

  • app bootstrap, before the first render

In practice, consumers who need render-ahead preloading have had to reach for private APIs (e.g. the lazy component's internal _load), which is fragile and unsupported.

Solution

Expose the same preload capability as a plain function on the stackflow() output, alongside the existing render-outside APIs (actions, stepActions):

const { Stack, actions, stepActions, prepare } = stackflow({
  config,
  components,
  plugins,
});

// outside React — bootstrap
prepare("Article", { articleId: "123" }); // warm chunk + fire data loader
prepare("Article");                       // warm chunk only

Design decisions:

  • Same instance, single source. prepare comes from the same stackflow() call as Stack/actions, so config and components never have to be passed twice and can never drift from the running instance. Since it does not touch the core store, it is usable from module-evaluation time — before <Stack> is mounted.
  • Unchanged signature. The existing Prepare type is reused as-is: omitting activityParams preloads only the component chunk; passing params also fires the activity's data loader. The returned Promise<void> resolves when all fired preload work settles.
  • Cache warming, not data injection. prepare does not store loader results; injecting loaderData into activities remains the loader plugin's responsibility.
  • Failure semantics. Failures (unregistered activity, loader/chunk errors) are delivered as a rejection of the returned promise with the original reason — never a synchronous throw. A failed chunk load is retried on a subsequent prepare, so transient failures don't poison the cache. Fire-and-forget callers should attach .catch.
  • usePrepare becomes a thin wrapper over the same implementation, so existing in-tree callers keep working unchanged.
  • Type safety preserved. RegisteredActivityName / InferActivityParams<K> flow through unchanged, so invalid activity names or params remain compile-time errors.

🤖 Generated with Claude Code

ENvironmentSet and others added 6 commits June 4, 2026 15:26
- Add Jest + @swc/jest + jsdom + Testing Library to integrations/react,
  following the inline-config convention of plugin-blocker/plugin-history-sync
- Exclude *.spec.* files from esbuild/dts build output; add tsconfig.test.json
  so `yarn typecheck` covers spec files (incl. Register augmentations)
- Type-only cast in PluginRenderer so library source stays type-checkable
  when specs augment the Register interface
- Add a harness smoke spec using an inline renderer plugin (public render API)
  to avoid a workspace dependency cycle with plugin-renderer-basic

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FEP-2357-SPEC.md: Linear issue + locked interface design, plus spec-owner
  decisions (reject semantics, original-reason propagation, retry after
  failure; loader dedupe / chunk duplicate firing / atomicity left unspecified)
- FEP-2357-TEST-PLAN.md: 32 given-when-then test items (A-G), approved by
  test reviewer after two review rounds

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Translates FEP-2357-TEST-PLAN.md items A2-A9, B1-B3, C1-C2, E1-E10 and
F1-F2 into Jest specs against the public entry (./index). `prepare` is
not implemented yet, so all 25 tests fail with "prepare is not a
function" — verified red for the right reason by temporarily wiring a
reference implementation (all green) and reverting it.

Register augmentation uses optional params only ({ id?: string });
registering required params breaks package-internal typecheck variance
in stackflow.tsx/useStepFlow.ts. Because Register merges globally,
every stackflow() call passes a complete components map via
baseComponents spread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D1-D2 verify the current usePrepare behavior that the new prepare must
match (spec §3 "thin wrapper"). These run against existing code and
pass today.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
G1-G4 are typecheck-only assertions placed in never-called function
bodies (swc does not typecheck; runtime execution must be avoided).
A1 lives here because Jest requires at least one test per spec file.

Until prepare lands, `yarn workspace @stackflow/react typecheck` fails
with TS2339 (Property 'prepare' does not exist on StackflowOutput) in
both this file and prepare.spec.tsx — a single root cause. Verified
that a typed reference implementation turns typecheck fully green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The smoke spec invited removal once real specs cover the same ground;
prepare.spec.tsx/usePrepare.spec.tsx now exercise the same harness
surface (spec pickup, @swc/jest, jsdom + Testing Library, workspace
deps, inline renderer plugin).

Keeping it would also break typecheck: Register augmentation merges
package-wide, so its stackflow() call would need components for every
Prepare* activity registered by the new specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 4, 2026

⚠️ No Changeset found

Latest commit: 51ad11d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ab159cd8-013f-434c-8236-158386ff7282

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fep-2357

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 4, 2026

@stackflow/link

yarn add https://pkg.pr.new/@stackflow/link@716.tgz

@stackflow/plugin-basic-ui

yarn add https://pkg.pr.new/@stackflow/plugin-basic-ui@716.tgz

@stackflow/plugin-blocker

yarn add https://pkg.pr.new/@stackflow/plugin-blocker@716.tgz

@stackflow/plugin-google-analytics-4

yarn add https://pkg.pr.new/@stackflow/plugin-google-analytics-4@716.tgz

@stackflow/plugin-history-sync

yarn add https://pkg.pr.new/@stackflow/plugin-history-sync@716.tgz

@stackflow/plugin-lifecycle

yarn add https://pkg.pr.new/@stackflow/plugin-lifecycle@716.tgz

@stackflow/plugin-renderer-basic

yarn add https://pkg.pr.new/@stackflow/plugin-renderer-basic@716.tgz

@stackflow/plugin-renderer-web

yarn add https://pkg.pr.new/@stackflow/plugin-renderer-web@716.tgz

@stackflow/react-ui-core

yarn add https://pkg.pr.new/@stackflow/react-ui-core@716.tgz

@stackflow/react

yarn add https://pkg.pr.new/@stackflow/react@716.tgz

commit: 51ad11d

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: 51ad11d
Status: ✅  Deploy successful!
Preview URL: https://7e048082.stackflow-demo.pages.dev
Branch Preview URL: https://feature-fep-2357.stackflow-demo.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
stackflow-docs 51ad11d Commit Preview URL Jun 04 2026, 11:26 AM

@ENvironmentSet ENvironmentSet changed the title feat(react): support activity component/data preload outside React context (FEP-2357) feat(react): support activity component/data preload outside React context Jun 4, 2026
@ENvironmentSet ENvironmentSet changed the title feat(react): support activity component/data preload outside React context feat(react): support activity component/data preload outside React runtime context Jun 4, 2026
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