@storybook/cli 10.4.0-alpha.15 → 10.4.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/_node-chunks/{block-dependencies-versions-3JQG5RZL.js → block-dependencies-versions-2FTSXOV4.js} +11 -11
  2. package/dist/_node-chunks/{block-experimental-addon-test-DYNRVKQS.js → block-experimental-addon-test-MBAWHIVX.js} +9 -9
  3. package/dist/_node-chunks/{block-major-version-WUJ2IYKH.js → block-major-version-GLL4GDWB.js} +9 -9
  4. package/dist/_node-chunks/{block-node-version-ML6OV5FZ.js → block-node-version-RVJJJ4XR.js} +9 -9
  5. package/dist/_node-chunks/{block-webpack5-frameworks-IY3NOB6J.js → block-webpack5-frameworks-XEU7EO2R.js} +11 -11
  6. package/dist/_node-chunks/{chunk-522W525H.js → chunk-34WT6WHN.js} +6 -6
  7. package/dist/_node-chunks/{chunk-MNTUL4IJ.js → chunk-3FXXXDDK.js} +7 -7
  8. package/dist/_node-chunks/chunk-6HOHNSVJ.js +810 -0
  9. package/dist/_node-chunks/{chunk-TQZC6S4H.js → chunk-C2UHYVPK.js} +7 -7
  10. package/dist/_node-chunks/{chunk-KMODVRZU.js → chunk-CXC2V53L.js} +15 -15
  11. package/dist/_node-chunks/chunk-INX3KICK.js +20 -0
  12. package/dist/_node-chunks/chunk-JNFHA3R2.js +23 -0
  13. package/dist/_node-chunks/chunk-RXAW6T4J.js +527 -0
  14. package/dist/_node-chunks/chunk-SIZ5DHPZ.js +11 -0
  15. package/dist/_node-chunks/{globby-PFUJPJPH.js → globby-N46UCCVS.js} +8 -8
  16. package/dist/_node-chunks/monorepo-TVSJOSCW.js +111 -0
  17. package/dist/_node-chunks/optimized-tests-V6WOQ2XH.js +107 -0
  18. package/dist/_node-chunks/{p-limit-FVG52ILT.js → p-limit-BGPAPMNJ.js} +7 -7
  19. package/dist/_node-chunks/pattern-copy-play-6YIEGMCW.js +20 -0
  20. package/dist/_node-chunks/relaxed-limits-YQA6Y4DQ.js +107 -0
  21. package/dist/_node-chunks/{run-KUMQEGOH.js → run-IPZ5VOW3.js} +357 -1042
  22. package/dist/_node-chunks/{setup-VZ6IVNC4.js → setup-HAYJDL6K.js} +10 -10
  23. package/dist/bin/index.js +7 -7
  24. package/package.json +4 -4
  25. package/dist/_node-chunks/chunk-WSXOTENG.js +0 -11
