@storybook/cli 10.4.0-alpha.9 → 10.4.0-beta.0

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