Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a3615c9
fix: disallow extra properties in eslint plugin rule options
andreww2012 Jul 16, 2025
989d67f
Merge branch 'next' into fix/disallow-extra-properties-in-rule-options
yannbf Jul 24, 2025
4e2111a
Merge branch 'next' into fix/disallow-extra-properties-in-rule-options
yannbf Jul 24, 2025
7f4a3f4
Merge branch 'next' into fix/disallow-extra-properties-in-rule-options
yannbf Dec 15, 2025
4733313
Merge branch 'next' into fix/disallow-extra-properties-in-rule-options
andreww2012 Dec 16, 2025
a3a3a18
Build: Update tests to expect any numeric value and fix flake
yannbf Jan 15, 2026
394c8a8
Fix VitestManager to correctly set firstVitestConfig directory from c…
yannbf Feb 18, 2026
5b0df51
Docs: Add AI section
kylegach Mar 3, 2026
79e7215
Address feedback
kylegach Mar 3, 2026
9cd3d99
Address feedback
kylegach Mar 5, 2026
7969f72
Apply suggestions from code review
kylegach Mar 5, 2026
79a43b5
Address feedback
kylegach Mar 6, 2026
b4af5e7
Gate all docs to React-only
kylegach Mar 9, 2026
fa7f577
Merge branch 'next' into docs-ai
kylegach Mar 9, 2026
bfe077d
Update docs/ai/best-practices.mdx
kylegach Mar 9, 2026
b9afde7
Update docs/ai/mcp/sharing.mdx
kylegach Mar 9, 2026
b2000d4
Address feedback
kylegach Mar 9, 2026
fb1ac64
Fix Meta import
JReinhold Mar 10, 2026
4da78e6
Initial plan
Copilot Mar 10, 2026
944b91a
fix: bail out eslint flat config setup if eslint-plugin-storybook alr…
Copilot Mar 10, 2026
d96456f
Restore portable story CI jobs for svelte and nextjs
kasperpeulen Mar 10, 2026
f27420b
Merge remote-tracking branch 'origin/next' into kasper/restore-svelte…
kasperpeulen Mar 10, 2026
9a85d2c
Merge pull request #32056 from andreww2012/fix/disallow-extra-propert…
valentinpalkovic Mar 10, 2026
7b9d207
Merge pull request #34090 from storybookjs/kasper/restore-svelte-port…
kasperpeulen Mar 10, 2026
fc1e789
fix: use traverse to detect eslint-plugin-storybook imports (static a…
Copilot Mar 10, 2026
ac7431b
Merge branch 'next' into yann/fix-ghost-channel-test
yannbf Mar 10, 2026
6b5b4c3
Fix Nx cloud in sandbox generation
yannbf Mar 10, 2026
824d5d7
REVERT THIS COMMIT
yannbf Mar 10, 2026
d0f049a
Merge pull request #33547 from storybookjs/yann/fix-ghost-channel-test
yannbf Mar 10, 2026
61b0fcb
Merge branch 'next-release' into next
storybook-bot Mar 10, 2026
577a872
Revert "REVERT THIS COMMIT"
yannbf Mar 10, 2026
4feaa45
Merge pull request #34095 from storybookjs/yann/fix-sandbox-generatio…
yannbf Mar 10, 2026
8335dc0
Merge branch 'next' into docs-ai
kylegach Mar 10, 2026
7eecfa7
Address feedback
kylegach Mar 10, 2026
517922e
Merge pull request #33992 from storybookjs/docs-ai
kylegach Mar 10, 2026
16239f5
Merge pull request #34089 from storybookjs/copilot/fix-eslint-plugin-…
valentinpalkovic Mar 11, 2026
943b2b6
Merge pull request #33865 from storybookjs/yann/vitest-workspace-dete…
yannbf Mar 11, 2026
04efb8b
Revert "Toolbar: Remove extra toolbar divider when zoom controls not …
valentinpalkovic Mar 11, 2026
2f7360f
Merge pull request #34099 from storybookjs/revert-33731-fix/toolbar-d…
valentinpalkovic Mar 11, 2026
1c63f41
Write changelog for 10.3.0-alpha.17 [skip ci]
storybook-bot Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/workflows/generate-sandboxes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Generate and publish sandboxes

on:
schedule:
- cron: "2 2 */1 * *"
- cron: '2 2 */1 * *'
workflow_dispatch:
# To test fixes on push rather than wait for the scheduling, do the following:
# 1. Uncomment the lines below and add your branch.
Expand All @@ -14,8 +14,9 @@ on:
# 4. 👉 DON'T FORGET TO UNDO THE STEPS BEFORE YOU MERGE YOUR CHANGES!

env:
YARN_ENABLE_IMMUTABLE_INSTALLS: "false"
CLEANUP_SANDBOX_NODE_MODULES: "true"
YARN_ENABLE_IMMUTABLE_INSTALLS: 'false'
CLEANUP_SANDBOX_NODE_MODULES: 'true'
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

defaults:
run:
Expand Down Expand Up @@ -50,7 +51,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
node-version-file: '.nvmrc'

- name: Setup git user
run: |
Expand Down Expand Up @@ -119,7 +120,7 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
node-version-file: '.nvmrc'

- name: Setup git user
run: |
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 10.3.0-alpha.17

- Core: Correctly fallback to first detected vitest config file - [#33865](https://github.com/storybookjs/storybook/pull/33865), thanks @yannbf!
- ESLint-plugin: Disallow extra properties in eslint plugin rule options - [#32056](https://github.com/storybookjs/storybook/pull/32056), thanks @andreww2012!
- ESLint: bail out config setup if eslint-plugin-storybook is already imported - [#34089](https://github.com/storybookjs/storybook/pull/34089), thanks @copilot-swe-agent!
- Revert "Toolbar: Remove extra toolbar divider when zoom controls not shown" - [#34099](https://github.com/storybookjs/storybook/pull/34099), thanks @valentinpalkovic!

## 10.3.0-alpha.16

- A11y: Underline MDX links for WCAG SC 1.4.1 compliance - [#33139](https://github.com/storybookjs/storybook/pull/33139), thanks @NikhilChowdhury27!
Expand Down
47 changes: 47 additions & 0 deletions code/core/src/cli/eslintPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,53 @@ describe('configureEslintPlugin', () => {
export default someCustomConfig({}, [{}]);"
`);
});

it('should not modify config if eslint-plugin-storybook is already imported', async () => {
const mockPackageManager = {
getAllDependencies: vi.fn(),
} satisfies Partial<JsPackageManager>;

const mockConfigFile = dedent`
import sb from 'eslint-plugin-storybook';
export default [
...sb.configs['flat/recommended'],
];
`;

vi.mocked(readFile).mockResolvedValue(mockConfigFile);

await configureEslintPlugin({
eslintConfigFile: 'eslint.config.js',
packageManager: mockPackageManager as any,
isFlatConfig: true,
});
expect(vi.mocked(writeFile).mock.calls).toHaveLength(0);
});

it('should not modify config if eslint-plugin-storybook is already dynamically imported', async () => {
const mockPackageManager = {
getAllDependencies: vi.fn(),
} satisfies Partial<JsPackageManager>;

const mockConfigFile = dedent`
import { includeIgnoreFile } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import { composer } from 'eslint-flat-config-utils';

export default composer(
import('eslint-plugin-storybook').then((m) => m.default.configs['flat/recommended']),
);
`;

vi.mocked(readFile).mockResolvedValue(mockConfigFile);

await configureEslintPlugin({
eslintConfigFile: 'eslint.config.js',
packageManager: mockPackageManager as any,
isFlatConfig: true,
});
expect(vi.mocked(writeFile).mock.calls).toHaveLength(0);
});
});
});

Expand Down
32 changes: 32 additions & 0 deletions code/core/src/cli/eslintPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,35 @@ function unwrapTSExpression(expr: any): t.Expression | null | undefined {
export const configureFlatConfig = async (code: string) => {
const ast = babelParse(code);

// Bail out if eslint-plugin-storybook is already imported (static or dynamic) to avoid
// referencing an undefined variable or duplicating the config spread.
// Some configs use dynamic import() expressions (e.g. via eslint-flat-config-utils).
let alreadyHasStorybookImport = false;
traverse(ast, {
ImportDeclaration(path) {
if (path.node.source.value === 'eslint-plugin-storybook') {
alreadyHasStorybookImport = true;
path.stop();
}
},
CallExpression(path) {
// Dynamic import: import('eslint-plugin-storybook')
// Babel represents this as a CallExpression with callee.type === 'Import'
if (
t.isImport(path.node.callee) &&
path.node.arguments.length > 0 &&
t.isStringLiteral(path.node.arguments[0]) &&
path.node.arguments[0].value === 'eslint-plugin-storybook'
) {
alreadyHasStorybookImport = true;
path.stop();
}
},
});
if (alreadyHasStorybookImport) {
return code;
}

let tsEslintLocalName = '';
let eslintDefineConfigLocalName = '';
let eslintConfigExpression: any = null;
Expand Down Expand Up @@ -267,6 +296,9 @@ export async function configureEslintPlugin({
logger.debug(`Detected flat config at ${eslintConfigFile}`);
const code = await readFile(eslintConfigFile, { encoding: 'utf8' });
const output = await configureFlatConfig(code);
if (output === code) {
return;
}
await writeFile(eslintConfigFile, output);
} else {
const eslint = await readConfig(eslintConfigFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ describe('ghostStoriesChannel', () => {
runError: 'Failed to analyze components',
stats: {
globMatchCount: 0,
candidateAnalysisDuration: 0,
candidateAnalysisDuration: expect.any(Number),
totalRunDuration: expect.any(Number),
analyzedCount: 0,
avgComplexity: 0,
Expand Down Expand Up @@ -516,12 +516,12 @@ describe('ghostStoriesChannel', () => {
runError: 'JSON report not found',
stats: {
globMatchCount: 5,
candidateAnalysisDuration: 0,
totalRunDuration: 0,
candidateAnalysisDuration: expect.any(Number),
totalRunDuration: expect.any(Number),
analyzedCount: 2,
avgComplexity: 1.0,
candidateCount: 1,
testRunDuration: 0,
testRunDuration: expect.any(Number),
},
});
});
Expand Down Expand Up @@ -569,12 +569,12 @@ describe('ghostStoriesChannel', () => {
runError: 'Startup Error',
stats: {
globMatchCount: 5,
candidateAnalysisDuration: 0,
totalRunDuration: 0,
candidateAnalysisDuration: expect.any(Number),
totalRunDuration: expect.any(Number),
analyzedCount: 2,
avgComplexity: 1.0,
candidateCount: 1,
testRunDuration: 0,
testRunDuration: expect.any(Number),
},
});
});
Expand Down
5 changes: 2 additions & 3 deletions code/core/src/manager/components/preview/tools/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type { Combo } from 'storybook/manager-api';
const menuMapper = ({ api, state }: Combo) => ({
isVisible: api.getIsNavShown(),
singleStory: state.singleStory,
viewMode: state.viewMode,
toggle: () => api.toggleNav(),
});

Expand All @@ -23,7 +22,7 @@ export const menuTool: Addon_BaseType = {
match: ({ viewMode }) => ['story', 'docs'].includes(viewMode),
render: () => (
<Consumer filter={menuMapper}>
{({ isVisible, toggle, singleStory, viewMode }) =>
{({ isVisible, toggle, singleStory }) =>
!singleStory &&
!isVisible && (
<>
Expand All @@ -36,7 +35,7 @@ export const menuTool: Addon_BaseType = {
>
<MenuIcon />
</Button>
{viewMode === 'story' && <Separator />}
<Separator />
</>
)
}
Expand Down
1 change: 1 addition & 0 deletions code/lib/eslint-plugin/src/rules/no-uninstalled-addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default createStorybookRule({
},
},
},
additionalProperties: false,
},
],
},
Expand Down
3 changes: 2 additions & 1 deletion code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,6 @@
"Dependency Upgrades"
]
]
}
},
"deferredNextVersion": "10.3.0-alpha.17"
}
Binary file added docs/_assets/ai/addon-mcp-splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_assets/ai/manifest-debugger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions docs/_snippets/addon-mcp-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```shell renderer="common" language="js" packageManager="npm"
npx storybook add @storybook/addon-mcp
```

```shell renderer="common" language="js" packageManager="pnpm"
pnpm exec storybook add @storybook/addon-mcp
```

```shell renderer="common" language="js" packageManager="yarn"
yarn exec storybook add @storybook/addon-mcp
```
86 changes: 86 additions & 0 deletions docs/_snippets/addon-mcp-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
```js filename=".storybook/main.js" renderer="common" language="js" tabTitle="CSF 3"
export default {
// Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite, angular, etc.)
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// ... your existing addons
{
name: '@storybook/addon-mcp',
options: {
toolsets: {
dev: false,
},
},
},
],
};
```

```ts filename=".storybook/main.ts" renderer="common" language="ts" tabTitle="CSF 3"
// Replace your-framework with the framework you are using (e.g., react-vite, vue3-vite, angular, etc.)
import type { StorybookConfig } from '@storybook/your-framework';

const config: StorybookConfig = {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// ... your existing addons
{
name: '@storybook/addon-mcp',
options: {
toolsets: {
dev: false,
},
},
},
],
};

export default config;
```

```ts filename=".storybook/main.ts" renderer="react" language="ts" tabTitle="CSF Next 🧪"
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';

export default defineMain({
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// ... your existing addons
{
name: '@storybook/addon-mcp',
options: {
toolsets: {
dev: false,
},
},
},
],
});
```

<!-- JS snippets still needed while providing both CSF 3 & Next -->

```js filename=".storybook/main.js" renderer="react" language="js" tabTitle="CSF Next 🧪"
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';

export default defineMain({
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// ... your existing addons
{
name: '@storybook/addon-mcp',
options: {
toolsets: {
dev: false,
},
},
},
],
});
```
Loading
Loading