Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
baded86
chore: fix ejslint execution path in lint-staged
Yeonny0723 Jan 10, 2026
2a2f71e
UI: Implement landmark hook and place landmarks
Sidnioulz Jan 2, 2026
13592ac
Sidebar: Keep nav within keyboard reach when focusing search
Sidnioulz Jan 9, 2026
d2aba19
UI: Isolate landmark highlight code
Sidnioulz Jan 9, 2026
b48ad74
UI: Rename Skip to canvas to Skip to content
Sidnioulz Jan 9, 2026
248adab
Address PR feedback
Sidnioulz Jan 12, 2026
49f0abc
Mention landmark shortcuts in docs and UI
Sidnioulz Jan 12, 2026
7ccec3d
Update docs/get-started/browse-stories.mdx
Sidnioulz Jan 12, 2026
276e724
Update code/core/src/manager/settings/shortcuts.tsx
Sidnioulz Jan 12, 2026
e6921f6
Refactor ghost stories channel initialization
yannbf Jan 13, 2026
283d093
Merge pull request #33457 from storybookjs/sidnioulz/issue-32271
ndelangen Jan 13, 2026
4401664
Merge branch 'next' into yeonny0723/issue-33433
ndelangen Jan 13, 2026
5bfcbe7
add packageJson.type to telemetry
ndelangen Jan 13, 2026
cb720bd
categorize vite and vitest run errors
yannbf Jan 13, 2026
1a755c1
various fixes
yannbf Jan 13, 2026
0d9660c
fix types
yannbf Jan 13, 2026
090864b
handle no tests scenario
yannbf Jan 13, 2026
4f4da7c
Merge pull request #33504 from Yeonny0723/yeonny0723/issue-33433
ndelangen Jan 13, 2026
1f6a898
detect free port when running dev during initiate, to ensure the serv…
ndelangen Jan 14, 2026
f91b2e0
re-use logic from core
ndelangen Jan 14, 2026
7781dea
append after possible double dash
ndelangen Jan 14, 2026
a8906a2
Merge pull request #33532 from storybookjs/norbert/use-open-port-on-i…
ndelangen Jan 14, 2026
ce33702
Merge pull request #33525 from storybookjs/norbert/telemetry-add-pack…
ndelangen Jan 14, 2026
2840142
Merge pull request #33520 from storybookjs/yann/adjust-ghost-stories-run
yannbf Jan 14, 2026
9c4b46d
Refactor component file path handling in internal_getArgTypesData to …
yannbf Jan 14, 2026
b3cfc15
Merge pull request #33536 from storybookjs/yann/fix-ghost-stories-flow
yannbf Jan 14, 2026
898104f
Write changelog for 10.2.0-alpha.18 [skip ci]
storybook-bot Jan 14, 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
9 changes: 9 additions & 0 deletions CHANGELOG.prerelease.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 10.2.0-alpha.18