@@ -0,0 +1,810 @@
1
+ import CJS_COMPAT_NODE_URL_cy5uq3ou7e from 'node:url';
2
+ import CJS_COMPAT_NODE_PATH_cy5uq3ou7e from 'node:path';
3
+ import CJS_COMPAT_NODE_MODULE_cy5uq3ou7e from "node:module";
4
+
5
+ var __filename = CJS_COMPAT_NODE_URL_cy5uq3ou7e.fileURLToPath(import.meta.url);
6
+ var __dirname = CJS_COMPAT_NODE_PATH_cy5uq3ou7e.dirname(__filename);
7
+ var require = CJS_COMPAT_NODE_MODULE_cy5uq3ou7e.createRequire(import.meta.url);
8
+
9
+ // ------------------------------------------------------------
10
+ // end of CJS compatibility banner, injected by Storybook's esbuild configuration
11
+ // ------------------------------------------------------------
12
+ import {
13
+ getDocsMarkdownUrl
14
+ } from "./chunk-JNFHA3R2.js";
15
+ import {
16
+ getTypeImportSource
17
+ } from "./chunk-INX3KICK.js";
18
+
19
+ // src/ai/setup-prompts/pattern-copy-play.ts
20
+ import { dedent } from "ts-dedent";
21
+ function getDocsReferenceSection(projectInfo) {
22
+ let docsUrl = (path) => getDocsMarkdownUrl(path, projectInfo);
23
+ return dedent`
24
+ ### Storybook Documentation Reference
25
+
26
+ Use the following references to look up Storybook APIs, concepts, or examples:
27
+
28
+ - Full docs index: https://storybook.js.org/llms.txt
29
+ - See code snippets only with codeOnly=true param e.g. ${docsUrl("writing-stories")}&codeOnly=true
30
+
31
+ Key documentation pages for this task:
32
+ - Writing stories: ${docsUrl("writing-stories")}
33
+ - Decorators: ${docsUrl("writing-stories/decorators")}
34
+ - Args: ${docsUrl("writing-stories/args")}
35
+ - Play functions: ${docsUrl("writing-stories/play-function")}
36
+ - Vitest integration: ${docsUrl("writing-tests/vitest-plugin")}
37
+
38
+ Fetch these URLs directly when you need guidance on Storybook APIs or patterns.
39
+ `;
40
+ }
41
+ function getPreviewConfigExample(projectInfo) {
42
+ let configDir = projectInfo.configDir, typeImport = getTypeImportSource(projectInfo);
43
+ return projectInfo.hasCsfFactoryPreview ? dedent`
44
+ \`\`\`tsx
45
+ // ${configDir}/preview.tsx
46
+ import '../src/index.css'; // import global styles
47
+ import MockDate from 'mockdate';
48
+
49
+ import { definePreview } from 'storybook/preview';
50
+ import { SessionProvider } from '../src/contexts/SessionContext';
51
+
52
+ export default definePreview({
53
+ decorators: [
54
+ (Story) => (
55
+ <SessionProvider>
56
+ <Story />
57
+ </SessionProvider>
58
+ ),
59
+ ],
60
+ async beforeEach() {
61
+ localStorage.setItem('theme', 'dark');
62
+ localStorage.setItem('sidebar:open', 'true');
63
+ MockDate.set('2024-04-01T12:00:00Z');
64
+ },
65
+ });
66
+ \`\`\`
67
+ ` : dedent`
68
+ \`\`\`tsx
69
+ // ${configDir}/preview.tsx
70
+ import type { Preview } from '${typeImport}';
71
+ import MockDate from 'mockdate';
72
+ import '../src/index.css'; // import global styles
73
+ import { SessionProvider } from '../src/contexts/SessionContext';
74
+
75
+ const preview: Preview = {
76
+ decorators: [
77
+ (Story) => (
78
+ <SessionProvider>
79
+ <Story />
80
+ </SessionProvider>
81
+ ),
82
+ ],
83
+ async beforeEach() {
84
+ localStorage.setItem('theme', 'dark');
85
+ localStorage.setItem('sidebar:open', 'true');
86
+ MockDate.set('2024-04-01T12:00:00Z');
87
+ },
88
+ };
89
+
90
+ export default preview;
91
+ \`\`\`
92
+ `;
93
+ }
94
+ function getMockDateExample(projectInfo) {
95
+ let typeImport = getTypeImportSource(projectInfo);
96
+ return projectInfo.hasCsfFactoryPreview ? dedent`
97
+ \`\`\`tsx
98
+ import MockDate from 'mockdate';
99
+ import { definePreview } from 'storybook/preview';
100
+
101
+ export default definePreview({
102
+ async beforeEach() {
103
+ MockDate.set('2024-04-01T12:00:00Z');
104
+ },
105
+ });
106
+ \`\`\`
107
+ ` : dedent`
108
+ \`\`\`tsx
109
+ import type { Preview } from '${typeImport}';
110
+ import MockDate from 'mockdate';
111
+
112
+ const preview: Preview = {
113
+ async beforeEach() {
114
+ MockDate.set('2024-04-01T12:00:00Z');
115
+ },
116
+ };
117
+
118
+ export default preview;
119
+ \`\`\`
120
+ `;
121
+ }
122
+ function getMswPreviewExample(projectInfo) {
123
+ let configDir = projectInfo.configDir, typeImport = getTypeImportSource(projectInfo);
124
+ return projectInfo.hasCsfFactoryPreview ? dedent`
125
+ \`\`\`tsx
126
+ // ${configDir}/preview.tsx
127
+ import { definePreview } from 'storybook/preview';
128
+ import { initialize, mswLoader } from 'msw-storybook-addon';
129
+ import { mswHandlers } from './msw-handlers';
130
+
131
+ initialize({
132
+ onUnhandledRequest: 'bypass',
133
+ });
134
+
135
+ export default definePreview({
136
+ loaders: [mswLoader],
137
+ parameters: {
138
+ msw: {
139
+ handlers: mswHandlers,
140
+ },
141
+ },
142
+ });
143
+ \`\`\`
144
+ ` : dedent`
145
+ \`\`\`tsx
146
+ // ${configDir}/preview.tsx
147
+ import type { Preview } from '${typeImport}';
148
+ import { initialize, mswLoader } from 'msw-storybook-addon';
149
+ import { mswHandlers } from './msw-handlers';
150
+
151
+ initialize({
152
+ onUnhandledRequest: 'bypass',
153
+ });
154
+
155
+ const preview: Preview = {
156
+ loaders: [mswLoader],
157
+ parameters: {
158
+ msw: {
159
+ handlers: mswHandlers,
160
+ },
161
+ },
162
+ };
163
+
164
+ export default preview;
165
+ \`\`\`
166
+ `;
167
+ }
168
+ function getStoryExample(projectInfo) {
169
+ if (projectInfo.hasCsfFactoryPreview)
170
+ return dedent`
171
+ \`\`\`tsx
172
+ import preview from '#.storybook/preview';
173
+ import { expect } from 'storybook/test';
174
+ import { SomeComponent } from './SomeComponent';
175
+
176
+ const meta = preview.meta({
177
+ component: SomeComponent,
178
+ tags: ['ai-generated'],
179
+ });
180
+
181
+ export const Default = meta.story({
182
+ render: () => <SomeComponent variant="primary" disabled={false} />,
183
+ play: async ({ canvas }) => {
184
+ await expect(canvas.getByRole('button')).toBeVisible();
185
+ },
186
+ });
187
+ \`\`\`
188
+ `;
189
+ let typeImport = getTypeImportSource(projectInfo);
190
+ return dedent`
191
+ \`\`\`tsx
192
+ import type { Meta, StoryObj } from '${typeImport}';
193
+ import { expect } from 'storybook/test';
194
+ import { SomeComponent } from './SomeComponent';
195
+
196
+ const meta = {
197
+ component: SomeComponent,
198
+ tags: ['ai-generated'],
199
+ } satisfies Meta<typeof SomeComponent>;
200
+
201
+ export default meta;
202
+ type Story = StoryObj<typeof meta>;
203
+
204
+ export const Default: Story = {
205
+ render: () => <SomeComponent variant="primary" disabled={false} />,
206
+ play: async ({ canvas }) => {
207
+ await expect(canvas.getByRole('button')).toBeVisible();
208
+ },
209
+ };
210
+ \`\`\`
211
+ `;
212
+ }
213
+ function getNeedsWorkTagExample(projectInfo) {
214
+ return projectInfo.hasCsfFactoryPreview ? dedent`
215
+ \`\`\`ts
216
+ const meta = preview.meta({
217
+ component: SomeComponent,
218
+ tags: ['ai-generated', 'needs-work'],
219
+ });
220
+ \`\`\`
221
+ ` : dedent`
222
+ \`\`\`ts
223
+ const meta = {
224
+ component: SomeComponent,
225
+ tags: ['ai-generated', 'needs-work'],
226
+ } satisfies Meta<typeof SomeComponent>;
227
+ \`\`\`
228
+ `;
229
+ }
230
+ function getArgsStoryExample(projectInfo) {
231
+ if (projectInfo.hasCsfFactoryPreview)
232
+ return dedent`
233
+ \`\`\`tsx
234
+ import preview from '#.storybook/preview';
235
+ import { expect } from 'storybook/test';
236
+ import { Button } from './Button';
237
+
238
+ const meta = preview.meta({
239
+ component: Button,
240
+ tags: ['ai-generated'],
241
+ });
242
+
243
+ export const Primary = meta.story({
244
+ args: {
245
+ variant: 'primary',
246
+ children: 'Save',
247
+ },
248
+ play: async ({ canvas }) => {
249
+ await expect(canvas.getByRole('button', { name: /save/i })).toBeVisible();
250
+ },
251
+ });
252
+
253
+ export const Disabled = meta.story({
254
+ args: {
255
+ variant: 'primary',
256
+ disabled: true,
257
+ children: 'Save',
258
+ },
259
+ play: async ({ canvas }) => {
260
+ await expect(canvas.getByRole('button')).toBeDisabled();
261
+ },
262
+ });
263
+ \`\`\`
264
+ `;
265
+ let typeImport = getTypeImportSource(projectInfo);
266
+ return dedent`
267
+ \`\`\`tsx
268
+ import type { Meta, StoryObj } from '${typeImport}';
269
+ import { expect } from 'storybook/test';
270
+ import { Button } from './Button';
271
+
272
+ const meta = {
273
+ component: Button,
274
+ tags: ['ai-generated'],
275
+ } satisfies Meta<typeof Button>;
276
+
277
+ export default meta;
278
+ type Story = StoryObj<typeof meta>;
279
+
280
+ export const Primary: Story = {
281
+ args: {
282
+ variant: 'primary',
283
+ children: 'Save',
284
+ },
285
+ play: async ({ canvas }) => {
286
+ await expect(canvas.getByRole('button', { name: /save/i })).toBeVisible();
287
+ },
288
+ };
289
+
290
+ export const Disabled: Story = {
291
+ args: {
292
+ variant: 'primary',
293
+ disabled: true,
294
+ children: 'Save',
295
+ },
296
+ play: async ({ canvas }) => {
297
+ await expect(canvas.getByRole('button')).toBeDisabled();
298
+ },
299
+ };
300
+ \`\`\`
301
+ `;
302
+ }
303
+ function getRenderCompositionExample(projectInfo) {
304
+ if (projectInfo.hasCsfFactoryPreview)
305
+ return dedent`
306
+ \`\`\`tsx
307
+ import preview from '#.storybook/preview';
308
+ import { expect } from 'storybook/test';
309
+ import { Button } from './Button';
310
+ import { Card } from './Card';
311
+
312
+ const meta = preview.meta({
313
+ component: Button,
314
+ tags: ['ai-generated'],
315
+ });
316
+
317
+ export const InsideCard = meta.story({
318
+ render: () => (
319
+ <Card>
320
+ <Button disabled={false}>Save</Button>
321
+ </Card>
322
+ ),
323
+ play: async ({ canvas, userEvent }) => {
324
+ await expect(canvas.getByRole('button', { name: /save/i })).toBeVisible();
325
+ await userEvent.click(canvas.getByRole('button', { name: /save/i }));
326
+ },
327
+ });
328
+ \`\`\`
329
+ `;
330
+ let typeImport = getTypeImportSource(projectInfo);
331
+ return dedent`
332
+ \`\`\`tsx
333
+ import type { Meta, StoryObj } from '${typeImport}';
334
+ import { expect } from 'storybook/test';
335
+ import { Button } from './Button';
336
+ import { Card } from './Card';
337
+
338
+ const meta = {
339
+ component: Button,
340
+ tags: ['ai-generated'],
341
+ } satisfies Meta<typeof Button>;
342
+
343
+ export default meta;
344
+ type Story = StoryObj<typeof meta>;
345
+
346
+ export const InsideCard: Story = {
347
+ render: () => (
348
+ <Card>
349
+ <Button disabled={false}>Save</Button>
350
+ </Card>
351
+ ),
352
+ play: async ({ canvas, userEvent }) => {
353
+ await expect(canvas.getByRole('button', { name: /save/i })).toBeVisible();
354
+ await userEvent.click(canvas.getByRole('button', { name: /save/i }));
355
+ },
356
+ };
357
+ \`\`\`
358
+ `;
359
+ }
360
+ function getPageStoryExample(projectInfo) {
361
+ if (projectInfo.hasCsfFactoryPreview)
362
+ return dedent`
363
+ \`\`\`tsx
364
+ import preview from '#.storybook/preview';
365
+ import { expect } from 'storybook/test';
366
+ import { ProductPage } from './ProductPage';
367
+
368
+ const meta = preview.meta({
369
+ component: ProductPage,
370
+ tags: ['ai-generated'],
371
+ });
372
+
373
+ export const Default = meta.story({
374
+ render: () => <ProductPage />,
375
+ play: async ({ canvas }) => {
376
+ await expect(
377
+ canvas.getByRole('heading', { name: /products/i }),
378
+ ).toBeVisible();
379
+ },
380
+ });
381
+ \`\`\`
382
+ `;
383
+ let typeImport = getTypeImportSource(projectInfo);
384
+ return dedent`
385
+ \`\`\`tsx
386
+ import type { Meta, StoryObj } from '${typeImport}';
387
+ import { expect } from 'storybook/test';
388
+ import { ProductPage } from './ProductPage';
389
+
390
+ const meta = {
391
+ component: ProductPage,
392
+ tags: ['ai-generated'],
393
+ } satisfies Meta<typeof ProductPage>;
394
+
395
+ export default meta;
396
+ type Story = StoryObj<typeof meta>;
397
+
398
+ export const Default: Story = {
399
+ render: () => <ProductPage />,
400
+ play: async ({ canvas }) => {
401
+ await expect(
402
+ canvas.getByRole('heading', { name: /products/i }),
403
+ ).toBeVisible();
404
+ },
405
+ };
406
+ \`\`\`
407
+ `;
408
+ }
409
+ function instructions(projectInfo) {
410
+ let configDir = projectInfo.configDir, typeImport = getTypeImportSource(projectInfo);
411
+ return dedent`
412
+ Attention: The following instructions must be followed in order to successfully set up Storybook in this project. Do not skip steps or attempt to do them out of order.
413
+
414
+ Your goal is to make Storybook fully functional in this project by analyzing the codebase,
415
+ configuring the preview with the right decorators, and writing stories for some components.
416
+
417
+ The end state should be a Storybook where any component — from a small button to a full page — can be added without story-specific workarounds. All necessary providers, CSS, browser state, and network mocks should live in the shared preview so that just rendering the component in the story is enough.
418
+
419
+ After each created story, run Vitest to verify it renders.
420
+ If the test fails, read the error, fix the issue, and re-run until it passes before moving on.
421
+
422
+ - Copy real patterns from the codebase
423
+ - Keep the app code unchanged
424
+ - Put the default setup in \`${configDir}/preview.tsx\`
425
+ - Keep app mocking and runtime setup in \`${configDir}/preview.tsx\`, not in the stories
426
+
427
+ ${getDocsReferenceSection(projectInfo)}
428
+
429
+ ### Step 1: Analyze the codebase
430
+
431
+ Read enough of the app to understand the full runtime environment before writing any stories.
432
+
433
+ Do not stop at \`main.tsx\` or \`App.tsx\`.
434
+ Follow imports into providers, pages, hooks, and shared components until you know:
435
+
436
+ - which providers exist
437
+ - which CSS files are injected
438
+ - which queries fetch data
439
+ - which browser-state reads happen
440
+ - which portals and portal roots exist
441
+ - which pages and components show the real usage patterns
442
+
443
+ Example of what to copy:
444
+
445
+ \`\`\`tsx
446
+ // src/main.tsx
447
+ import "./index.css";
448
+ import App from "./App";
449
+ import { SessionProvider } from "./contexts/SessionContext";
450
+
451
+ createRoot(document.getElementById("root")!).render(
452
+ <SessionProvider>
453
+ <App />
454
+ </SessionProvider>,
455
+ );
456
+ \`\`\`
457
+
458
+ That means Storybook should copy:
459
+
460
+ - the \`index.css\` import
461
+ - the \`SessionProvider\`
462
+ - the same provider order
463
+
464
+ Example of tracing the app deeper:
465
+
466
+ \`\`\`tsx
467
+ // src/App.tsx
468
+ function App() {
469
+ const { products, loadMoreProducts } = useProducts();
470
+ const { currentUser, signOut } = useSession();
471
+ // ...
472
+ }
473
+ \`\`\`
474
+
475
+ \`\`\`ts
476
+ // src/hooks/useProducts.ts
477
+ const response = await fetch(apiBaseUrl + "/products?page=1");
478
+ \`\`\`
479
+
480
+ \`\`\`ts
481
+ // src/hooks/useTheme.ts
482
+ const savedTheme = localStorage.getItem("theme");
483
+ \`\`\`
484
+
485
+ That means the default Storybook setup should discover and prepare:
486
+
487
+ - provider state
488
+ - MSW handlers for queries
489
+ - browser-state values that are actually read during render
490
+
491
+ ### Step 2: Build one default app environment in preview
492
+
493
+ Set up Storybook once so most stories work without story-specific setup.
494
+
495
+ Start with the smallest faithful environment:
496
+
497
+ - the real provider tree
498
+ - the real root CSS
499
+ - seeded browser state if the app reads it during render
500
+ - MSW for network/data queries
501
+
502
+ It is fine to seed browser state such as \`localStorage\`, \`sessionStorage\`, and cookies when the app reads them during render.
503
+ Seed only the specific app-owned keys and values you need.
504
+ Do not clear all \`localStorage\`, \`sessionStorage\`, or cookies, and do not reset Storybook's own state.
505
+ Do not mock or redefine the browser runtime itself.
506
+ The stories run in Vitest browser mode, so the real browser environment should already exist.
507
+
508
+ ${getPreviewConfigExample(projectInfo)}
509
+
510
+ Use this same idea for:
511
+
512
+ - providers
513
+ - root CSS
514
+ - browser state
515
+ - dates, and if the app logic depends on them during render then always use \`mockdate\`
516
+
517
+ Example with the \`mockdate\` package:
518
+
519
+ ${getMockDateExample(projectInfo)}
520
+
521
+ ### Step 3: Support portals with preview-body.html
522
+
523
+ If the app uses portals, copy that setup into Storybook too.
524
+
525
+ Look for patterns like:
526
+
527
+ - \`createPortal(...)\`
528
+ - modal, dialog, drawer, popover, tooltip, toast, or dropdown portal components
529
+ - hard-coded roots such as \`#portal-root\`, \`#modal-root\`, \`#drawer-root\`, or \`#toast-root\`
530
+
531
+ Example of what to copy:
532
+
533
+ \`\`\`tsx
534
+ // real component
535
+ return createPortal(<ModalContent />, document.getElementById("portal-root")!);
536
+ \`\`\`
537
+
538
+ That means Storybook should create the same portal root in \`${configDir}/preview-body.html\`:
539
+
540
+ \`\`\`html
541
+ <!-- ${configDir}/preview-body.html -->
542
+ <div id="portal-root"></div>
543
+ \`\`\`
544
+
545
+ If the app uses multiple portal roots, create all of them there:
546
+
547
+ \`\`\`html
548
+ <!-- ${configDir}/preview-body.html -->
549
+ <div id="modal-root"></div>
550
+ <div id="drawer-root"></div>
551
+ <div id="toast-root"></div>
552
+ \`\`\`
553
+
554
+ If a library portals directly to \`document.body\`, do not add extra roots for it.
555
+ Make sure the copied page shell, CSS, and layout still allow overlays, fixed positioning, and z-index stacking to render correctly.
556
+
557
+ ### Step 4: Mock side effects globally
558
+
559
+ All network/data queries should be handled by the default Storybook environment.
560
+
561
+ - Always use \`msw-storybook-addon\` for query mocking.
562
+ - If you introduce MSW, run \`npx msw init ./public --save\` to create the worker file.
563
+ - Make sure Storybook serves \`./public\` as a static dir so \`mockServiceWorker.js\` is available.
564
+ - Do not mock \`fetch\` directly.
565
+ - Network/data queries should return deterministic mock data.
566
+ - If you need to change dependencies, first check the lockfile and use that package manager for the change.
567
+
568
+ Example of copying a real fetch pattern into shared handlers:
569
+
570
+ \`\`\`ts
571
+ // real app hook
572
+ const response = await fetch(
573
+ apiBaseUrl +
574
+ "/products?" +
575
+ new URLSearchParams({
576
+ page: "1",
577
+ sort: "featured",
578
+ }),
579
+ );
580
+ \`\`\`
581
+
582
+ \`\`\`ts
583
+ // ${configDir}/msw-handlers.ts
584
+ import { http, HttpResponse } from "msw";
585
+
586
+ export const mswHandlers = {
587
+ products: [
588
+ http.get("https://api.example.com/products", () =>
589
+ HttpResponse.json({
590
+ items: [
591
+ {
592
+ id: "product-1",
593
+ name: "Example product",
594
+ description: "Mock product description",
595
+ imageUrl: "https://images.example.com/product.jpg",
596
+ price: 42,
597
+ },
598
+ ],
599
+ }),
600
+ ),
601
+ ],
602
+ };
603
+ \`\`\`
604
+
605
+ ${getMswPreviewExample(projectInfo)}
606
+
607
+ \`\`\`ts
608
+ // ${configDir}/main.ts
609
+ import type { StorybookConfig } from "${typeImport}";
610
+
611
+ const config: StorybookConfig = {
612
+ staticDirs: ["../public"],
613
+ };
614
+
615
+ export default config;
616
+ \`\`\`
617
+
618
+ Keep these mocks global.
619
+ Do not put fetch mocks in individual stories.
620
+ Only add handlers for requests that the shared preview setup or the stories actually use.
621
+ Do not add catch-all handlers that can hide unrelated failures.
622
+ If the defaults are not enough, improve the shared default setup instead.
623
+ Seed browser state when needed, but do not mock \`window\`, \`document\`, \`navigator\`, observers, or similar runtime APIs.
624
+ The only exception is \`mockdate\` when date-based rendering exists.
625
+
626
+ ### Step 5: Write stories
627
+
628
+ Try to find around 10 good candidate components for story files.
629
+ Write colocated stories for top-level components, from low-level reusable components up to page components.
630
+ Write up to 10 story files, or fewer only if the codebase clearly has fewer meaningful targets.
631
+
632
+ The stories should use JSX copied from real usage patterns in:
633
+
634
+ - pages
635
+ - app shells
636
+ - routes
637
+ - tests
638
+ - existing feature code
639
+
640
+ As a rule of thumb, each story file should have around 3 story exports when the component or page has enough meaningful states.
641
+ It can have more when the real usage supports it, up to 10 story exports in one file.
642
+
643
+ Always show all imports explicitly in story and preview files.
644
+ Do not rely on omitted or implied imports in examples or generated code.
645
+
646
+ #### Story tags
647
+
648
+ Every story meta must include the \`ai-generated\` tag to identify AI-created stories:
649
+
650
+ ${getStoryExample(projectInfo)}
651
+
652
+ If a story could not be fully fixed after the self-healing loop (the test still fails
653
+ or the rendering is incomplete), add the \`needs-work\` tag alongside \`ai-generated\`:
654
+
655
+ ${getNeedsWorkTagExample(projectInfo)}
656
+
657
+ #### Args vs render
658
+
659
+ For simple components where props drive the state, prefer \`args\` stories — no \`render\` function needed:
660
+
661
+ ${getArgsStoryExample(projectInfo)}
662
+
663
+ Use \`render\` when the story needs composition — wrapping the component in layout, combining multiple components, or passing children as JSX:
664
+
665
+ ${getRenderCompositionExample(projectInfo)}
666
+
667
+ Keep app mocking and runtime setup in preview, not in the stories.
668
+ Do not build large story-specific harnesses.
669
+ Do not write story files for subcomponents, hooks, contexts, or helpers.
670
+ Do not create new application components.
671
+ Do not add a custom \`title\`.
672
+ Do not stop after only a few easy targets if the codebase has more meaningful components or pages available.
673
+
674
+ ### Step 6: Write a play function for every story
675
+
676
+ Every named story export must have a \`play\` function.
677
+ The \`play\` function is not optional, even for simple stories.
678
+
679
+ The purpose of the \`play\` function is to prove that the story actually works in the copied Storybook environment:
680
+
681
+ - the story renders something real and non-empty
682
+ - the decorators provide the needed context
683
+ - the CSS is applied well enough for the intended state to be visible
684
+ - the MSW mocks or seeded browser state are actually being used
685
+ - important interactions, async loading states, and portals behave correctly
686
+
687
+ Use \`play\` functions to verify behavior, not just to click around.
688
+ A story without assertions is incomplete.
689
+
690
+ Use tools from \`storybook/test\` such as:
691
+
692
+ - \`expect\`
693
+ - \`waitFor\`
694
+
695
+ Prefer \`canvas\` and \`userEvent\` from the \`play\` context.
696
+ Do not destructure \`canvasElement\` just to create \`const canvas = within(canvasElement)\`.
697
+ Do not import \`userEvent\` from \`storybook/test\`; use \`userEvent\` from the \`play\` context instead.
698
+ Only use \`canvasElement.ownerDocument\` when you need to query outside the canvas, such as for portals.
699
+
700
+ Example:
701
+
702
+ \`\`\`tsx
703
+ import type { StoryObj } from "${typeImport}";
704
+
705
+ export const FilledForm: Story = {
706
+ play: async ({ canvas, userEvent }) => {
707
+ const emailInput = canvas.getByLabelText("email", {
708
+ selector: "input",
709
+ });
710
+
711
+ await userEvent.type(emailInput, "example-email@email.com", {
712
+ delay: 100,
713
+ });
714
+
715
+ const passwordInput = canvas.getByLabelText("password", {
716
+ selector: "input",
717
+ });
718
+
719
+ await userEvent.type(passwordInput, "ExamplePassword", {
720
+ delay: 100,
721
+ });
722
+
723
+ const submitButton = canvas.getByRole("button");
724
+ await userEvent.click(submitButton);
725
+ },
726
+ };
727
+ \`\`\`
728
+
729
+ The assertions should match the real pattern you copied:
730
+
731
+ - for provider-backed stories, assert the provider-dependent UI appears correctly
732
+ - for mocked-data stories, wait for the mocked data to appear and assert on it
733
+ - for CSS-sensitive states, assert on visibility, text layout, class-driven states, or meaningful computed styles
734
+ - for routing or navigation stories, assert the routed state or navigation outcome
735
+ - for portal stories, query from \`canvasElement.ownerDocument\` when the UI renders outside the canvas
736
+
737
+ Examples of useful checks:
738
+
739
+ - a themed button has the expected label and is visibly enabled or disabled
740
+ - a modal opened through a decorator or provider is visible in the portal root
741
+ - mocked API data appears in the page instead of a loading spinner forever
742
+ - a selected tab actually shows the selected panel
743
+ - a toast, alert, or badge has the expected accessible text and visual state
744
+ - a CSS class or computed style confirms the real state that matters
745
+
746
+ ### Step 7: Prove CSS is loaded in exactly one story named \`CssCheck\`
747
+
748
+ In exactly one story, named \`CssCheck\`, assert a component-specific computed style. \`toBeVisible\` passes on an unstyled component; a concrete style value proves the shared preview loaded the app's CSS.
749
+
750
+ Pick a visually distinctive component, read a styling value from its source, and assert it with \`getComputedStyle\`:
751
+
752
+ \`\`\`tsx
753
+ export const CssCheck: Story = {
754
+ args: { children: "Submit" },
755
+ play: async ({ canvas }) => {
756
+ const button = canvas.getByRole("button", { name: /submit/i });
757
+ // PrimaryButton uses bg-blue-600 — fails if Tailwind / global CSS did not load.
758
+ await expect(getComputedStyle(button).backgroundColor).toBe("rgb(37, 99, 235)");
759
+ },
760
+ };
761
+ \`\`\`
762
+
763
+ ### Step 8: Cover the patterns you found
764
+
765
+ Write stories for the real patterns in the codebase, for example:
766
+
767
+ - a low-level reusable component in real JSX usage
768
+ - a provider-backed component
769
+ - a browser-state-backed component
770
+ - a fetched-data component
771
+ - a real page component
772
+
773
+ Use \`App.tsx\` to inspect the real provider tree and usage patterns, but do not make a story for \`App\` when the codebase has actual page components.
774
+
775
+ Example page story:
776
+
777
+ ${getPageStoryExample(projectInfo)}
778
+
779
+ ### Step 9: Verify both rendering and types
780
+
781
+ As you work, verify the stories with Vitest:
782
+
783
+ \`\`\`bash
784
+ npx vitest --project storybook <path-to-story-file>
785
+ \`\`\`
786
+
787
+ Also verify types so you catch missing required props, broken imports, and preview typing issues. Run the same TypeScript command the project itself uses.
788
+
789
+ \`\`\`bash
790
+ <project-specific-typescript-command>
791
+ \`\`\`
792
+
793
+ After verification passes, review every changed file and remove anything that is not needed for the final solution, especially debug fixes, overly broad mocks, unnecessary dependencies, and eval artifacts.
794
+
795
+ Keep iterating until:
796
+
797
+ - every story you wrote passes
798
+ - every story you wrote has a meaningful passing \`play\` function
799
+ - the changed stories and preview setup pass the project's real TypeScript check
800
+ - the rendered output looks sensible
801
+ - the default global mocked environment is strong enough that stories do not need manual fetch overrides
802
+ - stories no longer fail because the shared preview setup and story JSX are fixed
803
+ - all passing stories have \`tags: ['ai-generated']\` in their meta
804
+ - any stories that still need work have \`tags: ['ai-generated', 'needs-work']\` in their meta
805
+ `;
806
+ }
807
+
808
+ export {
809
+ instructions
810
+ };