Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
41cd7e3
fix: categorize UniversalStore follower timeout error
justismailmemon Apr 19, 2026
8d714ee
fix: clarify UniversalStore follower timeout error message
justismailmemon Apr 19, 2026
c6abd29
Add optional TypeScript peer to react-vite
wojtekmaj Apr 24, 2026
69da71e
fix: preserve disabled a11y rules with runOnly
cyphercodes Apr 28, 2026
92dda65
Build: upgrade type-fest to latest version 5.6.0
tobiasdiez May 13, 2026
3e03f52
Merge branch 'next' into type-fest-upgrade
tobiasdiez May 14, 2026
e298199
Merge branch 'next' into type-fest-upgrade
huang-julien May 18, 2026
a328ec0
Addon-Docs: Resolve CSF4 module exports without a default export
TheSeydiCharyyev May 19, 2026
93548c3
docs(agents): add Code Authoring Principles for agents
valentinpalkovic May 19, 2026
dd37c3a
Update AGENTS.md
valentinpalkovic May 19, 2026
f3f9fca
Apply suggestion from @valentinpalkovic
valentinpalkovic May 19, 2026
d24c7e6
Apply suggestion from @valentinpalkovic
valentinpalkovic May 19, 2026
363433b
Align TypeScript peer range with sibling packages and update lockfile
valentinpalkovic May 19, 2026
03e252e
Merge branch 'next' into add-typescript-peer
valentinpalkovic May 20, 2026
7265811
Merge branch 'next' into fix/34566-universal-store-timeout
valentinpalkovic May 20, 2026
786eb1c
Merge branch 'next' into add-typescript-peer
valentinpalkovic May 21, 2026
27c62c2
Core: Add runtime instance registry
kasperpeulen May 21, 2026
a96e5a4
chore: fix formatting
valentinpalkovic May 21, 2026
c6fa1d3
Merge branch 'next' into type-fest-upgrade
valentinpalkovic May 21, 2026
40a8326
Build: Fix type-fest resolution to not break external packages
valentinpalkovic May 21, 2026
86aa0d6
Build: Write ESLint config to the primary package directory
valentinpalkovic May 21, 2026
92f81a3
Merge branch 'next' into kasper/ade-instance-registry
kasperpeulen May 21, 2026
b9641ae
Build: Sync EXISTING_RESOLUTIONS with the updated resolutions
valentinpalkovic May 21, 2026
45f8ebc
refactor: simplify getDisabledRules to a single-pass loop
valentinpalkovic May 21, 2026
5544e37
Merge pull request #34791 from tobiasdiez/type-fest-upgrade
valentinpalkovic May 21, 2026
6dbc1fa
Merge branch 'next' into fix/a11y-runonly-disabled-rules-34647
valentinpalkovic May 21, 2026
8b798f8
Merge branch 'next' into kasper/ade-instance-registry
kasperpeulen May 21, 2026
2b68ca8
Use addon preset for MCP runtime metadata
kasperpeulen May 21, 2026
2380fd6
Wrap MCP endpoint preset in runtime status
kasperpeulen May 21, 2026
dc1c4dc
Read MCP endpoint from preset object
kasperpeulen May 21, 2026
ba73594
Test addon MCP preset loading
kasperpeulen May 21, 2026
c2966e1
feat: add tags type support
unional May 21, 2026
97cc282
test: stricken `AddonTypes.tags` type
unional May 21, 2026
2d677ed
fix: since there is `Tag = string`, reuse the type
unional May 21, 2026
af92c4a
test: add Tag[] test
unional May 21, 2026
5dcadf1
Merge branch 'next' into fix/issue-34159-csf4-meta-split-chunk
kasperpeulen May 22, 2026
92d8357
Test addon MCP preset options
kasperpeulen May 22, 2026
f453d49
Merge pull request #34834 from TheSeydiCharyyev/fix/issue-34159-csf4-…
kasperpeulen May 22, 2026
d59b09c
Simplify MCP endpoint metadata lookup
kasperpeulen May 22, 2026
f1457f9
Keep MCP addon lookup local
kasperpeulen May 22, 2026
4596fa3
update testing guidelines and assumptions for agent development
valentinpalkovic May 22, 2026
e174cbe
Merge pull request #34819 from unional/tags-csf-next
valentinpalkovic May 22, 2026
0c63d92
Merge pull request #34649 from cyphercodes/fix/a11y-runonly-disabled-…
valentinpalkovic May 22, 2026
7bc69af
Merge pull request #34627 from wojtekmaj/add-typescript-peer
valentinpalkovic May 22, 2026
96ce44c
Merge pull request #34592 from justismailmemon/fix/34566-universal-st…
valentinpalkovic May 22, 2026
9b9ac3f
Merge pull request #34863 from storybookjs/kasper/ade-instance-registry
kasperpeulen May 22, 2026
e4b7f35
Merge pull request #34835 from storybookjs/valentin/add-karpathy-agen…
valentinpalkovic May 22, 2026
83dd929
Write changelog for 10.5.0-alpha.2 [skip ci]
storybook-bot May 22, 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
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,18 @@ Avoid `console.log`, `console.warn`, and `console.error` unless the file is isol

