Skip to content

Docgen: Add mock open service#34954

Open
JReinhold wants to merge 13 commits into
nextfrom
claude/wizardly-bartik-cc2705
Open

Docgen: Add mock open service#34954
JReinhold wants to merge 13 commits into
nextfrom
claude/wizardly-bartik-cc2705

Conversation

@JReinhold
Copy link
Copy Markdown
Contributor

@JReinhold JReinhold commented May 28, 2026

What I did

First concrete consumer of the open-service architecture: the core/docgen service exposes per-component documentation keyed by componentId. Renderers and addons contribute through an experimental_docgenProvider preset using middleware-style chaining (each provider wraps the previously accumulated one).

This PR ships scaffolding + a mock React provider so the wiring is exercised end-to-end. Real RCM-backed extraction lands in #34963.

Feature-flagged

Everything is gated behind experimentalDocgenServer (sibling of experimentalReactComponentMeta). When the flag is off:

  • the service is not registered
  • the preset chain is not built
  • no story-index generation is forced
  • build-static.ts's getRegisteredServices().length > 0 check correctly suppresses the "Building open services.." log

The internal Storybook (code/.storybook/main.ts) opts in so we can dogfood the static build.

What's in this PR

  • core/docgen open service — definition in services/docgen/{definition,server,types}.ts. The query is a tolerant sync read of state.components[componentId] (returns undefined until extracted). The real work — story-index lookup, provider invocation, error handling — lives in the extractDocgen command, which returns the payload it just stored.
  • experimental_docgenProvider preset with a DocgenProviderPreset type that types nextDocgen as non-nullable (core's identity seed is always supplied). Providers don't need ?. noise.
  • Middleware contract documented on DocgenProvider's JSDoc: providers should merge with downstream via { ...downstream, ...yourOverrides } and downstream?.field ?? yours so future fields aren't silently dropped and explicit downstream values (incl. empty strings) are preserved.
  • Provider input is just { importPath } (taken from IndexEntry.importPath). Providers that can only read CSF bail on .mdx paths by forwarding to nextDocgen. The service resolves index → entry → importPath before calling.
  • Static-build snapshots written to <outputDir>/services/core/docgen/<componentId>.json via the open-service staticInputs + staticPath machinery. Gated by !options.ignorePreview.
  • Mock React provider in code/renderers/react/src/docgen/preset.ts and an addon-docs description enricher in code/addons/docs/src/docgen.ts, both wired through experimental_docgenProvider. The chain is exercised in tests so two-provider composition is covered before phase 2/3 swaps in real data.
  • Shared getComponentIdFromEntry helper extracted to code/core/src/common/utils/component-id.ts and adopted by both the docgen service and the existing manifests generator.
  • OpenServiceDocgenMissingComponentError typed error (code 12) for unknown componentIds.
  • README guidance (code/core/src/shared/open-service/README.md) captures a lesson from building the service: query.load bodies should be minimal — ideally a one-liner that calls a command — so the real work stays reusable, testable, and reachable outside the load drain. Added to both the Load concept section and the Design Rules.

Telescoping plan

  1. This PR — DocgenService scaffolding + preset key + feature flag + mock provider
  2. #34963 — phases 2 & 3 combined: per-component RCM extraction + real RCM-backed React provider + tightened payload schema

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

Ten tests in services/docgen/server.test.ts cover: extractDocgen end-to-end (return value + state write + provider input shape), undefined-on-no-payload semantics, missing-componentId error, provider error propagation, sync query returning undefined before load, .loaded() driving the load body, .loaded() surfacing missing-component errors, static-build output paths and contents, two-provider middleware composition, and the bottom-of-chain undefined passthrough.

Manual testing

  1. cd code && yarn storybook:ui:build
  2. Confirm code/storybook-static/services/core/docgen/<componentId>.json files exist for components in the internal Storybook
  3. Each file should contain { components: { <componentId>: { componentId, name, description, props: [] } } } with the mocked placeholder strings — phase 2/3 swaps these for real RCM-extracted data

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
  • Make sure this PR contains one of the labels below: `bug`, `maintenance`, `build`, `cleanup`, `feature request`, etc.

🦋 Canary release

This PR does not have a canary release associated.

🤖 Generated with Claude Code

First concrete open-service consumer: a per-component docgen service
backed by an `experimental_docgen` middleware-style preset. Renderers and
addons can register extractors that wrap the previous accumulated
extractor and merge results.

Phase 1 ships the service definition, registration, static-snapshot
wiring under `docgen/<componentId>.json`, and a mock extractor in the
React renderer. Phase 2 will teach RCM per-component extraction; phase 3
replaces the mock with the real RCM-backed extractor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JReinhold JReinhold self-assigned this May 28, 2026
JReinhold and others added 2 commits May 28, 2026 16:02
…rovider

Rename DocgenExtractor → DocgenProvider (and matching field/variable names)
to better reflect that registrants supply data rather than extract it from a
single source.

Adds a second docgen provider in addon-docs that enriches description and
appends a synthetic prop. With the React mock provider also registered,
this exercises the middleware-merge path end-to-end across two packages,
validating that the chosen preset chaining pattern composes correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er; drop per-provider identity fallback

The preset slot, type re-exports, Presets.apply overload, and every preset
file's named export now use `experimental_docgenProvider` — keeps the
preset name consistent with the DocgenProvider type.

Each provider file no longer carries its own identity-provider seed.
Providers call `nextDocgen?.(input)` and treat the downstream payload as
optional, falling back to literals where needed. Core still seeds the
chain with an identity provider so the composed result is always a
defined function, but individual provider files no longer need to know
about that detail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JReinhold JReinhold changed the base branch from next to jeppe/service-registration-attempt-2 May 28, 2026 19:04
@JReinhold JReinhold changed the base branch from jeppe/service-registration-attempt-2 to claude/practical-jones-e3d0c1 May 28, 2026 19:05
@JReinhold JReinhold changed the title core: add DocgenService (phase 1, mocked extractor) Docgen: Add mock open service May 28, 2026
@JReinhold JReinhold added maintenance User-facing maintenance tasks ci:normal labels May 28, 2026
@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented May 28, 2026

Package Benchmarks

Commit: 88740ec, ran on 29 May 2026 at 11:19:30 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 72 72 0
Self size 20.39 MB 20.41 MB 🚨 +14 KB 🚨
Dependency size 36.11 MB 36.11 MB 0 B
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 203 203 0
Self size 908 KB 908 KB 🚨 +144 B 🚨
Dependency size 88.58 MB 88.59 MB 🚨 +15 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 196 196 0
Self size 32 KB 32 KB 🎉 -36 B 🎉
Dependency size 87.07 MB 87.08 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 73 73 0
Self size 1.08 MB 1.08 MB 🚨 +66 B 🚨
Dependency size 56.50 MB 56.52 MB 🚨 +14 KB 🚨
Bundle Size Analyzer node node

The open-service registration API recently moved from a single
`static: { path, inputs }` block to a definition-time `filePath`
plus a registration-time `staticInputs`. Snapshot paths now also
auto-prefix by service id, so the docgen `filePath` only needs to
produce `<componentId>.json` (the runtime turns it into
`core/docgen/<componentId>.json`).

Updates server.ts, the definition, and the matching static-build
test to the new shape. Phase-1 tests all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JReinhold JReinhold marked this pull request as ready for review May 28, 2026 20:11
@JReinhold JReinhold requested a review from ndelangen May 28, 2026 20:11
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an experimental core docgen open service: provider middleware types/schemas, server registration and handler wiring, core services preset registration, docs-addon and React renderer provider presets, componentId utility centralization, tests, and conditional static-build export.

Changes

Experimental Docgen Provider System

Layer / File(s) Summary
ComponentId extraction utility
code/core/src/common/utils/component-id.ts, code/core/src/common/index.ts, code/renderers/react/src/componentManifest/generator.ts
Centralizes component ID parsing via getComponentIdFromEntry(entry) and updates the React manifest generator to use it.
Docgen type contracts and service schemas
code/core/src/shared/open-service/services/docgen/types.ts, code/core/src/shared/open-service/services/docgen/definition.ts
Adds DocgenProviderInput, DocgenPayload, DocgenProvider types and Valibot schemas for input/payload/output, plus service state shape and compile-time type compatibility checks.
Docgen service registration and handlers
code/core/src/server-errors.ts, code/core/src/shared/open-service/services/docgen/server.ts
Implements registerDocgenService, registers getDocgen query and extractDocgen command, invokes the configured provider with entries filtered by componentId, and throws OpenServiceDocgenMissingComponentError when no entries exist.
Core server preset integration
code/core/src/core-server/presets/common-preset.ts
Extends the services preset to await story index generation, resolve experimental_docgenProvider (with identity fallback), and call registerDocgenService with getIndex and the provider.
Public type surface and preset API
code/core/src/types/modules/core-common.ts
Re-exports DocgenProvider; adds experimental_docgenProvider overload to Presets.apply; and introduces experimental_docgenProvider fields to StorybookConfigRaw and StorybookConfig (plus feature flag).
Addon and renderer docgen providers
code/addons/docs/src/docgen.ts, code/addons/docs/src/preset.ts, code/renderers/react/src/docgen/preset.ts, code/renderers/react/src/preset.ts
Adds experimental_docgenProvider presets in the docs addon and React renderer that wrap downstream providers, derive fallback names/descriptions, and merge or synthesize props (including a synthetic docs marker).
Comprehensive docgen service tests
code/core/src/shared/open-service/services/docgen/server.test.ts
Tests service registration, extractDocgen and getDocgen flows, provider input filtering, missing-component error propagation, static build JSON output per componentId, and provider middleware composition/merging.
Static build integration
code/core/src/core-server/build-static.ts
Only schedule open-service static file generation when registered services exist; adds import of getRegisteredServices and a conditional push of writeOpenServiceStaticFiles during build.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
code/addons/docs/src/preset.ts (1)

228-228: ⚡ Quick win

Use explicit extension in the new re-export.

Line 228 should use an explicit .ts extension to stay consistent with the TS module import/export guideline.

Proposed fix
-export { experimental_docgenProvider } from './docgen';
+export { experimental_docgenProvider } from './docgen.ts';

As per coding guidelines "For TypeScript source code in the repo, 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".

🤖 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/addons/docs/src/preset.ts` at line 228, The re-export for
experimental_docgenProvider uses a bare specifier; update the export in
preset.ts so the relative module path includes the explicit .ts extension
(export { experimental_docgenProvider } from './docgen.ts') to comply with the
TypeScript module import/export guideline and keep consistency with other TS
source files.
🤖 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/core-server/presets/common-preset.ts`:
- Around line 332-351: The services preset sets
globalThis.STORYBOOK_SERVICES_LOADED before awaiting async work in services; if
any await (e.g., options.presets.apply('storyIndexGenerator') or
apply('experimental_docgenProvider')) throws, the flag remains true and prevents
retries—wrap the async initialization in a try/catch/finally within services and
in the finally reset globalThis.STORYBOOK_SERVICES_LOADED to false when
initialization failed (only keep it true on successful completion) so that
failed initialization does not block subsequent attempts to apply services;
ensure the logic around generator, provider, and registerDocgenService is inside
the try and the flag-reset happens on error.

---

Nitpick comments:
In `@code/addons/docs/src/preset.ts`:
- Line 228: The re-export for experimental_docgenProvider uses a bare specifier;
update the export in preset.ts so the relative module path includes the explicit
.ts extension (export { experimental_docgenProvider } from './docgen.ts') to
comply with the TypeScript module import/export guideline and keep consistency
with other TS source files.
🪄 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: 9c9407d5-9dcf-40a6-bab4-8e74b38c61b9

📥 Commits

Reviewing files that changed from the base of the PR and between 01c6e99 and b048376.

📒 Files selected for processing (14)
  • code/addons/docs/src/docgen.ts
  • code/addons/docs/src/preset.ts
  • code/core/src/common/index.ts
  • code/core/src/common/utils/component-id.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/server-errors.ts
  • code/core/src/shared/open-service/services/docgen/definition.ts
  • code/core/src/shared/open-service/services/docgen/server.test.ts
  • code/core/src/shared/open-service/services/docgen/server.ts
  • code/core/src/shared/open-service/services/docgen/types.ts
  • code/core/src/types/modules/core-common.ts
  • code/renderers/react/src/componentManifest/generator.ts
  • code/renderers/react/src/docgen/preset.ts
  • code/renderers/react/src/preset.ts

Comment thread code/core/src/core-server/presets/common-preset.ts Outdated
Matches the existing "Loading presets" / "Building manager.." / "Building
preview.." CLI breadcrumbs so users can see when the open-service
snapshot step is running. Only emits the log (and only schedules the
work) when at least one service is registered, so installations without
any service consumers stay quiet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic
Copy link
Copy Markdown
Contributor

valentinpalkovic commented May 29, 2026

Review — multi-model pass (architecture · correctness/concurrency · DX · craft)

Verdict: no blockers. Sound phase-1 scaffolding; the design (sync-read query + command-does-work + middleware preset chain + per-component static files) fits the open-service framework well. Recommend addressing the HIGH items below, or deferring them as tracked TODOs in the phase-2/3 plan.

✅ Verified fine (so they don't need re-checking)

  • Error code 12 for OpenServiceDocgenMissingComponentError is unique in CORE_COMMON (existing 1–11, 16).
  • No double story-index generation from the two presets.apply('storyIndexGenerator') calls — common-preset.ts memoizes via the module-level storyIndexGeneratorPromise, so both share one generator instance.
  • Static files are not silently skipped by the new gate — await applyServicesPresetOnce() registers docgen before getRegisteredServices() is read.
  • Static-build hydration contract (file stores the whole { components: { … } } state; query reads state.components[id]) is sound.
  • filePath path-traversal risk is low (componentIds come from sanitized story ids).

🔴 HIGH

  • H1 — re-extract on every read, no cache/invalidation. getDocgen.load calls extractDocgen unconditionally. Cheap for the mock; in phase 3 (real RCM) this is an unbounded per-read recompute + last-finisher-wins under concurrency. Add if (ctx.self.state.components[input.componentId]) return; plus an explicit invalidate(componentId) seam for the future file watcher — or leave a tracked TODO so the cliff isn't inherited silently. (This is the "verify cache invalidation" already slated for phase 2.)
  • H2 — docgen static build isn't gated by ignorePreview. index.json/writeManifests sit behind !options.ignorePreview; docgen only behind getRegisteredServices().length > 0 (effectively always true). Since staticInputs calls getIndex(), a manager-only / preview-skipped build now forces full story-index generation + writes docgen files it previously wouldn't. Gate consistently, e.g. if (!options.ignorePreview && getRegisteredServices().length > 0), and/or guard the generator resolution inside the services preset.
  • H3 — STORYBOOK_SERVICES_LOADED is set before the new failable work. In common-preset.ts, the flag flips true, then the preset awaits storyIndexGenerator, provider composition, and registerDocgenService. If any throws (or on a second build/dev lifecycle in one process), the flag is stuck true with no service registered and re-application throws. Set the marker only after successful registration (or roll back in catch); prefer a lifecycle-scoped promise over a boolean.

🟠 MEDIUM

  • M1 — the middleware-chaining claim isn't tested through presets.apply. server.test.ts composes plain closures (identity → A → B) and passes the already-composed function in; it never exercises presets.apply('experimental_docgenProvider', identityDocgenProvider). The shipped React (renderers/react/src/docgen/preset.ts) and addon-docs (addons/docs/src/docgen.ts) providers have no tests. Add one preset-level composition test + focused tests for the two mock providers.
  • M2 — docgen indexes all entries → spurious files. staticInputs/extractDocgen run getComponentIdFromEntry over every index.entries value. The manifest generator filters via selectComponentEntries; docgen doesn't, so a standalone intro--docs page yields core/docgen/intro.json with a fabricated payload — wrong static contract even in mock phase. Reuse the manifest filtering rule.
  • M3 — middleware DX: each provider re-merges every DocgenPayload field. A phase-3 field addition silently drops in providers that don't forward it, and props spreading is a double-append footgun. Consider a mergeDocgen(base, patch) helper or a Partial<DocgenPayload> contract merged by the core service.
  • M4 — the new getComponentIdFromEntry helper wasn't fully adopted. code/addons/docs/src/manifest.ts:112 still uses inline docsEntry.id.split('--')[0] (the two React-generator sites were converted). Swap it for the helper from storybook/internal/common. (save-story.ts:73 destructures a CSF id string — out of scope.)
  • M5 — merge-idiom inconsistency between the two mocks. React uses downstream?.name || fallbackName (empty → fallback); addon-docs uses downstream?.name ?? '' (keeps empty). Whichever contributors copy, they may inherit the wrong one. Pick one, document why, apply to both.

🟡 LOW / NIT

  • L1nextDocgen?.(input) defends an impossible state (the identity seed is always supplied). Making nextDocgen required + dropping ?. removes noise and surfaces future misconfiguration instead of swallowing it into empty strings.
  • L2props: unknown[] is shipped in an experimental public type; mark the field @experimental/unstable so consumers don't build on it before phase 3.
  • L3 — naming drift: code uses DocgenProvider/experimental_docgenProvider, but the extractDocgen command + several description strings say "extractor". Standardize on "provider".
  • L4 — each services/docgen/<id>.json contains the whole { components: { … } } state, not just the payload — correct (open-service convention) but worth a one-line doc note for external consumers.
  • L5getRegisteredServices().length > 0 is near-constant-true and writeOpenServiceStaticFiles is already empty-safe; fold into the !ignorePreview gate (H2) or drop.
  • L6_assertDocgenPayloadShapeMatches + eslint-disable guards two intentionally-loose types (near-zero value now); keep with a // TODO(phase-3) or defer until the schema is concrete.
  • L7 — phase-scoped JSDoc ("Phase 3 will replace this body…") across docgen.ts/preset.ts/definition.ts will rot; per AGENTS.md, trim to one-line rationale + a tracked TODO.
  • L8 — the two apply('storyIndexGenerator') calls use inconsistent generics (<Promise<StoryIndexGenerator>> vs <StoryIndexGenerator>); works via Awaited, just tidy for clarity.
  • L9 — log line 'Building open services..' (double dot, and it writes rather than builds) → 'Writing open service static files.'
  • L10core-common.ts (authoritative public types) imports + re-exports from the deep internal shared/open-service/services/docgen/types.ts; consider inverting so the public module owns the types. Types-only/subjective.

Suggested "before merge" set

  • H2 — gate docgen static build by !options.ignorePreview
  • H3 — set STORYBOOK_SERVICES_LOADED only after successful registration
  • M4 — adopt getComponentIdFromEntry at addons/docs/src/manifest.ts:112
  • M1 — preset-level composition test + tests for the two mock providers

Strongly recommended (cheap, prevents phase-3 pain): H1 (cache guard + invalidation seam), M2 (entry filtering), M3/M5 (unify merge semantics).

🤖 Generated with Claude Code — synthesized from a Claude + Codex + Gemini + pragmatic-engineering review pass.

JReinhold and others added 3 commits May 29, 2026 10:01
…perimentalDocgenServer

Three changes to the phase-1 docgen surface:

1. DocgenProvider input is now { importPath } instead of { componentId, entries }.
   The service resolves an entry for the requested componentId and hands the
   raw importPath to the provider chain — providers that don't know how to
   handle a given path (e.g. the React mock provider seeing an .mdx attached
   docs file) bail to nextDocgen.

2. DocgenProvider can return undefined. The identity seed in core's services
   preset now returns undefined so a chain with no real providers signals
   "no docgen here" instead of producing an empty payload. The addon-docs
   enricher mirrors this — it returns undefined when nothing downstream
   produced a payload, rather than fabricating one.

3. The whole docgen-service registration is gated behind a new
   `experimentalDocgenServer` feature flag (sibling of
   experimentalReactComponentMeta). When the flag is off, no service is
   registered, no preset chain is built, and the static-build log step
   already correctly skips itself via the existing registered-services
   length check.

Also picks up an upstream rename: query `filePath` → `staticPath`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The command's output schema was `void`, so callers had to follow up with a
separate `getDocgen` read to see what was extracted. The work was already
in scope — return the payload (or undefined) directly so a single
extractDocgen call gives consumers the data alongside the state write.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
code/core/src/shared/open-service/services/docgen/server.test.ts (1)

145-176: ⚡ Quick win

Add a mixed-entry regression test for shared componentId resolution.

Consider adding a case where one componentId has both a story entry and an attached-docs entry with different importPath values. That will lock in selection semantics and prevent regressions around unsupported-path provider bailouts.

🤖 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/open-service/services/docgen/server.test.ts` around
lines 145 - 176, Add a regression test to registerDocgenService that includes a
mixed index where one componentId appears twice with different importPaths (use
makeStoryEntry for e.g. 'button--primary' with importPath './button.stories.tsx'
and makeAttachedDocsEntry for an attached-docs entry that maps to the same
componentId but a different importPath like './attached/button.docs.tsx'), keep
the provider behavior the same (use importPath.includes('button') to pick
componentId 'button'), call buildStaticFiles and assert that only one
core/docgen/button.json is emitted and that its chosen entry matches the desired
selection semantics (verify the description or importPath-derived field to
confirm which importPath was used); reference registerDocgenService,
makeGetIndex, makeStoryEntry, makeAttachedDocsEntry, provider, and
buildStaticFiles when locating where to add the test.
🤖 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/addons/docs/src/docgen.ts`:
- Around line 19-25: The current return in docgen.ts unconditionally replaces
empty descriptions and always appends a marker to props; change it to use
nullish coalescing for description (preserve downstream.description when it is
an empty string) and merge props with deduplication (only add the marker if
downstream.props does not already contain an object with source
'`@storybook/addon-docs`' and kind 'docs-marker'), or better yet extract and call
a shared mergeDocgen(base, patch) helper used by the React provider so both
providers use the same nullish-coalesce semantics and a single-place dedupe rule
for props.

In `@code/core/src/shared/open-service/services/docgen/server.test.ts`:
- Around line 33-38: Tests repeatedly recreate the provider mock and inspect
untyped mock internals; centralize the mock by declaring const provider =
vi.fn<DocgenProvider>(...) at top-level and move per-test return/error behavior
into a beforeEach that calls provider.mockImplementationOnce(...) or
provider.mockResolvedValueOnce(...); replace direct access like
provider.mock.calls[...] with typed vi.mocked(provider) when asserting
calls/returns (e.g., vi.mocked(provider).mock.calls / .mock.results) so callers
of DocgenProvider are consistently typed and per-test setups live in beforeEach.

In `@code/core/src/shared/open-service/services/docgen/server.ts`:
- Around line 51-53: Replace the single .find(...) lookup with a two-step
selection: collect all matches from Object.values(index.entries) where
getComponentIdFromEntry(e) === input.componentId, then prefer and return an
entry that represents a story (e.g., an entry whose type/marker indicates a
story — check entry.type or other story-specific field) and fall back to the
first matched entry if no story entry exists; update the code around the
variable entry and the lookup over index.entries/getComponentIdFromEntry to
implement this preference so story entries are chosen over attached-docs .mdx
entries.

In `@code/renderers/react/src/docgen/preset.ts`:
- Around line 24-28: The merge currently mixes || and ?? causing empty strings
for name/description to be treated differently than componentId/props; update
the merge in preset.ts so name and description use nullish coalescing like the
others (change downstream?.name || componentId to downstream?.name ??
componentId and downstream?.description || `Mocked docgen for
${input.importPath}` to downstream?.description ?? `Mocked docgen for
${input.importPath}`) to make behavior consistent for downstream, and as a
follow-up per reviewer M5 consider aligning with the docs addon provider by
making description be always overwritten by downstream?.description (use ??) and
changing props merge logic to append downstream props rather than replace
(adjust the props handling on the props field accordingly).

---

Nitpick comments:
In `@code/core/src/shared/open-service/services/docgen/server.test.ts`:
- Around line 145-176: Add a regression test to registerDocgenService that
includes a mixed index where one componentId appears twice with different
importPaths (use makeStoryEntry for e.g. 'button--primary' with importPath
'./button.stories.tsx' and makeAttachedDocsEntry for an attached-docs entry that
maps to the same componentId but a different importPath like
'./attached/button.docs.tsx'), keep the provider behavior the same (use
importPath.includes('button') to pick componentId 'button'), call
buildStaticFiles and assert that only one core/docgen/button.json is emitted and
that its chosen entry matches the desired selection semantics (verify the
description or importPath-derived field to confirm which importPath was used);
reference registerDocgenService, makeGetIndex, makeStoryEntry,
makeAttachedDocsEntry, provider, and buildStaticFiles when locating where to add
the test.
🪄 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: 367645ee-80d8-4cc7-867e-604bca144d12

📥 Commits

Reviewing files that changed from the base of the PR and between 9c30cef and a9ada24.

📒 Files selected for processing (8)
  • code/addons/docs/src/docgen.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/shared/open-service/services/docgen/definition.ts
  • code/core/src/shared/open-service/services/docgen/server.test.ts
  • code/core/src/shared/open-service/services/docgen/server.ts
  • code/core/src/shared/open-service/services/docgen/types.ts
  • code/core/src/types/modules/core-common.ts
  • code/renderers/react/src/docgen/preset.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • code/core/src/types/modules/core-common.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/shared/open-service/services/docgen/definition.ts

Comment thread code/addons/docs/src/docgen.ts
Comment thread code/core/src/shared/open-service/services/docgen/server.test.ts Outdated
Comment thread code/core/src/shared/open-service/services/docgen/server.ts
Comment thread code/renderers/react/src/docgen/preset.ts
JReinhold and others added 4 commits May 29, 2026 10:37
…ttern

Captures a lesson from building the docgen service: when query.load and a
command both look plausible for the same work, the work belongs in the
command. The load body should be a one-line passthrough that calls it.

Extends the Load concept section with the rationale (reusability,
testability, clarity) and adds a matching bullet to the Design Rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four review nits, addressed together because they overlap:

H2 — Docgen static build wasn't gated by ignorePreview, so a manager-only
build forced full story-index generation (staticInputs calls getIndex()).
Mirror the !options.ignorePreview gate around index.json / writeManifests
at the registration boundary: don't register the service at all when
previewing is off.

L1 — `nextDocgen?.(input)` defended an impossible state (core always
seeds the chain). New `defineDocgenProvider` helper from
`storybook/internal/common` types `next` as required and throws a clear
error if the seed is ever missing, instead of silently degrading to empty
payloads.

M3 + M5 — The two phase-1 mocks merged downstream inconsistently (React
rebuilt the payload field-by-field; addon-docs used spread). A future
provider adding a `DocgenPayload` field would be silently dropped by the
React mock. Document the spread + `??` convention on the DocgenProvider
type's JSDoc and on `defineDocgenProvider`, and update the React mock to
match. The convention preserves both unknown fields and explicit
downstream values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets us dogfood the docgen service against real components in
storybook:ui:build and storybook:ui dev runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Base automatically changed from claude/practical-jones-e3d0c1 to next May 29, 2026 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal maintenance User-facing maintenance tasks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants