Skip to content

feat:unified withStorybook wrapper for entrypoint-swapping#871

Open
Copilot wants to merge 37 commits intonextfrom
copilot/add-entry-point-swapping-storybook
Open

feat:unified withStorybook wrapper for entrypoint-swapping#871
Copilot wants to merge 37 commits intonextfrom
copilot/add-entry-point-swapping-storybook

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 1, 2026

Tracked by storybookjs/storybook#34276 ("Easy React Native Setup")
Companion PR: storybookjs/storybook#34333 (CLI init overhaul, codemods, automigrations)
Closes #873 (deviceAddons)
Documentation PR: #877

What this does

This PR adds a bundler-agnostic withStorybook wrapper at @storybook/react-native/withStorybook that introduces entry-point swapping as the new recommended way to run Storybook for React Native — matching the mental model of Storybook for Web, where Storybook runs as its own entry point rather than being deeply integrated into the user's app.

It also introduces the deviceAddons config property, separating on-device addons from the addons array to prevent CriticalPresetLoadError during server-side operations like extract.

Entry-point swapping

The new withStorybook wrapper:

  • Auto-detects whether the project uses Metro or Re.Pack and delegates accordingly
  • Swaps the app entry point for .rnstorybook/index when STORYBOOK_ENABLED=true is set — no changes to App.tsx required
  • Is a strict no-op when STORYBOOK_ENABLED is not set — zero Storybook code in the production bundle
  • Reads configuration from environment variables (STORYBOOK_ENABLED, STORYBOOK_WS_HOST, STORYBOOK_WS_PORT, STORYBOOK_WS_SECURED, STORYBOOK_SERVER, STORYBOOK_DISABLE_UI), so the same metro config works for all environments
  • Auto-injects WebSocket host/port into the generated storybook.requires file, eliminating the need to manually match config between withStorybook and getStorybookUI

The existing ./metro/withStorybook and ./repack/withStorybook exports are completely untouched and remain fully supported for projects using the in-app integration approach.

deviceAddons

On-device addons (like @storybook/addon-ondevice-controls) contain React Native code that can't be evaluated in Node.js. When listed in addons, Storybook Core tries to load them as presets during operations like extract, which fails. The new deviceAddons property ensures they're only loaded at runtime on the device.

const main: StorybookConfig = {
  stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
  deviceAddons: [
    '@storybook/addon-ondevice-controls',
    '@storybook/addon-ondevice-actions',
  ],
};