These usually start long-running development servers and are the wrong default for agents.

## Code Authoring Principles

These are recurring failure modes in agent-authored changes to this repo. Apply them when writing or reviewing code, not just when asked.

- **Comments are maintenance docs, not an investigation transcript.** Explain *why* for the next maintainer. Do not commit internal ticket / acceptance-criteria codes (`AC-X2`, `Probe B`, `R6`), the narrative of how you figured something out, "verified byte-identical" provenance prose, or cross-file line references (`L125→L131`) — they are noise and they rot. One or two sentences of rationale beats a paragraph of evidence.
- **Verify environment assumptions empirically before encoding them.** If a design rests on "the bundler strips X" or "this metadata is empty here", prove it with a throwaway probe before building on it (and before writing it into a comment as fact). A 10-line experiment is cheaper than a wrong architecture.
- **Encode assumptions with static checks first.** If an assumption is expected to always hold, prefer making it impossible via TypeScript types and existing lint rules. When static checks are not practical, add a cheap runtime assertion close to the boundary so violations fail loudly at the source.
- **Avoid redundant tests already covered elsewhere.** Do not add tests for code patterns already guaranteed by TypeScript or linting, and do not duplicate coverage that already exists in Storybook `play` functions or Playwright tests.
- **Test contracts (including side effects), not private implementation details.** It is valid to assert side effects when they are part of the public contract. Avoid assertions about internals that are not part of an exported contract, user-visible DOM output, or externally observable behavior.
- **Bias toward broader coverage for security and migrations.** For security-sensitive code paths and legacy data migration logic, prefer handling more edge cases and documenting evidence for the chosen safeguards. Migration compatibility code should be explicitly version-scoped so it can be removed once the support window ends.
- **Prefer deletion and simplicity over speculative generality.** No abstraction, fallback, or "flexibility" for a consumer or scenario that does not exist in this codebase today. If a change adds many lines, check whether the right change removes them.

## Maintenance Rules For Agents

- Use this file as the canonical instruction source
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## 10.5.0-alpha.2

