@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.
- package/dist/_node-chunks/{block-dependencies-versions-3JQG5RZL.js → block-dependencies-versions-2FTSXOV4.js} +11 -11
- package/dist/_node-chunks/{block-experimental-addon-test-DYNRVKQS.js → block-experimental-addon-test-MBAWHIVX.js} +9 -9
- package/dist/_node-chunks/{block-major-version-WUJ2IYKH.js → block-major-version-GLL4GDWB.js} +9 -9
- package/dist/_node-chunks/{block-node-version-ML6OV5FZ.js → block-node-version-RVJJJ4XR.js} +9 -9
- package/dist/_node-chunks/{block-webpack5-frameworks-IY3NOB6J.js → block-webpack5-frameworks-XEU7EO2R.js} +11 -11
- package/dist/_node-chunks/{chunk-522W525H.js → chunk-34WT6WHN.js} +6 -6
- package/dist/_node-chunks/{chunk-MNTUL4IJ.js → chunk-3FXXXDDK.js} +7 -7
- package/dist/_node-chunks/chunk-6HOHNSVJ.js +810 -0
- package/dist/_node-chunks/{chunk-TQZC6S4H.js → chunk-C2UHYVPK.js} +7 -7
- package/dist/_node-chunks/{chunk-KMODVRZU.js → chunk-CXC2V53L.js} +15 -15
- package/dist/_node-chunks/chunk-INX3KICK.js +20 -0
- package/dist/_node-chunks/chunk-JNFHA3R2.js +23 -0
- package/dist/_node-chunks/chunk-RXAW6T4J.js +527 -0
- package/dist/_node-chunks/chunk-SIZ5DHPZ.js +11 -0
- package/dist/_node-chunks/{globby-PFUJPJPH.js → globby-N46UCCVS.js} +8 -8
- package/dist/_node-chunks/monorepo-TVSJOSCW.js +111 -0
- package/dist/_node-chunks/optimized-tests-V6WOQ2XH.js +107 -0
- package/dist/_node-chunks/{p-limit-FVG52ILT.js → p-limit-BGPAPMNJ.js} +7 -7
- package/dist/_node-chunks/pattern-copy-play-6YIEGMCW.js +20 -0
- package/dist/_node-chunks/relaxed-limits-YQA6Y4DQ.js +107 -0
- package/dist/_node-chunks/{run-KUMQEGOH.js → run-IPZ5VOW3.js} +357 -1042
- package/dist/_node-chunks/{setup-VZ6IVNC4.js → setup-HAYJDL6K.js} +10 -10
- package/dist/bin/index.js +7 -7
- package/package.json +4 -4
- 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
|
+
};
|