From 638930dd0f680658cc12466a4b32fa7ac626029c Mon Sep 17 00:00:00 2001 From: yannbf Date: Thu, 16 Jul 2026 10:19:04 +0200 Subject: [PATCH 1/3] CLI: Update AI setup instructions to msw-storybook-addon v3 The setup prompts taught the v2 API, which crashes at module load under v3: `initialize` no longer exists and `mswLoader` moved to the `msw-storybook-addon/csf3` subpath. - register the addon via `storybook add msw-storybook-addon@3` so it lands in the main config (and the install command is pinned to the major the snippets teach, so they cannot drift apart) - CSF3 previews: `loaders: [mswLoader()]` from `/csf3`, handlers wired in `beforeEach({ msw })` instead of the deprecated `parameters.msw` - CSF factories previews (`hasCsfFactoryPreview`): `addonMsw()` in `definePreview({ addons })` - handlers file example is a flat array now that it is spread into `msw.use(...)` Co-Authored-By: Claude Fable 5 --- ...-tests-relaxed-limits-no-story-deletion.ts | 2 +- .../core/src/cli/ai/setup-prompts/monorepo.ts | 2 +- .../cli/ai/setup-prompts/optimized-tests.ts | 2 +- .../cli/ai/setup-prompts/partials/examples.ts | 52 ++++++++++---- .../cli/ai/setup-prompts/partials/steps.ts | 20 +++--- .../cli/ai/setup-prompts/pattern-copy-play.ts | 67 ++++++++----------- .../cli/ai/setup-prompts/relaxed-limits.ts | 2 +- 7 files changed, 84 insertions(+), 63 deletions(-) diff --git a/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts b/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts index 9b2701b49856..b3c503977a7e 100644 --- a/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts +++ b/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts @@ -41,7 +41,7 @@ export function instructions(projectInfo: ProjectInfo): string { const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); const mswInstall = packageManager.getInstallCommand( - ['msw', 'msw-storybook-addon', 'mockdate'], + ['msw', 'mockdate'], true ); diff --git a/code/core/src/cli/ai/setup-prompts/monorepo.ts b/code/core/src/cli/ai/setup-prompts/monorepo.ts index 65ebb006e35c..19308a774075 100644 --- a/code/core/src/cli/ai/setup-prompts/monorepo.ts +++ b/code/core/src/cli/ai/setup-prompts/monorepo.ts @@ -41,7 +41,7 @@ export function instructions(projectInfo: ProjectInfo): string { const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); const mswInstall = packageManager.getInstallCommand( - ['msw', 'msw-storybook-addon', 'mockdate'], + ['msw', 'mockdate'], true ); diff --git a/code/core/src/cli/ai/setup-prompts/optimized-tests.ts b/code/core/src/cli/ai/setup-prompts/optimized-tests.ts index c984a39e6ccf..97db3e21515b 100644 --- a/code/core/src/cli/ai/setup-prompts/optimized-tests.ts +++ b/code/core/src/cli/ai/setup-prompts/optimized-tests.ts @@ -39,7 +39,7 @@ export function instructions(projectInfo: ProjectInfo): string { const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); const mswInstall = packageManager.getInstallCommand( - ['msw', 'msw-storybook-addon', 'mockdate'], + ['msw', 'mockdate'], true ); diff --git a/code/core/src/cli/ai/setup-prompts/partials/examples.ts b/code/core/src/cli/ai/setup-prompts/partials/examples.ts index 0665bad3e843..2dd301043b85 100644 --- a/code/core/src/cli/ai/setup-prompts/partials/examples.ts +++ b/code/core/src/cli/ai/setup-prompts/partials/examples.ts @@ -3,21 +3,49 @@ import type { ProjectInfo } from '../../types.ts'; import { ext } from '../../utils/ext.ts'; export function getPreviewExample(projectInfo: ProjectInfo): string { - const { configDir, language, framework, rendererPackage } = projectInfo; + const { configDir, language, framework, rendererPackage, hasCsfFactoryPreview } = projectInfo; const tsx = ext(language, true); const typeImport = framework || rendererPackage || '@storybook/react-vite'; - if (language === 'js') { + if (hasCsfFactoryPreview) { return dedent` \`\`\`${tsx} // ${configDir}/preview.${tsx} + import { definePreview } from '${typeImport}'; import '../src/index.css'; import MockDate from 'mockdate'; - import { initialize, mswLoader } from 'msw-storybook-addon'; + import addonMsw from 'msw-storybook-addon'; import { SessionProvider } from '../src/contexts/SessionContext'; import { mswHandlers } from './msw-handlers'; - initialize({ onUnhandledRequest: 'bypass' }); + export default definePreview({ + addons: [addonMsw()], + decorators: [ + (Story) => ( + + + + ), + ], + async beforeEach({ msw }) { + msw.use(...mswHandlers); + localStorage.setItem('theme', 'dark'); + MockDate.set('2024-04-01T12:00:00Z'); + }, + }); + \`\`\` + `; + } + + if (language === 'js') { + return dedent` + \`\`\`${tsx} + // ${configDir}/preview.${tsx} + import '../src/index.css'; + import MockDate from 'mockdate'; + import { mswLoader } from 'msw-storybook-addon/csf3'; + import { SessionProvider } from '../src/contexts/SessionContext'; + import { mswHandlers } from './msw-handlers'; const preview = { decorators: [ @@ -27,9 +55,9 @@ export function getPreviewExample(projectInfo: ProjectInfo): string { ), ], - loaders: [mswLoader], - parameters: { msw: { handlers: mswHandlers } }, - async beforeEach() { + loaders: [mswLoader()], + async beforeEach({ msw }) { + msw.use(...mswHandlers); localStorage.setItem('theme', 'dark'); MockDate.set('2024-04-01T12:00:00Z'); }, @@ -46,12 +74,10 @@ export function getPreviewExample(projectInfo: ProjectInfo): string { import type { Preview } from '${typeImport}'; import '../src/index.css'; import MockDate from 'mockdate'; - import { initialize, mswLoader } from 'msw-storybook-addon'; + import { mswLoader } from 'msw-storybook-addon/csf3'; import { SessionProvider } from '../src/contexts/SessionContext'; import { mswHandlers } from './msw-handlers'; - initialize({ onUnhandledRequest: 'bypass' }); - const preview: Preview = { decorators: [ (Story) => ( @@ -60,9 +86,9 @@ export function getPreviewExample(projectInfo: ProjectInfo): string { ), ], - loaders: [mswLoader], - parameters: { msw: { handlers: mswHandlers } }, - async beforeEach() { + loaders: [mswLoader()], + async beforeEach({ msw }) { + msw.use(...mswHandlers); localStorage.setItem('theme', 'dark'); MockDate.set('2024-04-01T12:00:00Z'); }, diff --git a/code/core/src/cli/ai/setup-prompts/partials/steps.ts b/code/core/src/cli/ai/setup-prompts/partials/steps.ts index 160644caff60..a74c41cd0b08 100644 --- a/code/core/src/cli/ai/setup-prompts/partials/steps.ts +++ b/code/core/src/cli/ai/setup-prompts/partials/steps.ts @@ -183,12 +183,18 @@ export function mswStep( { configDir, mswInstall, packageManager, ts }: InstructionsContext ): { title: string; body: string } { const mswInit = getMswInitCommand(packageManager); + const mswAddonAdd = packageManager.getPackageCommand([ + 'storybook', + 'add', + 'msw-storybook-addon@3', + ]); return { title: 'MSW handlers (only what stories will hit)', - body: `Use \`msw-storybook-addon\`. Install with: + body: `Use \`msw-storybook-addon\`. Register it with \`storybook add\` (which also adds it to the \`addons\` field of \`${configDir}/main.${ts}\`), then install its peer dependencies and generate the worker script: \`\`\`bash + ${mswAddonAdd} ${mswInstall} ${mswInit} \`\`\` @@ -203,13 +209,11 @@ export function mswStep( // ${configDir}/msw-handlers.${ts} import { http, HttpResponse } from 'msw'; - export const mswHandlers = { - products: [ - http.get('https://api.example.com/products', () => - HttpResponse.json({ items: [{ id: 'p1', name: 'Example', price: 42 }] }) - ), - ], - }; + export const mswHandlers = [ + http.get('https://api.example.com/products', () => + HttpResponse.json({ items: [{ id: 'p1', name: 'Example', price: 42 }] }) + ), + ]; \`\`\` `, }; diff --git a/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts b/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts index e73243fb9fb6..007b594c39d2 100644 --- a/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts +++ b/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts @@ -143,19 +143,13 @@ function getMswPreviewExample(projectInfo: ProjectInfo): string { \`\`\`tsx // ${configDir}/preview.tsx import { definePreview } from 'storybook/preview'; - import { initialize, mswLoader } from 'msw-storybook-addon'; + import addonMsw from 'msw-storybook-addon'; import { mswHandlers } from './msw-handlers'; - initialize({ - onUnhandledRequest: 'bypass', - }); - export default definePreview({ - loaders: [mswLoader], - parameters: { - msw: { - handlers: mswHandlers, - }, + addons: [addonMsw()], + async beforeEach({ msw }) { + msw.use(...mswHandlers); }, }); \`\`\` @@ -166,19 +160,13 @@ function getMswPreviewExample(projectInfo: ProjectInfo): string { \`\`\`tsx // ${configDir}/preview.tsx import type { Preview } from '${typeImport}'; - import { initialize, mswLoader } from 'msw-storybook-addon'; + import { mswLoader } from 'msw-storybook-addon/csf3'; import { mswHandlers } from './msw-handlers'; - initialize({ - onUnhandledRequest: 'bypass', - }); - const preview: Preview = { - loaders: [mswLoader], - parameters: { - msw: { - handlers: mswHandlers, - }, + loaders: [mswLoader()], + async beforeEach({ msw }) { + msw.use(...mswHandlers); }, }; @@ -453,6 +441,11 @@ export function instructions(projectInfo: ProjectInfo): string { const configDir = projectInfo.configDir; const typeImport = getTypeImportSource(projectInfo); const mswInit = getMswInitCommand(projectInfo.packageManager); + const mswAddonAdd = projectInfo.packageManager.getPackageCommand([ + 'storybook', + 'add', + 'msw-storybook-addon@3', + ]); const vitestRunFile = getVitestStorybookRunCommand( projectInfo.packageManager, '' @@ -609,7 +602,7 @@ export function instructions(projectInfo: ProjectInfo): string { All network/data queries should be handled by the default Storybook environment. - Always use \`msw-storybook-addon\` for query mocking. - - If you introduce MSW, run \`${mswInit}\` to create the worker file. + - If you introduce MSW, register the addon with \`${mswAddonAdd}\` (which also adds it to the main config) and run \`${mswInit}\` to create the worker file. - Make sure Storybook serves \`./public\` as a static dir so \`mockServiceWorker.js\` is available. - Do not mock \`fetch\` directly. - Network/data queries should return deterministic mock data. @@ -633,23 +626,21 @@ export function instructions(projectInfo: ProjectInfo): string { // ${configDir}/msw-handlers.ts import { http, HttpResponse } from "msw"; - export const mswHandlers = { - products: [ - http.get("https://api.example.com/products", () => - HttpResponse.json({ - items: [ - { - id: "product-1", - name: "Example product", - description: "Mock product description", - imageUrl: "https://images.example.com/product.jpg", - price: 42, - }, - ], - }), - ), - ], - }; + export const mswHandlers = [ + http.get("https://api.example.com/products", () => + HttpResponse.json({ + items: [ + { + id: "product-1", + name: "Example product", + description: "Mock product description", + imageUrl: "https://images.example.com/product.jpg", + price: 42, + }, + ], + }), + ), + ]; \`\`\` ${getMswPreviewExample(projectInfo)} diff --git a/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts b/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts index f472395fe3f6..9ab865afe0b1 100644 --- a/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts +++ b/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts @@ -39,7 +39,7 @@ export function instructions(projectInfo: ProjectInfo): string { const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); const mswInstall = packageManager.getInstallCommand( - ['msw', 'msw-storybook-addon', 'mockdate'], + ['msw', 'mockdate'], true ); From 9200c8d3b2c108ad50eff020c6d5bbc4e4b9def1 Mon Sep 17 00:00:00 2001 From: yannbf Date: Thu, 23 Jul 2026 19:19:24 +0200 Subject: [PATCH 2/3] CLI: Warn about injected msw namespace entries in CSF factories previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `storybook add` syncs third-party addons into `definePreview` as namespace imports, which the factories types reject — a single such entry collapses the whole preview's inference to `never` (#35571). When the project uses CSF factories, the msw setup step now tells the agent to remove the injected `mswStorybookAddon` entry and register `addonMsw()` instead. --- code/core/src/cli/ai/setup-prompts/partials/steps.ts | 8 +++++++- code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/code/core/src/cli/ai/setup-prompts/partials/steps.ts b/code/core/src/cli/ai/setup-prompts/partials/steps.ts index a74c41cd0b08..102a72f0abb5 100644 --- a/code/core/src/cli/ai/setup-prompts/partials/steps.ts +++ b/code/core/src/cli/ai/setup-prompts/partials/steps.ts @@ -188,6 +188,12 @@ export function mswStep( 'add', 'msw-storybook-addon@3', ]); + const csfNextNote = projectInfo.hasCsfFactoryPreview + ? ` + + If \`storybook add\` injects \`import * as mswStorybookAddon from 'msw-storybook-addon/preview'\` and a \`mswStorybookAddon\` entry into your \`definePreview\` \`addons\`, remove both — that form breaks TypeScript inference for the entire preview. Register the addon with \`addonMsw()\` as shown below instead. +` + : ''; return { title: 'MSW handlers (only what stories will hit)', @@ -198,7 +204,7 @@ export function mswStep( ${mswInstall} ${mswInit} \`\`\` - +${csfNextNote} Make sure \`${configDir}/main.${ts}\` serves \`./public\`: ${getMainConfigExample(projectInfo)} diff --git a/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts b/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts index 007b594c39d2..d29b01653d15 100644 --- a/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts +++ b/code/core/src/cli/ai/setup-prompts/pattern-copy-play.ts @@ -602,7 +602,11 @@ export function instructions(projectInfo: ProjectInfo): string { All network/data queries should be handled by the default Storybook environment. - Always use \`msw-storybook-addon\` for query mocking. - - If you introduce MSW, register the addon with \`${mswAddonAdd}\` (which also adds it to the main config) and run \`${mswInit}\` to create the worker file. + - If you introduce MSW, register the addon with \`${mswAddonAdd}\` (which also adds it to the main config) and run \`${mswInit}\` to create the worker file.${ + projectInfo.hasCsfFactoryPreview + ? " If `storybook add` injects `import * as mswStorybookAddon from 'msw-storybook-addon/preview'` and a `mswStorybookAddon` entry into `definePreview` `addons`, remove both — that form breaks TypeScript inference for the entire preview; use `addonMsw()` instead." + : '' + } - Make sure Storybook serves \`./public\` as a static dir so \`mockServiceWorker.js\` is available. - Do not mock \`fetch\` directly. - Network/data queries should return deterministic mock data. From 80148d83bd040167ca96625682d6c6ea31cf05f8 Mon Sep 17 00:00:00 2001 From: yannbf Date: Fri, 24 Jul 2026 07:59:48 +0200 Subject: [PATCH 3/3] CLI: Fix formatting of msw install command in setup prompt variants --- ...orepo-optimized-tests-relaxed-limits-no-story-deletion.ts | 5 +---- code/core/src/cli/ai/setup-prompts/monorepo.ts | 5 +---- code/core/src/cli/ai/setup-prompts/optimized-tests.ts | 5 +---- code/core/src/cli/ai/setup-prompts/relaxed-limits.ts | 5 +---- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts b/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts index b3c503977a7e..4adaf33b00ed 100644 --- a/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts +++ b/code/core/src/cli/ai/setup-prompts/monorepo-optimized-tests-relaxed-limits-no-story-deletion.ts @@ -40,10 +40,7 @@ export function instructions(projectInfo: ProjectInfo): string { const tsx = ext(language, true); const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); - const mswInstall = packageManager.getInstallCommand( - ['msw', 'mockdate'], - true - ); + const mswInstall = packageManager.getInstallCommand(['msw', 'mockdate'], true); const ctx: SetupInstructionsContext = { configDir, diff --git a/code/core/src/cli/ai/setup-prompts/monorepo.ts b/code/core/src/cli/ai/setup-prompts/monorepo.ts index 19308a774075..7c3f6bc8a549 100644 --- a/code/core/src/cli/ai/setup-prompts/monorepo.ts +++ b/code/core/src/cli/ai/setup-prompts/monorepo.ts @@ -40,10 +40,7 @@ export function instructions(projectInfo: ProjectInfo): string { const tsx = ext(language, true); const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); - const mswInstall = packageManager.getInstallCommand( - ['msw', 'mockdate'], - true - ); + const mswInstall = packageManager.getInstallCommand(['msw', 'mockdate'], true); const ctx: SetupInstructionsContext = { configDir, diff --git a/code/core/src/cli/ai/setup-prompts/optimized-tests.ts b/code/core/src/cli/ai/setup-prompts/optimized-tests.ts index 97db3e21515b..69dad4883a13 100644 --- a/code/core/src/cli/ai/setup-prompts/optimized-tests.ts +++ b/code/core/src/cli/ai/setup-prompts/optimized-tests.ts @@ -38,10 +38,7 @@ export function instructions(projectInfo: ProjectInfo): string { const tsx = ext(language, true); const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); - const mswInstall = packageManager.getInstallCommand( - ['msw', 'mockdate'], - true - ); + const mswInstall = packageManager.getInstallCommand(['msw', 'mockdate'], true); const ctx: SetupInstructionsContext = { configDir, diff --git a/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts b/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts index 9ab865afe0b1..541a66425c89 100644 --- a/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts +++ b/code/core/src/cli/ai/setup-prompts/relaxed-limits.ts @@ -38,10 +38,7 @@ export function instructions(projectInfo: ProjectInfo): string { const tsx = ext(language, true); const ts = ext(language, false); const docsUrl = (path: string) => getDocsMarkdownUrl(path, projectInfo); - const mswInstall = packageManager.getInstallCommand( - ['msw', 'mockdate'], - true - ); + const mswInstall = packageManager.getInstallCommand(['msw', 'mockdate'], true); const ctx: SetupInstructionsContext = { configDir,