- Build: Fix `ejslint` execution path in lint-staged - [#33504](https://github.com/storybookjs/storybook/pull/33504), thanks @Yeonny0723!
- CLI: Detect free port when running dev during initiate - [#33532](https://github.com/storybookjs/storybook/pull/33532), thanks @ndelangen!
- Core: Improve path handling in arg types data extraction - [#33536](https://github.com/storybookjs/storybook/pull/33536), thanks @yannbf!
- Core: Refactor channel initialization - [#33520](https://github.com/storybookjs/storybook/pull/33520), thanks @yannbf!
- Telemetry: Add `packageJson.type` - [#33525](https://github.com/storybookjs/storybook/pull/33525), thanks @ndelangen!
- UI: Improve landmark navigation - [#33457](https://github.com/storybookjs/storybook/pull/33457), thanks @Sidnioulz!

## 10.2.0-alpha.17

- Core: Improve the story generation experience - [#33259](https://github.com/storybookjs/storybook/pull/33259), thanks @yannbf!
Expand Down
1 change: 1 addition & 0 deletions code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
"@radix-ui/react-scroll-area": "1.2.0-rc.7",
"@radix-ui/react-slot": "^1.0.2",
"@react-aria/interactions": "^3.25.5",
"@react-aria/landmark": "^3.0.8",
"@react-aria/overlays": "^3.29.1",
"@react-aria/tabs": "^3.10.7",
"@react-aria/toolbar": "3.0.0-beta.20",
Expand Down
7 changes: 4 additions & 3 deletions code/core/src/components/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { type ComponentProps, forwardRef } from 'react';
import React, { type ComponentProps, type DOMAttributes, forwardRef } from 'react';

import type { CSSObject, color } from 'storybook/theming';
import { keyframes, styled } from 'storybook/theming';
Expand Down Expand Up @@ -92,15 +92,16 @@ const CardOutline = styled.div<{
interface CardProps extends ComponentProps<typeof CardContent> {
outlineAnimation?: 'none' | 'rainbow' | 'spin';
outlineColor?: keyof typeof color;
outlineAttrs?: DOMAttributes<HTMLDivElement>;
}

export const Card = Object.assign(
forwardRef<HTMLDivElement, CardProps>(function Card(
{ outlineAnimation = 'none', outlineColor, ...props },
{ outlineAnimation = 'none', outlineColor, outlineAttrs: outlineAttrs = {}, ...props },
ref
) {
return (
<CardOutline animation={outlineAnimation} color={outlineColor} ref={ref}>
<CardOutline animation={outlineAnimation} color={outlineColor} ref={ref} {...outlineAttrs}>
<CardContent {...props} />
</CardOutline>
);
Expand Down
3 changes: 2 additions & 1 deletion code/core/src/components/components/Form/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ export const styles = (({ theme }: { theme: StorybookTheme }) => ({
},
},

'&[disabled]': {
'&[disabled], &[aria-disabled="true"]': {
background: theme.base === 'light' ? theme.color.lighter : 'transparent',
cursor: 'not-allowed',
},

'&:-webkit-autofill': { WebkitBoxShadow: `0 0 0 3em ${theme.color.lightest} inset` },
Expand Down
2 changes: 2 additions & 0 deletions code/core/src/core-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ export {
fullTestProviderStore as internal_fullTestProviderStore,
universalTestProviderStore as internal_universalTestProviderStore,
} from './stores/test-provider';

export { getServerPort } from './utils/server-address';
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ describe('ghostStoriesChannel', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFile.mockResolvedValue(
JSON.stringify({
success: true,
numTotalTests: 2,
numPassedTests: 2,
numFailedTests: 0,
Expand Down Expand Up @@ -186,7 +185,6 @@ describe('ghostStoriesChannel', () => {

// Telemetry is called with the correct data
expect(mockTelemetry.telemetry).toHaveBeenCalledWith('ghost-stories', {
success: true,
stats: {
globMatchCount: 10,
candidateAnalysisDuration: expect.any(Number),
Expand All @@ -199,8 +197,6 @@ describe('ghostStoriesChannel', () => {
results: {
total: 2,
passed: 2,
failed: 0,
failureRate: 0,
successRate: 1,
successRateWithoutEmptyRender: 1,
categorizedErrors: expect.any(Object),
Expand Down Expand Up @@ -238,7 +234,6 @@ describe('ghostStoriesChannel', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFile.mockResolvedValue(
JSON.stringify({
success: false,
numTotalTests: 2,
numPassedTests: 0,
numFailedTests: 2,
Expand Down Expand Up @@ -291,7 +286,6 @@ describe('ghostStoriesChannel', () => {
expect(mockTelemetry.telemetry).toHaveBeenCalledWith(
'ghost-stories',
expect.objectContaining({
success: false,
stats: {
globMatchCount: 10,
candidateAnalysisDuration: expect.any(Number),
Expand All @@ -304,8 +298,6 @@ describe('ghostStoriesChannel', () => {
results: expect.objectContaining({
total: 2,
passed: 0,
failed: 2,
failureRate: 1,
successRate: 0,
// categorizedErrors is now an object with categories as keys
categorizedErrors: expect.any(Object),
Expand Down Expand Up @@ -437,8 +429,7 @@ describe('ghostStoriesChannel', () => {

expect(mockStoryGeneration.getComponentCandidates).toHaveBeenCalled();
expect(mockTelemetry.telemetry).toHaveBeenCalledWith('ghost-stories', {
success: false,
error: 'Failed to analyze components',
runError: 'Failed to analyze components',
stats: {
globMatchCount: 0,
candidateAnalysisDuration: 0,
Expand Down Expand Up @@ -478,8 +469,7 @@ describe('ghostStoriesChannel', () => {

expect(mockStoryGeneration.getComponentCandidates).toHaveBeenCalled();
expect(mockTelemetry.telemetry).toHaveBeenCalledWith('ghost-stories', {
success: false,
error: 'No candidates found',
runError: 'No candidates found',
stats: {
globMatchCount: 5,
candidateAnalysisDuration: expect.any(Number),
Expand Down Expand Up @@ -523,8 +513,7 @@ describe('ghostStoriesChannel', () => {
});

expect(mockTelemetry.telemetry).toHaveBeenCalledWith('ghost-stories', {
success: false,
error: 'JSON report not found',
runError: 'JSON report not found',
stats: {
globMatchCount: 5,
candidateAnalysisDuration: 0,
Expand Down Expand Up @@ -561,7 +550,6 @@ describe('ghostStoriesChannel', () => {
mockFs.existsSync.mockReturnValue(true);
mockFs.readFile.mockResolvedValue(
JSON.stringify({
success: false,
numTotalTests: 2,
numPassedTests: 0,
numFailedTests: 2,
Expand All @@ -578,8 +566,7 @@ describe('ghostStoriesChannel', () => {
});

expect(mockTelemetry.telemetry).toHaveBeenCalledWith('ghost-stories', {
success: false,
error: 'Startup Error',
runError: 'Startup Error',
stats: {
globMatchCount: 5,
candidateAnalysisDuration: 0,
Expand All @@ -605,8 +592,7 @@ describe('ghostStoriesChannel', () => {
});

expect(mockTelemetry.telemetry).toHaveBeenCalledWith('ghost-stories', {
success: false,
error: 'Cache error',
runError: 'Unknown error during ghost run',
stats: {},
});
});
Expand Down
33 changes: 19 additions & 14 deletions code/core/src/core-server/server-channel/ghost-stories-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ export function initGhostStoriesChannel(
try {
const ghostRunStart = Date.now();
const lastEvents = await getLastEvents();
const sessionId = await getSessionId();
const lastInit = lastEvents?.init;
const lastGhostStoriesRun = lastEvents?.['ghost-stories'];
if (!lastEvents || !lastInit) {
return;
}

const sessionId = await getSessionId();
const lastGhostStoriesRun = lastEvents['ghost-stories'];
if (
lastGhostStoriesRun ||
(lastInit?.body?.sessionId && lastInit?.body?.sessionId !== sessionId)
(lastInit.body?.sessionId && lastInit.body.sessionId !== sessionId)
) {
return;
}
Expand Down Expand Up @@ -70,19 +74,17 @@ export function initGhostStoriesChannel(
if (candidatesResult.error) {
stats.totalRunDuration = Date.now() - ghostRunStart;
telemetry('ghost-stories', {
success: false,
error: candidatesResult.error,
stats,
runError: candidatesResult.error,
});
return;
}

if (candidatesResult.candidates.length === 0) {
stats.totalRunDuration = Date.now() - ghostRunStart;
telemetry('ghost-stories', {
success: false,
error: 'No candidates found',
stats,
runError: 'No candidates found',
});
return;
}
Expand All @@ -92,19 +94,22 @@ export function initGhostStoriesChannel(
const testRunResult = await runStoryTests(candidatesResult.candidates);
stats.totalRunDuration = Date.now() - ghostRunStart;
stats.testRunDuration = testRunResult.duration;
if (testRunResult.runError) {
telemetry('ghost-stories', {
stats,
runError: testRunResult.runError,
});
return;
}

telemetry('ghost-stories', {
...(testRunResult.error !== undefined ? { error: testRunResult.error } : {}),
success: testRunResult.success,
stats,
results: testRunResult.summary,
});
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);

} catch {
telemetry('ghost-stories', {
success: false,
error: errorMessage,
stats,
runError: 'Unknown error during ghost run',
});
} finally {
// we don't currently do anything with this, but will be useful in the future
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ export async function getComponentCandidates({
candidates,
globMatchCount,
};
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
} catch {
return {
candidates: [],
error: 'Failed to find candidates',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,12 @@ describe('parse-vitest-report', () => {

const result = parseVitestResults(mockVitestResults);

expect(result.success).toBe(true);
expect(result.summary).toEqual({
total: 3,
passed: 3,
passedButEmptyRender: 0,
failed: 0,
successRate: 1.0,
successRateWithoutEmptyRender: 1.0,
failureRate: 0.0,
uniqueErrorCount: 0,
categorizedErrors: {},
});
Expand Down Expand Up @@ -88,12 +85,9 @@ describe('parse-vitest-report', () => {

const result = parseVitestResults(mockVitestResults);

expect(result.success).toBe(false);
expect(result.summary?.total).toBe(3);
expect(result.summary?.passed).toBe(1);
expect(result.summary?.failed).toBe(2);
expect(result.summary?.successRate).toBe(0.33);
expect(result.summary?.failureRate).toBe(0.67);
expect(result.summary?.uniqueErrorCount).toBe(2);
});

Expand Down Expand Up @@ -121,11 +115,18 @@ describe('parse-vitest-report', () => {
{
fullName: 'Story3',
status: 'failed',
failureMessages: ['Error: Module not found: react-router\n at import statement'],
failureMessages: [
'Error: Cannot read property "x" of undefined\n at /deps/styled-components.js:1168:14',
],
},
{
fullName: 'Story4',
status: 'failed',
failureMessages: ['Error: Module not found: react-router\n at import statement'],
},
{
fullName: 'Story5',
status: 'failed',
failureMessages: ['Error: Invalid hook call\n at useEffect'],
},
],
Expand All @@ -135,25 +136,23 @@ describe('parse-vitest-report', () => {

const result = parseVitestResults(mockVitestResults);

expect(result.success).toBe(false);
expect(result.summary?.total).toBe(4);
expect(result.summary?.passed).toBe(1);
expect(result.summary?.failed).toBe(3);
expect(result.summary?.uniqueErrorCount).toBe(3);
expect(result.summary?.categorizedErrors).toEqual({
HOOK_USAGE_ERROR: {
uniqueCount: 1,
count: 1,
description: 'React hook was used incorrectly',
matchedDependencies: [],
},
MISSING_THEME_PROVIDER: {
count: 1,
description: 'Component attempted to access theme values without a theme provider',
uniqueCount: 1,
count: 2,
matchedDependencies: ['styled-components'],
},
MODULE_IMPORT_ERROR: {
uniqueCount: 1,
count: 1,
description: 'A required dependency could not be resolved',
matchedDependencies: [],
},
});
Expand Down Expand Up @@ -246,7 +245,6 @@ describe('parse-vitest-report', () => {

expect(result.summary?.total).toBe(4);
expect(result.summary?.passed).toBe(3);
expect(result.summary?.failed).toBe(1);
});

it('should handle zero total tests', () => {
Expand All @@ -262,7 +260,6 @@ describe('parse-vitest-report', () => {

expect(result.summary?.total).toBe(0);
expect(result.summary?.successRate).toBe(0);
expect(result.summary?.failureRate).toBe(0);
});
});
});
Loading