- A11y-Addon: Preserve disabled a11y rules with runOnly - [#34649](https://github.com/storybookjs/storybook/pull/34649), thanks @cyphercodes!
- A11y: Fix MDX heading anchors not keyboard accessible - [#34368](https://github.com/storybookjs/storybook/pull/34368), thanks @TheSeydiCharyyev!
- Add an optional TypeScript peer to react-vite - [#34627](https://github.com/storybookjs/storybook/pull/34627), thanks @wojtekmaj!
- Addon-Docs: Resolve CSF4 module exports without a default export - [#34834](https://github.com/storybookjs/storybook/pull/34834), thanks @TheSeydiCharyyev!
- Angular: Detect model() signal outputs (type inference + compodoc autodocs + runtime binding) - [#34833](https://github.com/storybookjs/storybook/pull/34833), thanks @valentinpalkovic!
- Build: Upgrade type-fest to latest version 5.6.0 - [#34791](https://github.com/storybookjs/storybook/pull/34791), thanks @tobiasdiez!
- CLI: Support `peerDependencies` in framework detection for component libraries - [#34516](https://github.com/storybookjs/storybook/pull/34516), thanks @zhyd1997!
- CSF-Next: Add tags type support for - [#34819](https://github.com/storybookjs/storybook/pull/34819), thanks @unional!
- Core: Add runtime instance registry - [#34863](https://github.com/storybookjs/storybook/pull/34863), thanks @kasperpeulen!
- Core: Categorize UniversalStore follower timeout error - [#34592](https://github.com/storybookjs/storybook/pull/34592), thanks @justismailmemon!
- Docs: Add ariaLabel support to ActionItem interface - [#34749](https://github.com/storybookjs/storybook/pull/34749), thanks @TheSeydiCharyyev!
- Docs: Scope control input ids to each <Controls /> block instance - [#34793](https://github.com/storybookjs/storybook/pull/34793), thanks @TheSeydiCharyyev!
- Docs: Support explicit id prop on <Meta> for standalone MDX - [#34808](https://github.com/storybookjs/storybook/pull/34808), thanks @TheSeydiCharyyev!
- Maintenance: Replace `resolve` and `resolve.exports` with `oxc-resolver` - [#34692](https://github.com/storybookjs/storybook/pull/34692), thanks @valentinpalkovic!
- Next.js-Vite: Bump vite-plugin-storybook-nextjs to ^3.3.0 - [#34838](https://github.com/storybookjs/storybook/pull/34838), thanks @yatishgoel!
- Vitest: Reset playwright cursor position to avoid hover bug - [#34765](https://github.com/storybookjs/storybook/pull/34765), thanks @Sidnioulz!
- Vue3: Specify a specific version for non-dev dependency - [#34794](https://github.com/storybookjs/storybook/pull/34794), thanks @ScopeyNZ!

## 10.5.0-alpha.1

- Angular: Fix custom paths for stats.json on angular - [#34551](https://github.com/storybookjs/storybook/pull/34551), thanks @mrginglymus!
Expand Down
108 changes: 106 additions & 2 deletions code/addons/a11y/src/a11yRunner.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,62 @@
import type { AxeResults } from 'axe-core';
import type { Mock } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { addons } from 'storybook/preview-api';

import { EVENTS } from './constants.ts';

const { axeMock, documentMock } = vi.hoisted(() => {
const documentMock = {
body: {},
getElementById: vi.fn(),
location: { pathname: '/iframe.html' },
};

return {
documentMock,
axeMock: {
reset: vi.fn(),
configure: vi.fn(),
run: vi.fn(),
},
};
});

vi.mock('@storybook/global', () => ({
global: {
document: documentMock,
},
}));

vi.mock('axe-core', () => ({
default: axeMock,
}));

vi.mock('storybook/preview-api');
const mockedAddons = vi.mocked(addons);

const axeResults = {
violations: [],
passes: [],
incomplete: [],
inapplicable: [],
} as Partial<AxeResults> as AxeResults;

describe('a11yRunner', () => {
let mockChannel: { on: Mock; emit?: Mock };

beforeEach(() => {
mockedAddons.getChannel.mockReset();
vi.resetModules();
vi.clearAllMocks();

documentMock.getElementById.mockReturnValue(null);
axeMock.run.mockResolvedValue(axeResults);

mockChannel = { on: vi.fn(), emit: vi.fn() };
mockedAddons.getChannel.mockReturnValue(mockChannel as any);
mockedAddons.getChannel.mockReturnValue(
mockChannel as unknown as ReturnType<typeof addons.getChannel>
);
});

it('should listen to events', async () => {
Expand All @@ -24,4 +65,67 @@ describe('a11yRunner', () => {
expect(mockedAddons.getChannel).toHaveBeenCalled();
expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.MANUAL, expect.any(Function));
});

it('passes disabled configured rules to axe.run when runOnly is present', async () => {
const { run } = await import('./a11yRunner.ts');
const input = {
config: {
rules: [
{ id: 'target-size', enabled: false },
{ id: 'color-contrast', enabled: true },
],
},
options: {
runOnly: ['wcag2a'],
rules: {
'button-name': { enabled: false },
},
},
};

await run(input, 'example-story');

expect(axeMock.configure).toHaveBeenCalledWith({
rules: [
{ id: 'region', enabled: false },
{ id: 'target-size', enabled: false },
{ id: 'color-contrast', enabled: true },
],
});
expect(axeMock.run).toHaveBeenCalledWith(expect.any(Object), {
runOnly: ['wcag2a'],
rules: {
region: { enabled: false },
'target-size': { enabled: false },
'button-name': { enabled: false },
},
});
expect(axeMock.run.mock.calls[0][1]).not.toBe(input.options);
expect(input.options).toEqual({
runOnly: ['wcag2a'],
rules: {
'button-name': { enabled: false },
},
});
});

it('respects configured rule overrides when collecting disabled rules', async () => {
const { run } = await import('./a11yRunner.ts');

await run(
{
config: {
rules: [{ id: 'region', enabled: true }],
},
options: {
runOnly: ['wcag2a'],
},
},
'example-story'
);

expect(axeMock.run).toHaveBeenCalledWith(expect.any(Object), {
runOnly: ['wcag2a'],
});
});
});
47 changes: 45 additions & 2 deletions code/addons/a11y/src/a11yRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ElementA11yParameterError } from 'storybook/internal/preview-errors';

import { global } from '@storybook/global';

import type { AxeResults, ContextProp, ContextSpec } from 'axe-core';
import type { AxeResults, ContextProp, ContextSpec, RunOptions, Spec } from 'axe-core';
import { addons, waitForAnimations } from 'storybook/preview-api';

import { withLinkPaths } from './a11yRunnerUtils.ts';
Expand All @@ -21,6 +21,47 @@ const DISABLED_RULES = [
'region',
] as const;

const getDisabledRules = (rules: Spec['rules'] = []) => {
const disabledRules: NonNullable<RunOptions['rules']> = {};

// Rules are applied in order, so a later entry overrides an earlier one for the same id.
for (const { id, enabled } of rules) {
if (!id || typeof enabled !== 'boolean') {
continue;
}

if (enabled) {
delete disabledRules[id];
} else {
disabledRules[id] = { enabled: false };
}
}

return disabledRules;
};

const mergeDisabledRulesIntoRunOptions = (options: RunOptions, config: Spec): RunOptions => {
// axe.run({ runOnly }) can re-enable tagged rules, so mirror configured disables into
// the same run options object without mutating the user's parameters.
if (!options.runOnly) {
return options;
}

const disabledRules = getDisabledRules(config.rules);

if (Object.keys(disabledRules).length === 0) {
return options;
}

return {
...options,
rules: {
...disabledRules,
...options.rules,
},
};
};

// A simple queue to run axe-core in sequence
// This is necessary because axe-core is not designed to run in parallel
const queue: (() => Promise<void>)[] = [];
Expand Down Expand Up @@ -92,6 +133,8 @@ export const run = async (input: A11yParameters = DEFAULT_PARAMETERS, storyId: s

axe.configure(configWithDefault);

const optionsWithDisabledRules = mergeDisabledRulesIntoRunOptions(options, configWithDefault);

return new Promise<AxeResults>((resolve, reject) => {
const highlightsRoot = document?.getElementById('storybook-highlights-root');
if (highlightsRoot) {
Expand All @@ -100,7 +143,7 @@ export const run = async (input: A11yParameters = DEFAULT_PARAMETERS, storyId: s

const task = async () => {
try {
const result = await axe.run(context, options);
const result = await axe.run(context, optionsWithDisabledRules);
const resultWithLinks = withLinkPaths(result, storyId);
resolve(resultWithLinks);
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@
"tinyspy": "^3.0.2",
"ts-dedent": "^2.0.0",
"tsconfig-paths": "^4.2.0",
"type-fest": "^4.18.1",
"type-fest": "^5.6.0",
"type-plus": "^8.0.0-beta.8",
"typescript": "^5.8.3",
"unique-string": "^3.0.0",
"use-resize-observer": "^9.1.0",
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/cli/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const dev = async (cliOptions: CLIOptions) => {
configType: 'DEVELOPMENT',
ignorePreview: !!cliOptions.previewUrl && !cliOptions.forceBuildPreview,
cache: cache as any,
packageJson: packageJson as unknown as PackageJson, // type-fest types are wrong here because we're on an outdated version of the package
packageJson: packageJson,
} as Options;

await withTelemetry(
Expand Down
15 changes: 7 additions & 8 deletions code/core/src/cli/eslintPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,16 +315,15 @@ export async function configureEslintPlugin({
}
} else {
logger.debug('No ESLint config file found, configuring in package.json instead');
const { packageJson } = packageManager.primaryPackageJson;
const { packageJson, operationDir } = packageManager.primaryPackageJson;
const existingExtends = normalizeExtends(packageJson.eslintConfig?.extends).filter(Boolean);

packageManager.writePackageJson({
...packageJson,
eslintConfig: {
...packageJson.eslintConfig,
extends: [...existingExtends, 'plugin:storybook/recommended'],
},
});
packageJson.eslintConfig = {
...packageJson.eslintConfig,
extends: [...existingExtends, 'plugin:storybook/recommended'],
};

packageManager.writePackageJson(packageJson, operationDir);
}
}

Expand Down
25 changes: 9 additions & 16 deletions code/core/src/common/js-package-manager/JsPackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,10 @@ export abstract class JsPackageManager {

// Update cache with the written content
// Ensure dependencies and devDependencies exist (even if empty) to match PackageJsonWithDepsAndDevDeps type
const cachedPackageJson: PackageJsonWithIndent = {
...packageJsonToWrite,
dependencies: { ...(packageJsonToWrite.dependencies || {}) },
devDependencies: { ...(packageJsonToWrite.devDependencies || {}) },
peerDependencies: { ...(packageJsonToWrite.peerDependencies || {}) },
};
const cachedPackageJson = packageJsonToWrite as PackageJsonWithIndent;
cachedPackageJson.dependencies = { ...(packageJsonToWrite.dependencies || {}) };
cachedPackageJson.devDependencies = { ...(packageJsonToWrite.devDependencies || {}) };
cachedPackageJson.peerDependencies = { ...(packageJsonToWrite.peerDependencies || {}) };
cachedPackageJson[indentSymbol] = indent;
JsPackageManager.packageJsonCache.set(packageJsonPath, cachedPackageJson);
}
Expand Down Expand Up @@ -612,16 +610,11 @@ export abstract class JsPackageManager {
public addScripts(scripts: Record<string, string>) {
const { operationDir, packageJson } = this.#getPrimaryPackageJson();

this.writePackageJson(
{
...packageJson,
scripts: {
...packageJson.scripts,
...scripts,
},
},
operationDir
);
packageJson.scripts = {
...packageJson.scripts,
...scripts,
};
this.writePackageJson(packageJson, operationDir);
}

public addPackageResolutions(versions: Record<string, string>) {
Expand Down
17 changes: 17 additions & 0 deletions code/core/src/core-server/build-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ import { getManagerBuilder, getPreviewBuilder } from './utils/get-builders.ts';
import { getServerChannel } from './utils/get-server-channel.ts';
import { outputStartupInformation } from './utils/output-startup-information.ts';
import { outputStats } from './utils/output-stats.ts';
import {
getMcpMetadataFromMainConfig,
type RuntimeInstanceRecord,
writeStorybookRuntimeInstanceRecord,
} from './utils/runtime-instance-registry.ts';
import { getServerAddresses, getServerChannelUrl, getServerPort } from './utils/server-address.ts';
import { getServer } from './utils/server-init.ts';
import { stripCommentsAndStrings } from './utils/strip-comments-and-strings.ts';
Expand Down Expand Up @@ -303,6 +308,18 @@ export async function buildDevStandalone(
storybookDevServer(fullOptions, server)
);

const mcp: RuntimeInstanceRecord['mcp'] = getMcpMetadataFromMainConfig(config);

await writeStorybookRuntimeInstanceRecord({
address: localAddress,
mcp,
port,
storybookVersion,
}).catch((error: unknown) => {
logger.warn('Storybook failed to write its runtime instance registry record.');
logger.debug(error instanceof Error ? (error.stack ?? error.message) : String(error));
});

const previewTotalTime = previewResult?.totalTime;
const managerTotalTime = managerResult?.totalTime;
const previewStats = previewResult?.stats;
Expand Down
Loading