Skip to content

Assignment/issue 21524#34899

Open
beoziel wants to merge 7 commits into
storybookjs:nextfrom
RuBrock:assignment/issue-21524
Open

Assignment/issue 21524#34899
beoziel wants to merge 7 commits into
storybookjs:nextfrom
RuBrock:assignment/issue-21524

Conversation

@beoziel

@beoziel beoziel commented May 25, 2026

Copy link
Copy Markdown

Closes #

What I did

The stories field in .storybook/main.ts accepts custom resolver functions for advanced story discovery. When this issue was filed, Storybook already ran async resolvers correctly at runtime — the problem was that the TypeScript definition of StoriesEntry didn't allow them.

stories: async (list) => {
  const extraStories = await findStories();
  return [...list, ...extraStories];
}

So the type system and the actual runtime were out of sync: async functions worked fine when executed, but TypeScript flagged them as errors. Users had to work around it with type assertions or just ignore the compiler noise.

This is a type system/API consistency bug, not a runtime issue.

code/core/src/types/modules/core-common.ts

This file contains Storybook's shared configuration type definitions, including the StoriesEntry type used by .storybook/main.ts.

Changes made:

  • Updated the type definition to support asynchronous resolver functions.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

Release Notes

  • New Features

    • Stories configuration now supports async functions, enabling flexible and dynamic story resolution.
  • Documentation

    • Updated README with group members list and issues tracking information.
    • Added detailed issue reports documenting framework architecture improvements and design patterns.
  • Improvements

    • Enhanced error handling in UniversalStore with dedicated error types and improved categorization for clearer debugging.

Review Change Stack

@github-actions

Copy link
Copy Markdown
Contributor
Fails
🚫

PR is not labeled with one of: ["cleanup","BREAKING CHANGE","feature request","bug","documentation","maintenance","build","dependencies"]

🚫

PR is not labeled with one of: ["ci:normal","ci:merged","ci:daily","ci:docs"]

🚫 PR title must be in the format of "Area: Summary", With both Area and Summary starting with a capital letter Good examples: - "Docs: Describe Canvas Doc Block" - "Svelte: Support Svelte v4" Bad examples: - "add new api docs" - "fix: Svelte 4 support" - "Vue: improve docs"
🚫 The "#### Manual testing" section must be filled in. Please describe how to test the changes you've made, step by step, so that reviewers can confirm your PR works as intended.

Generated by 🚫 dangerJS against e59298e

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements UniversalStore error categorization for telemetry tracking and extends configuration support for async story entry resolution. It adds a dedicated error hierarchy in the universal-store module, integrates those errors into the manager error taxonomy, updates UniversalStore to throw them instead of generic TypeErrors, and broadens the stories configuration type to accept async functions.

Changes

UniversalStore Error Categorization

Layer / File(s) Summary
Error class hierarchy definition
code/core/src/shared/universal-store/errors.ts
Introduces UniversalStoreError base class extending StorybookError with a standardized MANAGER_UNIVERSAL-STORE category. Adds five concrete error subclasses: UniversalStoreFollowerTimeoutError (code 1, includes follower id), UniversalStoreNotConstructableError (code 1001), UniversalStoreIdRequiredError (code 1002), UniversalStoreNotReadyError (code 1003, includes id and action), and UniversalStoreMissingSubscribeArgumentError (code 1004, includes id).
Error category and registry integration
code/core/src/manager-errors.ts, code/core/src/manager/globals/exports.ts
Adds MANAGER_UNIVERSAL_STORE category to the error enum, re-exports all five UniversalStore error classes, and registers them in the global dependency export map for manager accessibility.
UniversalStore implementation and tests
code/core/src/shared/universal-store/index.ts, code/core/src/shared/universal-store/index.test.ts
Updates UniversalStore to throw the new error types at six failure points: constructor guard, create() ID validation, setState() and send() readiness checks, subscribe() argument validation, and follower timeout during synchronization. Updates four test snapshots to expect the new structured error messages with error codes.

Async Stories Configuration Support

Layer / File(s) Summary
Type definition
code/core/src/types/modules/core-common.ts
Updates StorybookConfigRaw.stories type to accept either StoriesEntry[] or () => Promise<StoriesEntry[]>, enabling async entry resolution at configuration time.
Preset and CLI implementation
code/core/src/core-server/presets/common-override-preset.ts, code/lib/cli-storybook/src/util.ts
Updates the common-override-preset stories handler to resolve async entries via an intermediate resolvedEntries variable before MDX processing and return. Updates CLI utility getStoriesPathsFromConfig to resolve async stories() before passing to normalizeStories.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • storybookjs/storybook#34592: Updates UniversalStore timeout error handling to use dedicated UniversalStoreFollowerTimeoutError with telemetry categorization via the error registry and shared error class definitions.
✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch assignment/issue-21524

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Warning

⚠️ This pull request might be slop. It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/lib/cli-storybook/src/util.ts (1)

802-813: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move the stories.length empty-check to after resolving stories

code/lib/cli-storybook/src/util.ts currently returns [] based on stories.length before the typeof stories === 'function' ? await stories() : stories resolution runs; when stories is a function, stories.length is the function arity, so a zero-arg resolver gets skipped.

Suggested fix
-  if (stories.length === 0) {
-    return [];
-  }
-
-const resolvedStories = typeof stories === 'function' 
-  ? await stories() 
-  : stories;
+const resolvedStories = typeof stories === 'function'
+  ? await stories()
+  : stories;
+
+  if (resolvedStories.length === 0) {
+    return [];
+  }
 
 const normalizedStories = normalizeStories(resolvedStories, {
    configDir,
     workingDir,
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/lib/cli-storybook/src/util.ts` around lines 802 - 813, The empty-check
currently uses stories.length before resolving when stories may be a resolver
function; move the guard to after resolving (i.e., after computing
resolvedStories via const resolvedStories = typeof stories === 'function' ?
await stories() : stories) and check resolvedStories.length instead; then return
[] if resolvedStories.length === 0 and proceed to call
normalizeStories(resolvedStories, { configDir, workingDir }) so normalizeStories
always receives the actual resolved array.
🧹 Nitpick comments (3)
code/core/src/shared/universal-store/index.ts (1)

4-10: ⚡ Quick win

Use explicit .ts extension for the new relative import.

Please update this import path to follow repository conventions.

Proposed change
 } from './errors';
+} from './errors.ts';

As per coding guidelines, code/**/*.{ts,tsx}: "Prefer explicit file extensions for relative code imports and exports such as ./foo.ts or ./bar.tsx when the target is another TS/JS module in this repository; keep framework-specific component imports like .vue and .svelte in their expected form".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/shared/universal-store/index.ts` around lines 4 - 10, The
import of error symbols (UniversalStoreFollowerTimeoutError,
UniversalStoreIdRequiredError, UniversalStoreMissingSubscribeArgumentError,
UniversalStoreNotConstructableError, UniversalStoreNotReadyError) is using a
bare relative path; update the import source from './errors' to './errors.ts' to
follow the repository convention of explicit .ts extensions for relative
TypeScript module imports, keeping the imported symbol list unchanged in the
import statement in index.ts.
code/core/src/shared/universal-store/errors.ts (1)

1-1: ⚡ Quick win

Use explicit .ts extension for this relative import.

Please align this import with the repository rule for explicit relative module extensions.

Proposed change
-import { StorybookError } from '../../storybook-error';
+import { StorybookError } from '../../storybook-error.ts';

As per coding guidelines, code/**/*.{ts,tsx}: "Prefer explicit file extensions for relative code imports and exports such as ./foo.ts or ./bar.tsx when the target is another TS/JS module in this repository; keep framework-specific component imports like .vue and .svelte in their expected form".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/shared/universal-store/errors.ts` at line 1, Update the
relative import for StorybookError in errors.ts to use an explicit .ts
extension; locate the import statement "import { StorybookError } from
'../../storybook-error';" and change it to reference '../../storybook-error.ts'
so it follows the repository rule for explicit relative TypeScript module
extensions.
code/core/src/manager-errors.ts (1)

51-57: ⚡ Quick win

Use explicit .ts extension in this relative re-export path.

Please make this re-export path explicit to match repository TS import/export conventions.

Proposed change
 } from './shared/universal-store/errors';
+} from './shared/universal-store/errors.ts';

As per coding guidelines, code/**/*.{ts,tsx}: "Prefer explicit file extensions for relative code imports and exports such as ./foo.ts or ./bar.tsx when the target is another TS/JS module in this repository; keep framework-specific component imports like .vue and .svelte in their expected form".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/manager-errors.ts` around lines 51 - 57, The re-export
currently omits the TypeScript file extension; update the export statement that
re-exports UniversalStoreFollowerTimeoutError, UniversalStoreIdRequiredError,
UniversalStoreMissingSubscribeArgumentError,
UniversalStoreNotConstructableError, and UniversalStoreNotReadyError so that the
module path includes the explicit .ts extension (replace
'./shared/universal-store/errors' with './shared/universal-store/errors.ts') and
keep the exported symbol list unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/shared/universal-store/errors.ts`:
- Around line 43-48: The error message in UniversalStoreNotReadyError's
constructor references a non-existent API (readyPromise); update the message to
point users to the correct readiness API (untilReady). Modify the message string
in the UniversalStoreNotReadyError constructor so it suggests checking
store.status or awaiting store.untilReady() (e.g., "You can get the current
status with store.status, or await store.untilReady() to wait for the store to
be ready before sending events."), leaving the rest of the error object (name,
code, data) unchanged.

In `@code/frameworks/nextjs-vite/src/index.ts`:
- Line 8: The import of storybookNextJsPlugin in index.ts uses a relative path
without an explicit extension; update the import statement for
storybookNextJsPlugin to reference the TypeScript file with its .ts extension
(e.g., './vite-plugin.ts') so the module resolver and coding guidelines are
satisfied and the symbol storybookNextJsPlugin is imported from the explicit
file.

---

Outside diff comments:
In `@code/lib/cli-storybook/src/util.ts`:
- Around line 802-813: The empty-check currently uses stories.length before
resolving when stories may be a resolver function; move the guard to after
resolving (i.e., after computing resolvedStories via const resolvedStories =
typeof stories === 'function' ? await stories() : stories) and check
resolvedStories.length instead; then return [] if resolvedStories.length === 0
and proceed to call normalizeStories(resolvedStories, { configDir, workingDir })
so normalizeStories always receives the actual resolved array.

---

Nitpick comments:
In `@code/core/src/manager-errors.ts`:
- Around line 51-57: The re-export currently omits the TypeScript file
extension; update the export statement that re-exports
UniversalStoreFollowerTimeoutError, UniversalStoreIdRequiredError,
UniversalStoreMissingSubscribeArgumentError,
UniversalStoreNotConstructableError, and UniversalStoreNotReadyError so that the
module path includes the explicit .ts extension (replace
'./shared/universal-store/errors' with './shared/universal-store/errors.ts') and
keep the exported symbol list unchanged.

In `@code/core/src/shared/universal-store/errors.ts`:
- Line 1: Update the relative import for StorybookError in errors.ts to use an
explicit .ts extension; locate the import statement "import { StorybookError }
from '../../storybook-error';" and change it to reference
'../../storybook-error.ts' so it follows the repository rule for explicit
relative TypeScript module extensions.

In `@code/core/src/shared/universal-store/index.ts`:
- Around line 4-10: The import of error symbols
(UniversalStoreFollowerTimeoutError, UniversalStoreIdRequiredError,
UniversalStoreMissingSubscribeArgumentError,
UniversalStoreNotConstructableError, UniversalStoreNotReadyError) is using a
bare relative path; update the import source from './errors' to './errors.ts' to
follow the repository convention of explicit .ts extensions for relative
TypeScript module imports, keeping the imported symbol list unchanged in the
import statement in index.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aebb4891-af73-4ef7-a30a-65d79dca9b28

📥 Commits

Reviewing files that changed from the base of the PR and between 1411339 and e59298e.

📒 Files selected for processing (12)
  • README.md
  • code/core/src/core-server/presets/common-override-preset.ts
  • code/core/src/manager-errors.ts
  • code/core/src/manager/globals/exports.ts
  • code/core/src/shared/universal-store/errors.ts
  • code/core/src/shared/universal-store/index.test.ts
  • code/core/src/shared/universal-store/index.ts
  • code/core/src/types/modules/core-common.ts
  • code/frameworks/nextjs-vite/src/index.ts
  • code/lib/cli-storybook/src/util.ts
  • issue-34258-report.md
  • issue-34566-report.md

Comment on lines +43 to +48
constructor(public data: { id: string; action: 'set state' | 'send event' }) {
super({
name: 'UniversalStoreNotReadyError',
code: 1003,
message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.`,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message points to a non-existent readiness API.

UniversalStore exposes untilReady(), but this message tells users to await store.readyPromise, which is misleading and not available.

Proposed change
-      message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.`,
+      message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.untilReady() to wait for the store to be ready before sending events.`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constructor(public data: { id: string; action: 'set state' | 'send event' }) {
super({
name: 'UniversalStoreNotReadyError',
code: 1003,
message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.`,
});
constructor(public data: { id: string; action: 'set state' | 'send event' }) {
super({
name: 'UniversalStoreNotReadyError',
code: 1003,
message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.untilReady() to wait for the store to be ready before sending events.`,
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/shared/universal-store/errors.ts` around lines 43 - 48, The
error message in UniversalStoreNotReadyError's constructor references a
non-existent API (readyPromise); update the message to point users to the
correct readiness API (untilReady). Modify the message string in the
UniversalStoreNotReadyError constructor so it suggests checking store.status or
awaiting store.untilReady() (e.g., "You can get the current status with
store.status, or await store.untilReady() to wait for the store to be ready
before sending events."), leaving the rest of the error object (name, code,
data) unchanged.

import type { ReactTypes } from '@storybook/react';

import type vitePluginStorybookNextJs from 'vite-plugin-storybook-nextjs';
import type { storybookNextJsPlugin } from './vite-plugin';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit file extension to relative import.

The import path should include an explicit .ts extension. As per coding guidelines, relative code imports should use explicit file extensions like ./foo.ts or ./bar.tsx.

📝 Proposed fix
-import type { storybookNextJsPlugin } from './vite-plugin';
+import type { storybookNextJsPlugin } from './vite-plugin/index.ts';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/frameworks/nextjs-vite/src/index.ts` at line 8, The import of
storybookNextJsPlugin in index.ts uses a relative path without an explicit
extension; update the import statement for storybookNextJsPlugin to reference
the TypeScript file with its .ts extension (e.g., './vite-plugin.ts') so the
module resolver and coding guidelines are satisfied and the symbol
storybookNextJsPlugin is imported from the explicit file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Empathy Queue (prioritized)

Development

Successfully merging this pull request may close these issues.

3 participants