Backwards compatible — addons still works and a console warning guides users to migrate. The companion PR (storybookjs/storybook#34333) includes an automigration (rn-ondevice-addons-to-device-addons) that handles this automatically via npx storybook automigrate.

How it works together

This PR (react-native repo) provides the runtime bundler wrapper. The companion PR (storybook repo) provides the CLI tooling:

Concern This PR storybookjs/storybook#34333
Bundler-agnostic withStorybook ✅ Implementation
Entry-point swapping ✅ Metro resolver + Re.Pack plugin
Env var support ✅ Runtime reading
deviceAddons type + runtime StorybookConfig type, generate.js
storybook init for RN ✅ Codemod, entrypoint generation, scripts
deviceAddons automigration rn-ondevice-addons-to-device-addons
Metro config codemod ✅ AST-based withStorybook() wrapping

Manual verification

Existing withStorybook (backwards compatibility)

Upgraded two existing projects to the canary release of this branch to verify the legacy ./metro/withStorybook path is unaffected:

  • React Native CLI project — upgraded to canary, confirmed existing setup works unchanged
  • Expo project — upgraded to canary, confirmed existing setup works unchanged

Both projects continued to function correctly with their existing metro config and in-app Storybook integration, confirming zero regressions.

storybook init CLI

Tested npx storybook@0.0.0-pr-34333-sha-865b50bc init in a fresh Expo project:

  • .rnstorybook/index.ts generated correctly
  • metro.config.js wrapped with withStorybook() using the bundler-agnostic import
  • storybook:ios and storybook:android scripts added to package.json
  • ⚠️ The CLI currently pulls main.ts (and other templates) from the latest published version of @storybook/react-native, not from the canary. This means the generated main.ts won't have deviceAddons until this PR is released. This will resolve itself once we publish the minor release.

npx storybook automigrate

Verified that npx storybook@0.0.0-pr-34333-sha-865b50bc automigrate correctly moves on-device addons from addonsdeviceAddons in an existing project's .rnstorybook/main.ts.

Release plan

Targeting release as a new minor version this week (v10.4), alongside:

Test coverage

  • 39 tests in @storybook/react-native (unit tests for withStorybook, enhanceMetroConfig, enhanceRepackConfig, entry-point resolution, env var handling)
  • 14 tests in the tests package (story generation snapshots)
  • All passing ✅

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 1, 2026

🦋 Changeset detected

Latest commit: 5c4dc2b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@storybook/react-native Minor
@storybook/react-native-ui Minor
@storybook/react-native-ui-lite Minor
@storybook/react-native-ui-common Minor
@storybook/react-native-theming Minor
@storybook/addon-ondevice-actions Minor
@storybook/addon-ondevice-backgrounds Minor
@storybook/addon-ondevice-controls Minor
@storybook/addon-ondevice-notes Minor

Not sure what this means? Click here to learn what changesets are.

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

@ndelangen
Copy link
Copy Markdown
Member

@copilot, can you try again? It looks like you had some internal error.

Copilot AI and others added 2 commits April 1, 2026 10:39
…overrides to withStorybook

M4 implementation:
- Add resolveEntryPoint() to detect app entry (Expo, Expo Router, RN CLI)
- Add entry-point swapping in resolver when STORYBOOK_ENABLED=true
- Add websocket env variable overrides (STORYBOOK_WS_HOST/PORT/SECURED)
- Add comprehensive tests for all new functionality

Agent-Logs-Url: https://github.com/storybookjs/react-native/sessions/58a5fb17-1ee3-4e0b-ae60-aee474de2d32

Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented Apr 1, 2026

@copilot, can you try again? It looks like you had some internal error.

Implemented M4 in commits a889fb4 and 537de98. The changes add:

  1. resolveEntryPoint() - detects app entry point for Expo (package.json#main), Expo Router (expo-router/entry), and RN CLI (index.js fallback)
  2. Entry-point swapping - when STORYBOOK_ENABLED=true, the Metro resolver redirects the app entry to .rnstorybook/index. Strict no-op when not set.
  3. Websocket env overrides - STORYBOOK_WS_HOST, STORYBOOK_WS_PORT, STORYBOOK_WS_SECURED environment variables
  4. 15 new tests covering all new functionality, all 33 tests passing

Copilot AI changed the title [WIP] Add entry point swapping for React Native Storybook feat: Add entry point swapping for React Native Storybook (M4) Apr 1, 2026
@ndelangen
Copy link
Copy Markdown
Member

@copilot Did you catch that we'd need backwards support?

I think it'd be best if we created 2 separate withStorybook config wrappers, so that 1 does entrypoint-swapping, and the other acts as the backwards support way.

Copilot AI and others added 2 commits April 1, 2026 13:04
- Restore withStorybook to original behavior (no entry-point swapping, no ws env overrides)
- Create withStorybookSwap with entry-point swapping + ws env overrides
- Export shared utilities (resolveEntryPoint, resolveFileWithExtensions, resolveStorybookEntry, applyWebsocketEnvOverrides) from withStorybook.ts
- Add metro/withStorybookSwap.js re-export and package.json export path
- Add withStorybookSwap to tsup build entries
- Tests: 33 passing across 5 suites

Agent-Logs-Url: https://github.com/storybookjs/react-native/sessions/a3213ee7-9a44-4b9a-a4b2-f434af51c3ce

Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>
Copilot AI changed the title feat: Add entry point swapping for React Native Storybook (M4) feat: split withStorybook into backwards-compat and entry-swap wrappers Apr 1, 2026
@ndelangen
Copy link
Copy Markdown
Member

@copilot I'm aiming for the new config wrapper to be a single agnostic one, exposed as @storybook/react-native/withStorybook.

This exports a single named export (withStorybook) which should support both metro and repack.
This way SB init command doesn't need to know about this complaexity, and can always generate the same thing.

The types of this function should be like so:
function<T>: T

Though internally we should try to be somewhat type-safe, but externally it's not exposed.

Copilot AI and others added 2 commits April 2, 2026 08:07
…/withStorybook

Creates a bundler-agnostic withStorybook<T>(config: T, options?): T that
auto-detects Metro vs Repack and delegates accordingly:
- Metro: entry-point swapping + ws env overrides (via withStorybookSwap)
- Repack: adds StorybookPlugin with ws env overrides to plugins array

This allows sb init to always generate the same config regardless of bundler.

Agent-Logs-Url: https://github.com/storybookjs/react-native/sessions/e08bd14d-760c-471e-938c-e26d89e9ae9b

Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented Apr 2, 2026

@copilot I'm aiming for the new config wrapper to be a single agnostic one, exposed as @storybook/react-native/withStorybook.

This exports a single named export (withStorybook) which should sup...

Added a unified withStorybook<T>(config: T, options?): T at @storybook/react-native/withStorybook in commit 7e754f5. It auto-detects Metro vs Repack from the config object (checks for transformer property) and delegates accordingly:

  • MetrowithStorybookSwap (entry-point swapping + ws env overrides)
  • Repack/Rspack/Webpack → adds StorybookPlugin to config.plugins with ws env overrides

Usage is identical regardless of bundler:

const { withStorybook } = require('@storybook/react-native/withStorybook');
module.exports = withStorybook(config);

The existing ./metro/withStorybook and ./repack/withStorybook exports remain for direct access.

@ndelangen
Copy link
Copy Markdown
Member

@copilot the previously existing ./metro/withStorybook and ./repack/withStorybook ARE the backwards support, and should remain completely untouched.

The @storybook/react-native/withStorybook should be the new entrypoint swapping config wrapper, can you check if that's what you did?

Copilot AI changed the title feat: split withStorybook into backwards-compat and entry-swap wrappers feat: add unified bundler-agnostic withStorybook wrapper Apr 2, 2026
Copilot AI and others added 4 commits April 9, 2026 12:58
…ore addons (#874)

* feat: add deviceAddons property for on-device addons separation

- Add `deviceAddons` to `StorybookConfig` type
- Update `generate.js` to merge addons from both `addons` and `deviceAddons`
- Update init template to use `deviceAddons` for on-device addons
- Update expo-example to use `deviceAddons`
- Add tests for deviceAddons and mixed addons scenarios
- Backwards compatible: addons in `addons` field still work

Agent-Logs-Url: https://github.com/storybookjs/react-native/sessions/82703242-efa9-4fae-8cca-c667593bb37f

Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>

* chore: add changeset for deviceAddons feature

Agent-Logs-Url: https://github.com/storybookjs/react-native/sessions/b99f73e3-2330-42e2-8149-ab13e5f3892c

Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>

* fix: update deviceAddons test snapshots after base branch merge

Agent-Logs-Url: https://github.com/storybookjs/react-native/sessions/0102d455-cd0f-430c-bab9-f3921b3ad9b8

Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ndelangen <3070389+ndelangen@users.noreply.github.com>
Co-authored-by: Norbert de Langen <ndelangen@me.com>
…thStorybook

- Enhanced logging in the View component to provide clearer output when a persisted story is not found.
- Updated the handling of the liteMode option in withStorybook to use a new environment variable for better configuration management.

All tests pass successfully.
…om:storybookjs/react-native into copilot/add-entry-point-swapping-storybook
@ndelangen
Copy link
Copy Markdown
Member

@dannyhw I've verified this work for existing setups and is backwards compatible

@dannyhw
Copy link
Copy Markdown
Member

dannyhw commented Apr 11, 2026

Will take a look soon, thanks

… Storybook

- Introduced a minimal WebSocket server for manual connectivity checks, configurable via environment variables `STORYBOOK_WS_HOST`, `STORYBOOK_WS_PORT`, and `STORYBOOK_WS_SECURED`.
- Updated `withStorybook` and `withStorybook.test.ts` to utilize these environment variables for improved configuration management.
- Enhanced the handling of WebSocket options to allow for dynamic binding based on environment settings.

All tests pass successfully.
@ndelangen ndelangen marked this pull request as ready for review April 13, 2026 13:36
@ndelangen ndelangen requested a review from dannyhw as a code owner April 13, 2026 13:36
Copilot AI review requested due to automatic review settings April 13, 2026 13:36
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new unified, bundler-agnostic withStorybook wrapper to @storybook/react-native, introduces a deviceAddons config field, and updates the generator/tests to support the new addon split and lite-mode plumbing.

Changes:

  • Add @storybook/react-native/withStorybook and internal config enhancers for Metro/Repack entry swapping.
  • Add deviceAddons to StorybookConfig and update generator + fixtures/tests to import addons from both addons and deviceAddons.
  • Extend runtime/UI to support lite-mode behavior (options propagation, UI toggling) and add a manual WS smoke test script.

Reviewed changes

Copilot reviewed 33 out of 34 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tsconfig.json Removes baseUrl setting.
tests/scripts/mocks/mixed-addons/preview.js Adds mock preview with decorators/parameters for mixed addons.
tests/scripts/mocks/mixed-addons/main.js Adds mock main config with addons + deviceAddons.
tests/scripts/mocks/mixed-addons/FakeStory.stories.tsx Adds mock story file for generator tests.
tests/scripts/mocks/mixed-addons/FakeComponent.tsx Adds mock component for generator tests.
tests/scripts/mocks/device-addons/preview.js Adds mock preview for deviceAddons-only scenario.
tests/scripts/mocks/device-addons/main.js Adds mock main config using deviceAddons.
tests/scripts/mocks/device-addons/FakeStory.stories.tsx Adds mock story file for generator tests.
tests/scripts/mocks/device-addons/FakeComponent.tsx Adds mock component for generator tests.
tests/scripts/generate.test.ts.snapshot Updates snapshots for new generated output (options + addon import behavior).
tests/scripts/generate.test.ts Adds tests covering deviceAddons and mixed addon lists.
packages/react-native/tsup.config.ts Adds src/withStorybook.ts as a build entry.
packages/react-native/template/cli/main.ts Updates template to use deviceAddons instead of addons.
packages/react-native/src/withStorybook.ts Introduces new unified wrapper with shared setup + bundler detection.
packages/react-native/src/withStorybook.test.ts Adds unit tests for unified wrapper behavior/env overrides.
packages/react-native/src/View.tsx Threads options into View and changes lite-mode/UI behavior and story-only rendering wrapper.
packages/react-native/src/Start.tsx Passes options into View constructor.
packages/react-native/src/repack/withStorybook.ts Updates Repack plugin to apply STORYBOOK_WS_* env overrides and revised server/generate behavior.
packages/react-native/src/prepareStories.ts Adds liteMode to ReactNativeOptions.
packages/react-native/src/metro/withStorybook.ts Updates Metro wrapper to apply STORYBOOK_WS_* env overrides and revised server/generate behavior.
packages/react-native/src/metro/withStorybook.test.ts Adds test for env-based websockets when option omitted.
packages/react-native/src/metro/utils.ts Adds shared utilities (entry/storybook entry resolution) and shared options type.
packages/react-native/src/metro/utils.test.ts Adds tests for entry-point resolution logic.
packages/react-native/src/index.ts Makes addons optional and introduces deviceAddons in StorybookConfig.
packages/react-native/src/enhanceRepackConfig.ts Adds internal helper to swap Repack entrypoint.
packages/react-native/src/enhanceRepackConfig.test.ts Adds tests for Repack entry swapping behavior.
packages/react-native/src/enhanceMetroConfig.ts Adds internal helper to adjust Metro config and swap entrypoint via resolver.
packages/react-native/src/enhanceMetroConfig.test.ts Adds tests for Metro enhancements and entry swapping.
packages/react-native/scripts/ws-smoke-server.mjs Adds a manual WS connectivity smoke server script.
packages/react-native/scripts/generate.js Extends generator to merge addons + deviceAddons and propagate liteMode into options.
packages/react-native/package.json Exports new ./withStorybook entry and adds ws-smoke-server script.
examples/expo-example/.rnstorybook/main.ts Splits addons into addons + deviceAddons in example config.
.changeset/unified-withstorybook-wrapper.md Changeset for the new unified wrapper export.
.changeset/add-device-addons.md Changeset for introducing deviceAddons config field.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

configPath,
useJs,
docTools,
...(!!host ? { host: host, port: websockets.port, secured: !websockets.secured } : {}),
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

withStorybook passes secured: !websockets.secured into generate(), which inverts the intended meaning and also turns secured on when websockets.secured is undefined (because !undefined === true). This will generate a wss:// URL when it should be ws:// (and vice-versa). Pass the actual secured value (defaulting to false) consistently with createChannelServer and the legacy wrappers.

Suggested change
...(!!host ? { host: host, port: websockets.port, secured: !websockets.secured } : {}),
...(!!host
? { host: host, port: websockets.port, secured: websockets.secured ?? false }
: {}),

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a valid point, just on face level

Comment on lines +110 to +117
if (server || experimental_mcp) {
createChannelServer({
port: websockets.port,
host: websockets.host,
configPath,
experimental_mcp,
websockets: Boolean(websockets.host),
secured: websockets.secured,
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebSocket enablement is derived from Boolean(websockets.host), which disables websockets for valid configurations like websockets: 'auto' (host intentionally undefined) or websockets: { port: 7007 }. This makes the unified wrapper behave differently from src/metro/withStorybook.ts / src/repack/withStorybook.ts, where 'auto' still enables the WS server. Consider computing a websocketsEnabled flag from the presence of the websockets option or STORYBOOK_WS_HOST, and bind host separately.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +83
export function withStorybook<T>(config: T, options: WithStorybookOptions = {}): T {
const enabled = envVariableToBoolean(process.env.STORYBOOK_ENABLED, false);
if (!enabled) {
return config;
}
const server = envVariableToBoolean(process.env.STORYBOOK_SERVER, true);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unified withStorybook accepts WithStorybookOptions (which includes enabled?: boolean), but the implementation ignores options.enabled and only checks process.env.STORYBOOK_ENABLED. This is an API footgun for consumers expecting parity with the existing Metro wrapper options. Either support options.enabled here, or define a dedicated options type for the unified wrapper that omits enabled.

Copilot uses AI. Check for mistakes.
Comment on lines +124 to 132
_options: any;
_idToPrepared: Record<string, PreparedStory<ReactRenderer>> = {};

constructor(preview: PreviewWithSelection<ReactRenderer>, channel: Channel) {
constructor(preview: PreviewWithSelection<ReactRenderer>, channel: Channel, options: any) {
this._preview = preview;
this._channel = channel;
this._options = options;
}

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start() passes options?: ReactNativeOptions through to new View(...), but options is optional and may be undefined. View.getStorybookUI() then unconditionally reads this._options.liteMode, which will throw if _options is undefined. Default options to {} either in start() or in the View constructor (and consider updating updateView() to refresh _options as well).

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +256
const storage = params.storage ?? {
getItem: async (key) => null,
setItem: async (key, value) => {},
};

const onDeviceUI = this._options.liteMode ? false : (params.onDeviceUI ?? true);
const shouldPersistSelection = this._options.liteMode
? false
: (params.shouldPersistSelection ?? true);

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

storage is now defaulted to a no-op implementation, which makes the later warning if (shouldPersistSelection && !storage) unreachable and can silently disable persistence (writes go to the no-op storage). If persistence requires a real storage implementation, keep storage undefined by default and warn, or change the warning condition to check params.storage (the user-provided value) while still allowing a safe fallback for non-persisting modes.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +53
const resolveResult = resolveFunction(theContext, moduleName, platform);

if (resolveResult?.filePath?.includes?.('@storybook/react/template/cli')) {
return { type: 'empty' };
}

if (moduleName === 'tty' || moduleName === 'os') {
return { type: 'empty' };
}

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveRequest calls resolveFunction(...) before handling the known Node core-module shims (tty/os). If the resolver throws on these (the workaround exists elsewhere in the codebase), this handler won't get a chance to return { type: 'empty' }. Consider short-circuiting tty/os before calling resolveFunction (as done in src/metro/withStorybook.ts).

Suggested change
const resolveResult = resolveFunction(theContext, moduleName, platform);
if (resolveResult?.filePath?.includes?.('@storybook/react/template/cli')) {
return { type: 'empty' };
}
if (moduleName === 'tty' || moduleName === 'os') {
return { type: 'empty' };
}
if (moduleName === 'tty' || moduleName === 'os') {
return { type: 'empty' };
}
const resolveResult = resolveFunction(theContext, moduleName, platform);
if (resolveResult?.filePath?.includes?.('@storybook/react/template/cli')) {
return { type: 'empty' };
}

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +31
import { createServer } from 'node:http';
import { WebSocketServer } from 'ws';

const PING_INTERVAL_MS = 10_000;

const port = Number(process.env.STORYBOOK_WS_PORT) || 7007;
const host = process.env.STORYBOOK_WS_HOST || undefined;

const httpServer = createServer((_req, res) => {
res.writeHead(404);
res.end();
});

const wss = new WebSocketServer({ server: httpServer });

// Same global ping interval as createChannelServer — keeps Storybook client transport alive.
const pingInterval = setInterval(() => {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ type: 'ping', args: [] }));
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ws-smoke-server.mjs references WebSocket.OPEN but only imports WebSocketServer from ws. This will throw a ReferenceError at runtime. Import WebSocket (or compare against the numeric readyState value / client.OPEN) before using it.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +16
function envVariableToBoolean(value: string | undefined, defaultValue: any = false): boolean {
switch (value) {
case 'true':
return true;
case 'false':
return false;
default:
return !!defaultValue;
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description states legacy ./metro/withStorybook and ./repack/withStorybook wrappers are "completely untouched", but this PR modifies both (e.g. adds env override handling and changes server/generate behavior). Please update the PR description to match the actual changes, or move these updates behind the new unified wrapper if the intent is to keep the legacy entrypoints unchanged.

Copilot uses AI. Check for mistakes.
- Reorganized scripts in package.json files to improve consistency and clarity, including the addition of `check:types` and reordering of existing scripts.
- Updated dependencies and added missing fields such as `license` and `files` in several package.json files.
- Enhanced the handling of `deviceAddons` in the configuration files to ensure proper migration warnings for legacy usage.
- Added new test cases to validate the migration of on-device addons and ensure backward compatibility.

All tests pass successfully.
@ndelangen ndelangen changed the title feat: add unified bundler-agnostic withStorybook wrapper feat:unified withStorybook wrapper for entrypoint-swapping Apr 14, 2026
- Updated the `StorybookConfig` interface to mark the `addons` field as deprecated, advising users to migrate to `deviceAddons`.
- Enhanced the `generate.js` script to log a deprecation warning when `addons` is used, guiding users to move their entries to `deviceAddons`.
- Adjusted tests to verify the deprecation warning functionality for non-empty `addons`.

All tests pass successfully.
…mment)

- Updated the condition to disable experimental MCP settings when the server is not present, ensuring proper configuration behavior.
- This change enhances the reliability of the withStorybook function in various environments.

All tests pass successfully.
- Adjusted the formatting of the `names` variable declaration for improved clarity.
- This change enhances the overall readability of the script without altering its functionality.

All tests pass successfully.
docTools,
...(!!host ? { host: host, port: websockets.port, secured: !websockets.secured } : {}),
liteMode,
} as any);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be cool if we could avoid this type cast

@@ -0,0 +1,56 @@
/**
* Minimal HTTP + WebSocket server for manual connectivity checks (LAN, firewall).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what this smoke server is about

"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"start": "docusaurus start",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what changed here? just the ordering?

* `storybook.requires`. Not evaluated as presets by Storybook Core, which
* avoids failures during server-side operations like `extract`.
*/
deviceAddons?: Addon[];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be curious to see if we added deviceAddons to the config type of react-native-web-vite if we could then work towards a single config folder

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also I guess it seems slightly odd if deviceAddons is needed in a react-native storybook specific config file. Like maybe the chromatic tooling could even handle the known edgecases of react native since they are small?

in v11 anyway i want to bring most of the ondevice addons into the core of rn storybook so maybe it gets less important then 🤔

* Note that this is for future and play functions are not yet fully supported on native.
*/
playFn?: boolean;
liteMode?: boolean;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was kinda thinking of moving this type of options to the features option and move to remove this ReactNativeOptions later, but open to thoughts on that

import { patchChannelForRN } from './patchChannelForRN';
import deepmerge from 'deepmerge';
import { useEffect, useMemo, useReducer, useState } from 'react';
import { StatusBar } from 'react-native';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theres already a react-native import below which is causing this lint warning

const exists = value && this._storyIdExists(value);

if (!exists) console.log('Storybook: could not find persisted story');
if (!exists) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I don't think we need these formatting changes

self._channel.emit(SET_CURRENT_STORY, { storyId: newStoryId })
}
storage={storage}
storage={this._storage}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a linting warning here, I'd like to keep linting fully clean if possible

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arguably just the storage variable is fine here as it was

} else {
return (
<StoryView useWrapper={storyViewWrapper} storyBackgroundColor={storyBackgroundColor} />
<SafeAreaProvider>
Copy link
Copy Markdown
Member

@dannyhw dannyhw Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I understand this part of the change, the ondeviceui is off in this scenario onDeviceUI==false so should we be adding any ui here?

is this intended specifically for the visual testing scenario?

sometimes the user would want no safearea here because they have a screen with a background color that fills the full area even on the safe area

we have a no safearea parameter maybe it could be a compromise to support that here

@dannyhw
Copy link
Copy Markdown
Member

dannyhw commented Apr 14, 2026

just left some initial thoughts so far, still need to test it out myself

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.

Split addons from "ondevice" addons in main.ts

4 participants