Core: Add ref-based components manifest index backed by docgen open service#35063
Conversation
…vice. Writes v1 manifest rows with summary fields and nested docgen.$ref targets, runs open-service static builds before manifests, gates manifest I/O on componentsManifest, and aligns docgen payloads with the React manifest shape. Co-authored-by: Cursor <cursoragent@cursor.com>
Manual QA reportExecuted the manual testing steps from the PR description against the internal Storybook ( Environment
Pass A —
|
| Check | Result | Evidence |
|---|---|---|
| Dev server starts | PASS | http://localhost:6006/ → 200 |
GET /manifests/components.html |
PASS | HTTP 200, ~2.6MB; Components Manifest debugger UI |
GET /manifests/components.json (dev) |
PASS | HTTP 404 (expected with docgen server) |
| Static build | PASS | yarn storybook:ui:build succeeded |
Built components.json |
PASS | v: 1; ref index with nested docgen.$ref pointers (~275 refs); not full inlined payloads |
Built components.html |
PASS | Exists, substantive HTML |
Pass B — experimentalDocgenServer: false
| Check | Result | Evidence |
|---|---|---|
| Dev server starts | PASS | http://localhost:6006/ → 200 |
GET /manifests/components.json (dev) |
PASS | HTTP 200; v: 0; inline objects (path, stories, …); no ref index |
GET /manifests/components.html (dev) |
PASS | HTTP 200; inline manifest rendering |
| Static build | PASS | yarn storybook:ui:build succeeded |
Built components.json |
PASS | v: 0; full inline component objects |
Built components.html |
PASS | Substantive HTML; legacy inline rendering |
Overall: PASS
Notes:
- Pass A dev HTML validation relied on debugger UI markup plus dev JSON 404 and static
v: 1ref index (page does not expose rawv: 1). - Pass B build overwrote Pass A static artifacts (sequential QA runs).
- Legacy manifest path works with docgen server disabled.
| input.id in ctx.self.state.components ? ctx.self.state.components[input.id] : undefined, | ||
| staticPath: (input) => docgenQueryStaticPath(input.id), | ||
| }, | ||
| getDocgenForAllComponents: { |
There was a problem hiding this comment.
this is used only by the manifest debugger, as it needs docgen for all components to be able to render the debugger. is not used in any other hot paths.
There was a problem hiding this comment.
the changes here are to make the docgen service format conform 100 % with the existing manifest format.
Remove the unused JSON-Ref resolver/dereferencer (no in-repo consumer), simplify the snapshot loader to a direct typed read, and rename json-references.ts to components-ref-manifest.ts. Dedup the per-operation story-index walk in manifests.ts by threading manifest entries through, and inline the trivial fs wrappers. Co-authored-by: Cursor <cursoragent@cursor.com>
Read docgen payloads once from the static service snapshots and reuse them for both the ref-based components.json index and the HTML debugger. Dev still renders from the live docgen service. Co-authored-by: Cursor <cursoragent@cursor.com>
Package BenchmarksCommit: No significant changes detected, all good. 👏 |
Apply oxfmt fixes and align ReactComponentManifest with DocgenPayload via index signatures. Narrow reactComponentMeta in buildDocgen tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Manual QA report (re-run after build HTML / CI fixes)Re-executed the PR manual testing steps after Environment
Pass A —
|
| Check | Result | Evidence |
|---|---|---|
| Dev server | PASS | localhost:6006 ready |
GET /manifests/components.html |
PASS | HTTP 200, ~2.6MB; Components Manifest debugger UI |
GET /manifests/components.json (dev) |
PASS | HTTP 404 (expected) |
| Static build | PASS | yarn storybook:ui:build succeeded |
Built components.json |
PASS | v: 1; 275 components; nested docgen.$ref index |
Built components.html |
PASS | ~2.6MB; substantive debugger HTML |
Pass B — experimentalDocgenServer: false
| Check | Result | Evidence |
|---|---|---|
Dev components.json |
PASS | HTTP 200; v: 0; inline legacy manifest; no docgen.$ref |
Dev components.html |
PASS | HTTP 200; ~2.8MB |
Built components.json |
PASS | v: 0; full inline payloads |
Built components.html |
PASS | Legacy inline rendering |
Build HTML regression check
| Check | Result | Notes |
|---|---|---|
| Pass A dev vs build HTML size | PASS | 2,615,921 → 2,618,387 bytes (+0.09%) |
| Pass B dev vs build HTML size | PASS | Stable (~2.8MB) |
| Build log docgen re-extraction | PASS | No docgen extract/re-extract noise in build output |
Overall: PASS
Legacy manifest path still works with docgen server off. Pass B build overwrote Pass A static artifacts (sequential QA runs).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR refactors the docgen service and manifest generation pipeline to support id-based component selection, ref-based manifest outputs, and experimental docgen-server flows. Changes include void-input query ergonomics, updated docgen contracts, React extraction modules, ref-based manifest artifacts, and updated manifest routing with comprehensive test coverage. ChangesDocgen, React extraction, and manifests
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/renderers/react/src/componentManifest/componentMetaManagerSingleton.ts`:
- Around line 19-27: The catch in getSharedComponentMetaManager() currently
masks all init failures as “TypeScript not available”; change it to detect a
genuine missing-module error (e.g., check error instanceof Error and inspect
error.code === 'MODULE_NOT_FOUND' or error.message includes "typescript") and
log the original Error details for non-missing-module failures (use logger.error
with error.stack/message) while still returning undefined in both cases so the
unawaited init and docgen fallback behavior remain unchanged; keep
ComponentMetaManager creation path and only treat the import failure as the
"missing TypeScript" debug log.
In `@code/renderers/react/src/docgen/buildDocgen.test.ts`:
- Around line 34-40: Tests currently create real temp dirs/files via
createTempDir/writeFiles/cleanup and helpers use node:fs functions; switch to an
in-memory fs by importing memfs (vol) and use vol.fromJSON(...) in a beforeEach
to populate fixtures and vol.reset() to clear state, update the afterEach to
call vol.reset() instead of cleanup and remove or adapt
createTempDir/writeFiles/cleanup usages so they write into memfs (or replace
them with a helper that constructs the same JSON map passed to vol.fromJSON),
and ensure tests import { vol } from 'memfs' and no longer rely on node:fs
helper side effects (references: afterEach, beforeEach, createTempDir,
writeFiles, cleanup, vol.fromJSON, vol.reset; also address helper functions that
call mkdirSync/writeFileSync/rmSync).
🪄 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: 581188ea-8831-4afb-b768-18cd20f844df
📒 Files selected for processing (33)
AGENTS.mdcode/.storybook/main.tscode/addons/docs/src/docgen.tscode/core/src/common/index.tscode/core/src/common/utils/select-component-entry.test.tscode/core/src/common/utils/select-component-entry.tscode/core/src/core-server/build-static.tscode/core/src/core-server/presets/common-preset.tscode/core/src/core-server/utils/manifests/components-ref-manifest.test.tscode/core/src/core-server/utils/manifests/components-ref-manifest.tscode/core/src/core-server/utils/manifests/manifests.test.tscode/core/src/core-server/utils/manifests/manifests.tscode/core/src/server-errors.tscode/core/src/shared/open-service/index.test-d.tscode/core/src/shared/open-service/service-runtime.test.tscode/core/src/shared/open-service/service-runtime.tscode/core/src/shared/open-service/services/docgen/definition.tscode/core/src/shared/open-service/services/docgen/paths.test.tscode/core/src/shared/open-service/services/docgen/paths.tscode/core/src/shared/open-service/services/docgen/server.test.tscode/core/src/shared/open-service/services/docgen/server.tscode/core/src/shared/open-service/services/docgen/types.tscode/core/src/shared/open-service/types.tscode/core/src/types/modules/core-common.tscode/renderers/react/src/componentManifest/buildReactComponentDocgen.tscode/renderers/react/src/componentManifest/componentMeta/componentMetaExtractor.test-helpers.tscode/renderers/react/src/componentManifest/componentMetaManagerSingleton.tscode/renderers/react/src/componentManifest/extractComponentDescription.tscode/renderers/react/src/componentManifest/generator.tscode/renderers/react/src/componentManifest/resolveComponents.tscode/renderers/react/src/docgen/buildDocgen.test.tscode/renderers/react/src/docgen/buildDocgen.tscode/renderers/react/src/docgen/preset.ts
💤 Files with no reviewable changes (1)
- code/addons/docs/src/docgen.ts
| try { | ||
| const ts = await import('typescript'); | ||
| return new ComponentMetaManager(ts); | ||
| } catch { | ||
| logger.debug( | ||
| '[reactComponentMeta] TypeScript not available, skipping component meta extraction.' | ||
| ); | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="code/renderers/react/src/componentManifest/componentMetaManagerSingleton.ts"
echo "== File =="
test -f "$FILE" && echo "Found $FILE" || { echo "Missing $FILE"; exit 1; }
echo
echo "== Relevant section (lines 1-120) =="
nl -ba "$FILE" | sed -n '1,140p'
echo
echo "== Search for ComponentMetaManager instantiation and catch usage =="
rg -n "ComponentMetaManager|import\\('typescript'\\)|TypeScript not available|catch \\{" "$FILE"Repository: storybookjs/storybook
Length of output: 258
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="code/renderers/react/src/componentManifest/componentMetaManagerSingleton.ts"
echo "== Relevant section (approx lines 1-140) =="
awk '{print NR ":" $0}' "$FILE" | sed -n '1,160p'
echo
echo "== Search within file =="
rg -n "ComponentMetaManager|import\\('typescript'\\)|TypeScript not available|catch \\{" "$FILE" || true
echo
echo "== Show surrounding try/catch =="
rg -n "try \\{|catch \\{" "$FILE" || trueRepository: storybookjs/storybook
Length of output: 2436
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the ComponentMetaManager implementation
FILE_MANAGER="code/renderers/react/src/componentManifest/componentMeta/ComponentMetaManager.ts"
test -f "$FILE_MANAGER" && echo "Found $FILE_MANAGER" || { echo "Missing $FILE_MANAGER"; exit 1; }
echo "== Constructor / init section (lines 1-220) =="
awk '{print NR ":" $0}' "$FILE_MANAGER" | sed -n '1,220p'
echo
echo "== Search for constructor throwing / TypeScript usage =="
rg -n "constructor\\(|new ComponentMetaManager|dispose\\(|import\\('typescript'\\)|typescript" "$FILE_MANAGER" || trueRepository: storybookjs/storybook
Length of output: 14442
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="code/renderers/react/src/componentManifest/componentMetaManagerSingleton.ts"
rg -n "getSharedComponentMetaManager\\(" -S code/renderers/react/src | head -n 50
echo
echo "== Show a couple call sites (with nearby code) =="
rg -n "getSharedComponentMetaManager\\(" -S code/renderers/react/src -n --context 4 | head -n 120Repository: storybookjs/storybook
Length of output: 4230
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for similar optional typescript imports/catches =="
rg -n "import\\('typescript'\\)|TypeScript not available|ERR_MODULE_NOT_FOUND|Cannot find package 'typescript'" code | head -n 200Repository: storybookjs/storybook
Length of output: 1066
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Files to inspect
FILES=(
"code/renderers/react/src/componentManifest/reactDocgen/utils.ts"
"code/renderers/react/src/componentManifest/reactDocgenTypescript.ts"
)
for f in "${FILES[@]}"; do
echo
echo "==== $f (first ~220 lines) ===="
awk '{print NR ":" $0}' "$f" | sed -n '1,220p'
done
echo
echo "==== show try/catch blocks in utils.ts around import('typescript') ===="
rg -n "import\\('typescript'\\)" "code/renderers/react/src/componentManifest/reactDocgen/utils.ts"
rg -n "try \\{|catch \\{" "code/renderers/react/src/componentManifest/reactDocgen/utils.ts"
echo
echo "==== show relevant part of reactDocgenTypescript.ts ===="
awk '{print NR ":" $0}' "code/renderers/react/src/componentManifest/reactDocgenTypescript.ts" | sed -n '1,160p'Repository: storybookjs/storybook
Length of output: 19775
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="code/renderers/react/src/docgen/preset.ts"
awk '{print NR ":" $0}' "$FILE" | sed -n '1,120p'Repository: storybookjs/storybook
Length of output: 2910
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for docgen provider invocation and error handling
rg -n "experimental_docgenProvider|docgenProvider|nextDocgen\\(input\\)|getSharedComponentMetaManager\\(" code/renderers/react/src -S
rg -n "nextDocgen\\(|DocgenProvider|experimental_docgenProvider" code -S | head -n 200Repository: storybookjs/storybook
Length of output: 7402
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="code/core/src/shared/open-service/services/docgen/server.ts"
test -f "$FILE" && echo "Found $FILE" || { echo "Missing $FILE"; exit 1; }
awk '{print NR ":" $0}' "$FILE" | sed -n '1,260p'
echo
echo "== Search for try/catch around provider execution =="
rg -n "try \\{|catch \\{|swallow|silent|nextDocgen" "$FILE" || trueRepository: storybookjs/storybook
Length of output: 3932
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILES=(
"code/renderers/react/src/componentManifest/generator.ts"
"code/renderers/react/src/docgen/buildDocgen.ts"
"code/core/src/core-server/presets/common-preset.ts"
"code/core/src/types/modules/core-common.ts"
)
for f in "${FILES[@]}"; do
echo
echo "==== $f (first/selected sections) ===="
test -f "$f" || { echo "Missing $f"; continue; }
awk '{print NR ":" $0}' "$f" | sed -n '1,220p'
done
echo
echo "== Search for how provider errors are handled in common-preset/docgen chain =="
rg -n "experimental_docgenProvider" code/core/src/core-server/presets/common-preset.ts
rg -n "catch \\{|throw|suppress|swallow|nextDocgen" code/core/src/core-server/presets/common-preset.tsRepository: storybookjs/storybook
Length of output: 25865
Avoid mislabeling non-TypeScript init errors as “TypeScript not available”
getSharedComponentMetaManager() is intentionally kicked off unawaited (void getSharedComponentMetaManager()) and docgen expects undefined fallback, so rethrowing from this catch would break that assumption. Keep returning undefined, but distinguish “typescript missing” from other initialization failures and log the actual error (so real ComponentMetaManager bugs aren’t silently misreported as “TypeScript not available”).
🤖 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/renderers/react/src/componentManifest/componentMetaManagerSingleton.ts`
around lines 19 - 27, The catch in getSharedComponentMetaManager() currently
masks all init failures as “TypeScript not available”; change it to detect a
genuine missing-module error (e.g., check error instanceof Error and inspect
error.code === 'MODULE_NOT_FOUND' or error.message includes "typescript") and
log the original Error details for non-missing-module failures (use logger.error
with error.stack/message) while still returning undefined in both cases so the
unawaited init and docgen fallback behavior remain unchanged; keep
ComponentMetaManager creation path and only treat the import failure as the
"missing TypeScript" debug log.
| afterEach(() => { | ||
| componentMetaManager?.dispose(); | ||
| componentMetaManager = undefined; | ||
| if (tempDir) { | ||
| cleanup(tempDir); | ||
| tempDir = undefined; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Confirm this suite relies on temp-dir helpers and that helpers use real fs.
rg -n "createTempDir|writeFiles|cleanup" code/renderers/react/src/docgen/buildDocgen.test.ts -C2
rg -n "node:fs|mkdtemp|writeFile|rmSync|mkdirSync" code/renderers/react/src/componentManifest/componentMeta/test-helpers.ts -C2Repository: storybookjs/storybook
Length of output: 2670
Use memfs for fs-backed fixtures in code/renderers/react/src/docgen/buildDocgen.test.ts
This suite writes real temp directories/files via createTempDir/writeFiles/cleanup, and the shared helpers use node:fs (mkdirSync, writeFileSync, rmSync). Replace the disk-backed setup with memfs (vol.fromJSON(...)) and add vol.reset() in a beforeEach.
Minimal direction
-import { afterEach, describe, expect, it } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it } from 'vitest';
+import { vol } from 'memfs';
+
+beforeEach(() => {
+ vol.reset();
+});🤖 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/renderers/react/src/docgen/buildDocgen.test.ts` around lines 34 - 40,
Tests currently create real temp dirs/files via createTempDir/writeFiles/cleanup
and helpers use node:fs functions; switch to an in-memory fs by importing memfs
(vol) and use vol.fromJSON(...) in a beforeEach to populate fixtures and
vol.reset() to clear state, update the afterEach to call vol.reset() instead of
cleanup and remove or adapt createTempDir/writeFiles/cleanup usages so they
write into memfs (or replace them with a helper that constructs the same JSON
map passed to vol.fromJSON), and ensure tests import { vol } from 'memfs' and no
longer rely on node:fs helper side effects (references: afterEach, beforeEach,
createTempDir, writeFiles, cleanup, vol.fromJSON, vol.reset; also address helper
functions that call mkdirSync/writeFileSync/rmSync).
Resolve conflicts in docgen server tests and buildReactComponentDocgen by keeping the ref-manifest branch behavior (no toReactComponentManifest). Co-authored-by: Cursor <cursoragent@cursor.com>
Babysit triageMerge conflicts: Resolved by merging CodeRabbit comments:
CI: Re-triggered CircleCI after transient 403 on |
Closes #
What I did
Wire the experimental docgen open service into static and dev manifest generation when
features.componentsManifestis enabled. Withfeatures.experimentalDocgenServer, React skips legacy components extraction and suppliesmeta.docgen: 'react-component-meta'viaexperimental_manifests; the docgen service holds full component payloads whilemanifests/components.jsonbecomes a v1 ref-based index with inlined summary fields and nesteddocgen.$refpointers into the service static store. Static build writes open-service artifacts before manifests, servescomponents.htmlfrom live docgen data, and keeps the docs manifest unchanged. Adds JSON reference utilities (RFC 6901 resolver, dereference helper), aligns docgen payloads withComponentManifest, and extends open-service runtime for void queries and subscription ergonomics.Agent prompt: migrate manifest consumers
Migrate a consumer from legacy inline
components.jsonto the ref-based multi-file format (support both)Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
Caution
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
Use the internal Storybook in
code/(code/.storybook). Compare behavior withfeatures.experimentalDocgenServertoggled on vs off.With
experimentalDocgenServer: true(current default in internal Storybook):cd code && yarn storybook:uihttp://localhost:6006/manifests/components.html— expect the components debugger rendered from the docgen open service.http://localhost:6006/manifests/components.json— expect 404 in dev (ref index is build-only).cd code && yarn storybook:ui:buildcode/storybook-static/manifests/components.json— expectv: 1ref-based index rows with nesteddocgen.$refpointers.code/storybook-static/manifests/components.htmlin a browser — expect the same docgen-backed debugger as dev.With
experimentalDocgenServer: false:http://localhost:6006/manifests/components.jsonshould return the legacy inline components manifest fromexperimental_manifests.storybook-static/manifests/components.jsonshould contain the legacy inline manifest (v: 0, full component payloads inlined).components.htmlshould still render from the inline manifest in both dev and build.Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsDeclare whether manual QA will be needed for this PR during the release, through
qa:neededorqa:skipMake 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/coreteam 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
New Features
components.jsonmanifests with structured docgen data including stories, subcomponents, and JSDoc tagsDocumentation
node:fsinteractions