Skip to content

Module Graph: Refactor from StoryDependencyService to open service-based ModuleGraphService#35048

Merged
JReinhold merged 19 commits into
nextfrom
jeppe-cursor/4b72cdff
Jun 9, 2026
Merged

Module Graph: Refactor from StoryDependencyService to open service-based ModuleGraphService#35048
JReinhold merged 19 commits into
nextfrom
jeppe-cursor/4b72cdff

Conversation

@JReinhold

@JReinhold JReinhold commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Closes #

What I did

This PR moves Storybook's story module graph out of change detection and into a new core/module-graph open service.

Concretely, it:

  • Moves the module graph engine plus its parser registry, resolver, dependency graph, and adapter types into shared/open-service/services/module-graph.
  • Registers core/module-graph as the single service boundary for module graph consumers.
  • Stores JSON-serializable module graph state using story-index-style relative paths such as ./src/Button.stories.tsx, so future static snapshots do not leak absolute filesystem paths.
  • Exposes open-service queries for:
    • getStatus — lifecycle state (booting, ready, error, unavailable) with serializable ErrorLike errors.
    • getStoriesForFiles — maps source files to affected story files and graph distance.
    • getGraphRevision — monotonic graph/index revision for consumers that need to rescan derived state.
    • getLatestStoryChanges — latest changed story files plus revision, used by DocGen reactivity.
  • Removes the legacy StoryDependencyGraphService/active-registry compatibility path; current consumers must use the open service.
  • Makes ChangeDetectionService consume module graph data through open-service queries/subscriptions instead of engine callbacks.
  • Adds DocGen reactivity by subscribing to getLatestStoryChanges and proactively refreshing already-extracted docgen for affected component ids.
  • Preserves the base branch's newer DocGen entry-selection/provider behavior while layering module graph reactivity on top.
Follow-up prompt: migrate addon-mcp to core/module-graph
You are working in the storybookjs/mcp repository. Update addon-mcp to stop using the removed Storybook dependency graph compatibility API and migrate it to Storybook's new `core/module-graph` open service API.

Context:
- Storybook PR storybookjs/storybook#35048 removes `StoryDependencyGraphService`, `experimental_getDependencyGraphService`, and the active dependency graph registry.
- The replacement is the `core/module-graph` open service.
- The old addon-mcp usage is in `packages/addon-mcp/src/utils/resolve-component-stories.ts`.
- Today that file imports `getDependencyGraphService` from `./change-detection.ts`, awaits it, checks `service.hasGraph()`, and then calls `service.lookup(target)` for each component/barrel target.

New Storybook module graph API to use:
- Resolve the open service runtime for service id `core/module-graph` instead of using `getDependencyGraphService`.
- Use `getStatus` to determine availability/readiness:
  - `{ value: 'booting' }`: graph is still building; return an unavailable response with a helpful "hasn't built yet" reason.
  - `{ value: 'ready' }`: graph is ready; continue lookup.
  - `{ value: 'unavailable', reason, error? }`: return unavailable with the provided reason.
  - `{ value: 'error', error }`: return unavailable/failure with the serialized error message.
- Use `getStoriesForFiles.loaded({ files })` instead of `lookup(target)`.
  - Input accepts absolute paths, `./`-prefixed relative paths, or relative paths without `./`, with POSIX or Windows separators.
  - Output is positional: result `i` corresponds to input file `i`.
  - Output story files are story-index-style relative paths such as `./src/Button.stories.tsx`, not absolute paths.
  - Each hit has `{ storyFile, depth }`, where `depth` is the breadth-first-search import distance.

Migration requirements:
1. Remove the dependency on the old `getDependencyGraphService` helper and any old `StoryDependencyGraphService` typing.
2. Add a small helper for accessing the open service in the addon environment. Prefer whatever addon-mcp already uses for Storybook open services; if no helper exists, create one that mirrors Storybook's open-service access pattern for `core/module-graph`.
3. In `resolveComponentStories`:
   - Get the module graph service.
   - Await `moduleGraph.queries.getStatus.loaded(undefined)` before resolving component stories.
   - If status is not `ready`, return `{ available: false, reason: ... }` with behavior equivalent to the old unavailable/hasn't-built branches.
   - Keep the existing path normalization, canonicalisation, and barrel expansion behavior (`expandBarrelTargets`, `canonicalise`) so caller ergonomics do not regress.
   - Dedupe all expanded target paths per request, call `getStoriesForFiles.loaded({ files: targets })` once for the batch if practical, and merge hits back per original component path.
   - Since returned `storyFile` values are now relative story-index paths, map story ids from the story index using `entry.importPath` directly (or normalize to the same `./...` format) instead of absolute story-file keys.
   - Preserve minimum-depth merging across barrel-expanded targets and stable sorting by `depth` then `storyId`.
4. Update tests for `resolve-component-stories.ts`:
   - ready status + direct source file hit
   - booting status returns unavailable
   - unavailable/error status returns helpful unavailable reason
   - returned relative story paths map to story ids correctly
   - barrel target expansion still merges minimum depth
   - path-not-found behavior is unchanged
5. Run the addon-mcp test/lint/typecheck commands used in that repo and fix any failures.

Keep the migration focused. Do not reintroduce Storybook's removed dependency graph compatibility API.

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

Focused validation run locally after resolving conflicts with claude/docgen-phase-3-rcm-provider:

  • npx vitest run --config code/core/vitest.config.ts code/core/src/shared/open-service/services/module-graph code/core/src/shared/open-service/services/docgen/server.test.ts code/core/src/core-server/change-detection/change-detection-service.test.ts (12 files, 150 tests)
  • FORCE_COLOR=0 yarn exec jiti ./scripts/check/check-package.ts --cwd code/core
  • yarn --cwd code lint:js:cmd core/src/shared/open-service/services/docgen/server.ts core/src/shared/open-service/services/docgen/server.test.ts core/src/shared/open-service/services/module-graph/definition.ts core/src/shared/open-service/services/module-graph/server.ts core/src/shared/open-service/services/module-graph/server.test.ts core/src/shared/open-service/services/module-graph/types.ts core/src/core-server/change-detection/change-detection-service.ts core/src/core-server/change-detection/change-detection.test-helpers.ts core/src/core-server/dev-server.ts --fix

Manual testing

Please QA the change detection path in a Storybook project using a builder that supports change detection, such as a React Vite Storybook.

  1. Start from a clean git working tree in a Storybook project with at least one component story, for example a Button.tsx component and Button.stories.tsx story.

  2. Enable the change detection feature flag in .storybook/main.ts:

    export default {
      features: {
        changeDetection: true,
      },
    };

    If the project already has a features object, add changeDetection: true to it.

  3. Start Storybook in dev mode.

  4. Open the manager UI and confirm the initial sidebar has no unexpected change-detection statuses for the clean working tree.

  5. Edit the component file imported by the story, for example Button.tsx, without committing the change.

  6. Confirm the related story receives the expected changed/modified status in the Storybook sidebar.

  7. Edit a transitive dependency of that story, for example a helper/module imported by Button.tsx, and confirm the dependent story receives the expected affected status.

  8. Add or rename a story file, then trigger story index regeneration by saving the file or requesting index.json; confirm the story list and change-detection status update without restarting Storybook.

  9. Revert the edited files back to the clean git state and confirm the change-detection statuses clear after Storybook processes the file changes.

  10. While Storybook is still running, make one more source-file edit and confirm the status updates again, verifying the module graph continues to process incremental changes after the initial build.

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

  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip

  • 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 pull request has been released as version 0.0.0-pr-35048-sha-4dcfdcfd. Try it out in a new sandbox by running npx storybook@0.0.0-pr-35048-sha-4dcfdcfd sandbox or in an existing project with npx storybook@0.0.0-pr-35048-sha-4dcfdcfd upgrade.

More information
Published version 0.0.0-pr-35048-sha-4dcfdcfd
Triggered by @yannbf
Repository storybookjs/storybook
Branch jeppe-cursor/4b72cdff
Commit 4dcfdcfd
Datetime Tue Jun 9 14:52:51 UTC 2026 (1781016771)
Workflow run 27214758955

To request a new release of this pull request, mention the @storybookjs/core team.

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

Made with Cursor

Summary by CodeRabbit

  • Refactor

    • Change detection now integrates with a shared module-graph service and uses a simplified start flow (single enabled flag).
  • New Features

    • Added a module-graph service with status, revisions, and file→story mappings for more accurate change tracking and subscriptions.
    • Dev server and docgen now subscribe to module-graph updates to drive refreshes.
  • Bug Fixes

    • Docgen refreshes proactively when dependent story files change, reducing stale docs.
  • Documentation

    • Guidance added for correctly awaiting cross-service query loads.

JReinhold and others added 2 commits June 3, 2026 23:23
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold self-assigned this Jun 4, 2026
@JReinhold JReinhold added maintenance User-facing maintenance tasks core ci:normal Run our default set of CI jobs (choose this for most PRs). qa:needed Pull Requests that will need manual QA prior to release. labels Jun 4, 2026
@JReinhold JReinhold changed the title Refactor module graph into open service Module Graph: Refactor from StoryDependencyService to open service-based ModuleGraphService Jun 4, 2026
@JReinhold JReinhold changed the base branch from next to claude/docgen-phase-3-rcm-provider June 4, 2026 05:23
Co-authored-by: Cursor <cursoragent@cursor.com>
@storybook-app-bot

storybook-app-bot Bot commented Jun 4, 2026

Copy link
Copy Markdown

Package Benchmarks

Commit: a1f5ea6, ran on 9 June 2026 at 19:48:57 UTC

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

@storybook/addon-docs

Before After Difference
Dependency count 18 18 0
Self size 1.26 MB 1.28 MB 🚨 +20 KB 🚨
Dependency size 9.27 MB 9.27 MB 🎉 -6 B 🎉
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 72 72 0
Self size 20.72 MB 20.75 MB 🚨 +34 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 🎉 -213 B 🎉
Dependency size 88.96 MB 89.00 MB 🚨 +34 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 196 196 0
Self size 32 KB 32 KB 0 B
Dependency size 87.45 MB 87.49 MB 🚨 +34 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 73 73 0
Self size 1.08 MB 1.08 MB 0 B
Dependency size 56.83 MB 56.86 MB 🚨 +34 KB 🚨
Bundle Size Analyzer node node

JReinhold and others added 7 commits June 4, 2026 08:41
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold marked this pull request as ready for review June 4, 2026 11:43
@JReinhold JReinhold requested review from ghengeveld and yannbf June 4, 2026 11:46
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

Refactor change-detection to use a new core/module-graph open-service: add shared types/service definition, implement ModuleGraphEngine, rewire ChangeDetectionService to query module-graph, update server/presets/dev-server/docgen/index-json wiring, remove legacy barrels, and update tests and imports.

Changes

Module-Graph Open-Service

Layer / File(s) Summary
Service types, error class, and utilities
code/core/src/shared/open-service/services/module-graph/types.ts, code/core/src/shared/open-service/services/module-graph/errors.ts
Adds ErrorLike, ModuleGraphStatus/ModuleGraphServiceState, error conversion, path normalization (toStoryIndexPath, storyIndexPathToAbsolutePath), reverseIndex serialization, GraphUpdate payload, and ModuleGraphFailureError.
Service definition
code/core/src/shared/open-service/services/module-graph/definition.ts
Introduces moduleGraphServiceDef with valibot schemas and queries/commands: getStoriesForFiles, getStatus, getGraphRevision, getLatestStoryChanges, applyGraphSnapshot, applyGraphUpdate, bumpGraphRevision, setStatus.
Service registration and runtime
code/core/src/shared/open-service/services/module-graph/server.ts
Adds changeDetectionAdapterPromise/resolveChangeDetectionAdapter, registerModuleGraphService wiring, settlement barrier, and starts a ModuleGraphEngine that mirrors engine snapshots/updates into service commands.
Service tests & helpers
code/core/src/shared/open-service/services/module-graph/server.test.ts, module-graph.test-helpers.ts
Comprehensive tests for initial state, snapshot/update semantics, path normalization, revisions/subscriptions, and wiring; adds test helpers (createDeferred, createStoryIndex, createMockAdapter, buildReverseIndex, installDependencyGraphMocks).

ModuleGraphEngine Refactor

Layer / File(s) Summary
Engine implementation
code/core/src/shared/open-service/services/module-graph/engine/module-graph-engine.ts
Renames StoryDependencyGraphService → ModuleGraphEngine; replaces storyIndexGeneratorPromise with getIndex(); emits onSnapshot/onUpdate/onStoryIndexInvalidated; updates buffering, patch queue, startup/unavailable/error handling, and reconciliation flows.
Engine internals and dependency-graph fixes
code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/*
Normalize import paths/casing, adjust type-only imports, rename constant MAX_BREADTH_FIRST_SEARCH_DEPTH, and update JSDoc/comment phrasing.
Engine tests
code/core/src/shared/open-service/services/module-graph/engine/module-graph-engine.test.ts
Refactor tests to assert new callbacks (onSnapshot/onUpdate/onStoryIndexInvalidated), updated error/unavailable behaviors, and revised teardown expectations.

ChangeDetectionService Refactor

Layer / File(s) Summary
Service integration with module-graph
code/core/src/core-server/change-detection/change-detection-service.ts
Subscribe to core/module-graph queries (getStatus, getStoriesForFiles, getGraphRevision); add errorLikeToError; change start signature to start(enabled); gate scans on module-graph ready; use getStoriesForFiles.loaded({ files }) for file→story mapping; unsubscribe on dispose.
Tests and test helpers
code/core/src/core-server/change-detection/change-detection.test-helpers.ts, code/core/src/core-server/change-detection/change-detection-service.test.ts
Add installModuleGraphQueryMock(engine) to mock the open-service query surface; update createWiredChangeDetection to return { service, graph, moduleGraphMock }; update tests to call service.start(enabled), remove explicit graph.dispose() where appropriate, and use module-graph mock methods for invalidation/status scenarios.

Core-server, Presets, Dev-server, Docgen, and index-json

Layer / File(s) Summary
Dev-server wiring
code/core/src/core-server/dev-server.ts
Construct ChangeDetectionService directly, resolve adapter via resolveChangeDetectionAdapter(adapter), and call changeDetectionService.start(features.changeDetection) without passing adapter; remove graph disposal from dev-server teardown.
Presets/service registration
code/core/src/core-server/presets/common-preset.ts
Resolve storyIndexGenerator earlier and register registerModuleGraphService in services preset with getIndex backed by the resolved generator; reuse for docgen.
Docgen integration
code/core/src/shared/open-service/services/docgen/server.ts
Add optional workingDir option; register docgen service runtime; subscribe to module-graph latest story changes and refresh existing docgen state for affected components.
Index JSON route
code/core/src/core-server/utils/index-json.ts
Remove onStoryIndexInvalidated callback parameter; invalidation now emits STORY_INDEX_INVALIDATED to channel only; update tests accordingly.
Core-server exports
code/core/src/core-server/index.ts
Redirect change-detection re-exports to granular modules and shared module-graph type modules; remove StoryDependencyGraphService and active-service-registry re-exports.

Barrel removal and type-source consolidation

Layer / File(s) Summary
Remove change-detection barrels
code/core/src/core-server/change-detection/index.ts, dependency-graph/index.ts, parser-registry/index.ts, adapters/index.ts
Remove aggregated re-exports to consolidate public API surfaces and move types to shared module-graph engine modules.
Type updates
code/core/src/types/modules/core-common.ts
Point Builder.changeDetectionAdapter and StorybookConfigRaw.experimental_importParsers types at the shared module-graph engine type locations.

Misc, docs, and test import fixes

Layer / File(s) Summary
README and guidance
code/core/src/shared/open-service/README.md
Add guidance preferring await service.queries.<query>.loaded(input) for cross-service reads to avoid staleness from background loads.
Test path/casing fixes
multiple engine/* and parser-registry tests
Fix import specifiers/casing for Vitest in dependency-graph and parser-registry tests, and update a few comment strings and constants.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • storybookjs/storybook#34369: Related changes to ChangeDetectionService and module-graph integration (overlaps on module-graph wiring semantics).
  • storybookjs/storybook#35009: Adds/uses the active StoryDependencyGraphService registry that this PR removes — strong overlap/conflict on active-service-registry.
  • storybookjs/storybook#34498: Overlapping edits to dev-server change-detection startup/wiring; likely to touch similar integration points.

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

@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: 3

🧹 Nitpick comments (1)
code/core/src/core-server/change-detection/change-detection.test-helpers.ts (1)

41-48: 💤 Low value

Hardcoded /repo path in test helper may cause path mismatch.

The toStoryIndexPath(storyFile, '/repo') uses a hardcoded working directory. If a test provides a different workingDir to createWiredChangeDetection, the mock's path conversion won't match the service's expectations.

♻️ Consider parameterizing the workingDir

The installModuleGraphQueryMock could accept an optional workingDir parameter to align with the test's working directory, though the current approach works for existing test fixtures using /repo.

🤖 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/core-server/change-detection/change-detection.test-helpers.ts`
around lines 41 - 48, getStoriesForFiles uses a hardcoded workingDir when
calling toStoryIndexPath, causing mismatches if tests call
createWiredChangeDetection with a different workingDir; update the
installModuleGraphQueryMock (or the helper that provides getStoriesForFiles) to
accept an optional workingDir parameter and pass that through to
getStoriesForFiles so toStoryIndexPath(storyFile, workingDir) is used instead of
'/repo', and update any callers of installModuleGraphQueryMock in tests to
forward their createWiredChangeDetection workingDir where needed.
🤖 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/open-service/services/module-graph/definition.ts`:
- Around line 171-176: The handlers that set or bump graphRevision (e.g., the
anonymous handler shown, applyGraphSnapshot, and bumpGraphRevision) update
state.graphRevision but do not clear state.latestChangedStoryFiles, causing
getLatestStoryChanges to return stale files; update each place that increments
or replaces the revision (the handler that sets status='ready',
applyGraphSnapshot, and bumpGraphRevision) to also reset
state.latestChangedStoryFiles to an empty collection (e.g., [] or {}) inside the
same ctx.self.setState callback so the newest revision has no leftover
changed-file entries.

In
`@code/core/src/shared/open-service/services/module-graph/engine/module-graph-engine.ts`:
- Around line 99-117: mirrorUpdate currently only uses the post-patch
reverseIndex lookup and can miss stories whose edges were removed by the patch;
change mirrorUpdate to accept (or obtain) the pre-patch dependents and union
them with the post-patch lookup results before computing bumpedStoryFiles.
Specifically, update the mirrorUpdate signature to accept an extra
prePatchDependents (Set<string> or string[]), or capture lookup(normalized)
before applying the patch in the caller and pass it in; then merge that set with
the current this.reverseIndex.lookup(normalized) results plus this.storyFiles
into bumpedStoryFiles, and keep the rest of the logic (options.onUpdate,
reverseIndexToStoriesByFile, toStoryIndexPath) unchanged so both pre- and
post-patch dependents are included.
- Around line 237-239: Register the adapter.onStartupFailure handler before
performing any async startup work so early failures aren't missed: move the
attachment of adapter.onStartupFailure?.(...) to occur before calling
getResolveConfig(), getIndex(), or build(), and ensure it forwards event.reason
and event.error to this.options.onUnavailable so any startup error during those
async calls will be emitted.

---

Nitpick comments:
In `@code/core/src/core-server/change-detection/change-detection.test-helpers.ts`:
- Around line 41-48: getStoriesForFiles uses a hardcoded workingDir when calling
toStoryIndexPath, causing mismatches if tests call createWiredChangeDetection
with a different workingDir; update the installModuleGraphQueryMock (or the
helper that provides getStoriesForFiles) to accept an optional workingDir
parameter and pass that through to getStoriesForFiles so
toStoryIndexPath(storyFile, workingDir) is used instead of '/repo', and update
any callers of installModuleGraphQueryMock in tests to forward their
createWiredChangeDetection workingDir where needed.
🪄 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: f503b29f-7eea-4d79-b10d-fd9b95ebf43f

📥 Commits

Reviewing files that changed from the base of the PR and between 58b8104 and 7c1a334.

📒 Files selected for processing (47)
  • code/core/src/core-server/change-detection/active-service-registry.ts
  • code/core/src/core-server/change-detection/adapters/index.ts
  • code/core/src/core-server/change-detection/change-detection-service.test.ts
  • code/core/src/core-server/change-detection/change-detection-service.ts
  • code/core/src/core-server/change-detection/change-detection.test-helpers.ts
  • code/core/src/core-server/change-detection/dependency-graph/index.ts
  • code/core/src/core-server/change-detection/index.ts
  • code/core/src/core-server/change-detection/parser-registry/index.ts
  • code/core/src/core-server/dev-server.ts
  • code/core/src/core-server/index.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/core-server/utils/index-json.test.ts
  • code/core/src/core-server/utils/index-json.ts
  • code/core/src/shared/open-service/README.md
  • 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/module-graph/definition.ts
  • code/core/src/shared/open-service/services/module-graph/engine/adapters/types.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/adapters/types.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/dependency-graph-builder.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/dependency-graph-builder.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/incremental-patch.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/incremental-patcher.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/parse-resolve-cache.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/parse-resolve-cache.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/resolver-factory.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/resolver-factory.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/reverse-index.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/reverse-index.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/scope.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/types.ts
  • code/core/src/shared/open-service/services/module-graph/engine/dependency-graph/walk-from-story.ts
  • code/core/src/shared/open-service/services/module-graph/engine/module-graph-engine.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/module-graph-engine.ts
  • code/core/src/shared/open-service/services/module-graph/engine/parser-registry/builtins.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/parser-registry/builtins.ts
  • code/core/src/shared/open-service/services/module-graph/engine/parser-registry/mdx-parse.ts
  • code/core/src/shared/open-service/services/module-graph/engine/parser-registry/parser-registry.test.ts
  • code/core/src/shared/open-service/services/module-graph/engine/parser-registry/parser-registry.ts
  • code/core/src/shared/open-service/services/module-graph/engine/parser-registry/types.ts
  • code/core/src/shared/open-service/services/module-graph/errors.ts
  • code/core/src/shared/open-service/services/module-graph/module-graph.test-helpers.ts
  • code/core/src/shared/open-service/services/module-graph/server.test.ts
  • code/core/src/shared/open-service/services/module-graph/server.ts
  • code/core/src/shared/open-service/services/module-graph/story-files.ts
  • code/core/src/shared/open-service/services/module-graph/types.ts
  • code/core/src/types/modules/core-common.ts
💤 Files with no reviewable changes (7)
  • code/core/src/core-server/utils/index-json.ts
  • code/core/src/core-server/change-detection/adapters/index.ts
  • code/core/src/core-server/change-detection/parser-registry/index.ts
  • code/core/src/core-server/change-detection/active-service-registry.ts
  • code/core/src/core-server/utils/index-json.test.ts
  • code/core/src/core-server/change-detection/dependency-graph/index.ts
  • code/core/src/core-server/change-detection/index.ts

Keep selectComponentEntriesByComponentId from the docgen phase-3 base
while preserving module-graph subscription reactivity in docgen.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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.

Caution

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

⚠️ Outside diff range comments (1)
code/core/src/core-server/presets/common-preset.ts (1)

333-367: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep story-index resolution lazy in services.

options.presets.apply('storyIndexGenerator') initializes the full generator immediately, so manager-only / ignorePreview runs still build the story index before the guard on Lines 345-348 is checked. That defeats the skip documented there and can bring back unnecessary work or preview-side failures in flows that intentionally avoid preview output.

Suggested change
-  // `presets.apply` flattens the generator preset's returned promise, so this is the resolved
-  // generator, not a promise.
-  const storyIndexGenerator =
-    await options.presets.apply<StoryIndexGenerator>('storyIndexGenerator');
+  const getIndex = async () => {
+    const storyIndexGenerator =
+      await options.presets.apply<StoryIndexGenerator>('storyIndexGenerator');
+    return storyIndexGenerator.getIndex();
+  };

   registerModuleGraphService({
     channel: options.channel,
-    getIndex: () => storyIndexGenerator.getIndex(),
+    getIndex,
     workingDir: process.cwd(),
     presets: options.presets,
   });
@@
     registerDocgenService({
-      getIndex: () => storyIndexGenerator.getIndex(),
+      getIndex,
       provider,
       workingDir: process.cwd(),
     });
🤖 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/core-server/presets/common-preset.ts` around lines 333 - 367,
The code eagerly calls options.presets.apply('storyIndexGenerator') (symbol:
storyIndexGenerator) which forces full story-index init even when preview is
skipped; instead make resolution lazy: remove the top-level await and replace it
with a lazily-resolving accessor (e.g. a getStoryIndex or
storyIndexGeneratorPromise resolver) that only calls
options.presets.apply('storyIndexGenerator') the first time getIndex is invoked,
cache the resolved generator, and then have registerModuleGraphService and
registerDocgenService use that accessor (replace getIndex: () =>
storyIndexGenerator.getIndex() with getIndex: () => getStoryIndex().then(g =>
g.getIndex()) or a sync wrapper that awaits internally) so the generator is only
created when needed and the features/ignorePreview guard can skip creating it
entirely.
🤖 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.

Outside diff comments:
In `@code/core/src/core-server/presets/common-preset.ts`:
- Around line 333-367: The code eagerly calls
options.presets.apply('storyIndexGenerator') (symbol: storyIndexGenerator) which
forces full story-index init even when preview is skipped; instead make
resolution lazy: remove the top-level await and replace it with a
lazily-resolving accessor (e.g. a getStoryIndex or storyIndexGeneratorPromise
resolver) that only calls options.presets.apply('storyIndexGenerator') the first
time getIndex is invoked, cache the resolved generator, and then have
registerModuleGraphService and registerDocgenService use that accessor (replace
getIndex: () => storyIndexGenerator.getIndex() with getIndex: () =>
getStoryIndex().then(g => g.getIndex()) or a sync wrapper that awaits
internally) so the generator is only created when needed and the
features/ignorePreview guard can skip creating it entirely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19e86824-5cc1-46b4-8e11-ed112a32806c

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1a334 and 3930029.

📒 Files selected for processing (4)
  • code/core/src/core-server/presets/common-preset.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/types/modules/core-common.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • code/core/src/shared/open-service/services/docgen/server.ts
  • code/core/src/shared/open-service/services/docgen/server.test.ts
  • code/core/src/types/modules/core-common.ts

Base automatically changed from claude/docgen-phase-3-rcm-provider to next June 5, 2026 18:32
@JReinhold

JReinhold commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

Manual QA — Change Detection (internal UI Storybook)

QA run against the internal UI Storybook (code/.storybook, port 6006) on branch jeppe-cursor/4b72cdff after merging claude/docgen-phase-3-rcm-provider. Sandboxes were not used (gitignored / won't reflect this branch).

Environment

Field Value
Storybook URL http://localhost:6006
Primary story controls-controlspanel--ref-story-controls-load
Test files ControlsPanel.tsx, ControlsPanel.stories.tsx, SaveStory.tsx (transitive)
Config features.changeDetection: true, core.changeDetection: true in code/.storybook/main.ts

Overall verdict: PASS

Per-step results

Step Result Notes
1. Clean git tree PASS Clean at start and end
2. Change detection enabled PASS Already configured in internal UI
3. Start dev server PASS yarn storybook:ui
4. Clean sidebar (no unexpected statuses) PASS No change-detection badges on clean tree
5. Edit component → Modified PASS Modified dots on Controls Panel + Ref Story Controls Load after expanding sidebar + activating review filter
6. Edit transitive dep → Affected PASS* Status store shows status-value:affected on dependent story; Affected icon is not rendered in sidebar (Tree.tsx hides it by design)
7. Add story file → index update PASS New entry in index.json within ~1–3s; New status on component + story
8. Revert edits → statuses clear PASS Statuses cleared; index entry removed
9. Second edit (incremental) PASS Modified status returns without restart

Step 4

step4-clean-sidebar

Step 5
step5-modified

Step 5 expanded
step5-modified-expanded

Step 6
step6-affected-store

Step 7
step7-new-story

Step 8
step8-clean-revert

Step 9
step9-incremental

QA tip for reviewers

Status icons are only visible when the sidebar tree is expanded (controlsControls Panel) and, for Modified, the Review … stories switch is active.

Screenshots

Screenshots could not be embedded automatically (GitHub gist/API does not accept binary uploads via CLI). Please attach the files from the local qa-screenshots/ folder on the PR branch worktree:

Step File
4 — clean sidebar qa-screenshots/step4-clean-sidebar.png
5 — Modified qa-screenshots/step5-modified.png
6 — transitive edit (SaveStory) qa-screenshots/step6-affected-store.png
7 — new story qa-screenshots/step7-new-story.png
8 — reverted / clean qa-screenshots/step8-clean-revert.png
9 — incremental update qa-screenshots/step9-incremental.png

Local path: qa-screenshots/ at the repo root of the PR worktree.

Observations

  1. Affected status is computed and stored correctly but the sidebar does not render an Affected badge (intentional UI behavior in Tree.tsx).
  2. Modified badges require the review filter to be active.
  3. Brief HMR error if you delete a story while the browser is still on its URL — resolves after navigating to a valid story.

Per-story revision stamps let scoped subscribers react only to relevant graph changes, while watch-all no longer advances on irrelevant file events.

Co-authored-by: Cursor <cursoragent@cursor.com>

@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.

🧹 Nitpick comments (2)
code/core/src/shared/open-service/services/module-graph/server.test.ts (2)

360-375: ⚡ Quick win

Consider strengthening the assertion to match the comment.

The test comment (line 373) claims "The snapshot is a no-op for the revision," but the assertion seen.at(-1).toBe(1) would pass whether the snapshot triggered a notification of 0 or not. To confirm the snapshot behavior, assert the full sequence.

🧪 Recommended: Assert the exact notification sequence
-      // The snapshot is a no-op for the revision; the update advances it to 1.
-      expect(seen.at(-1)).toBe(1);
+      // The snapshot is a no-op for the revision; only the update triggers a notification of 1.
+      expect(seen).toEqual([1]);
🤖 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/module-graph/server.test.ts`
around lines 360 - 375, The test should assert the full notification sequence
from runtime.queries.getGraphRevision.subscribe to ensure applyGraphSnapshot did
not emit a revision; after calling registerBareModuleGraph(), subscribe and push
revisions into seen, then call runtime.commands.applyGraphSnapshot(...) and
runtime.commands.applyGraphUpdate(...), and replace the final loose assertion
with a strict equality assertion that seen equals [1] (i.e., assert the array
contains only the single notification from applyGraphUpdate) to confirm the
snapshot was a no-op; reference the runtime variable, registerBareModuleGraph(),
runtime.queries.getGraphRevision.subscribe, applyGraphSnapshot, and
applyGraphUpdate when making the change.

377-412: ⚡ Quick win

Strengthen the assertion to verify selectivity.

The test validates that a scoped subscriber is notified "only when its story is bumped," but the assertion seen.at(-1).toBe(2) only checks the final value. This would pass even if the subscriber were spuriously notified when Card was bumped (e.g., seen = [0, 1, 2]).

🧪 Recommended: Assert the full notification sequence
-      expect(seen.at(-1)).toBe(2);
+      // Verify Card bump (revision 1) did not trigger notification; only Button bump (revision 2) did.
+      expect(seen).toEqual([2]);
🤖 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/module-graph/server.test.ts`
around lines 377 - 412, The test currently only checks the final seen value;
strengthen it by asserting the full notification sequence so the subscriber
wasn't notified on the Card bump. After subscribing via
runtime.queries.getGraphRevision.subscribe (and after the initial
applyGraphSnapshot and the two applyGraphUpdate calls), replace the final
expect(seen.at(-1)).toBe(2) with an assertion that the entire seen array equals
the expected sequence (e.g., [1, 2]) so you verify there was only the initial
notification and the Button bump notification and no spurious notification for
Card; locate this change around the test using registerBareModuleGraph,
runtime.commands.applyGraphSnapshot, and runtime.commands.applyGraphUpdate.
🤖 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.

Nitpick comments:
In `@code/core/src/shared/open-service/services/module-graph/server.test.ts`:
- Around line 360-375: The test should assert the full notification sequence
from runtime.queries.getGraphRevision.subscribe to ensure applyGraphSnapshot did
not emit a revision; after calling registerBareModuleGraph(), subscribe and push
revisions into seen, then call runtime.commands.applyGraphSnapshot(...) and
runtime.commands.applyGraphUpdate(...), and replace the final loose assertion
with a strict equality assertion that seen equals [1] (i.e., assert the array
contains only the single notification from applyGraphUpdate) to confirm the
snapshot was a no-op; reference the runtime variable, registerBareModuleGraph(),
runtime.queries.getGraphRevision.subscribe, applyGraphSnapshot, and
applyGraphUpdate when making the change.
- Around line 377-412: The test currently only checks the final seen value;
strengthen it by asserting the full notification sequence so the subscriber
wasn't notified on the Card bump. After subscribing via
runtime.queries.getGraphRevision.subscribe (and after the initial
applyGraphSnapshot and the two applyGraphUpdate calls), replace the final
expect(seen.at(-1)).toBe(2) with an assertion that the entire seen array equals
the expected sequence (e.g., [1, 2]) so you verify there was only the initial
notification and the Button bump notification and no spurious notification for
Card; locate this change around the test using registerBareModuleGraph,
runtime.commands.applyGraphSnapshot, and runtime.commands.applyGraphUpdate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc560138-c79c-4514-b83f-35556844404d

📥 Commits

Reviewing files that changed from the base of the PR and between 3930029 and 3e6d6c6.

📒 Files selected for processing (5)
  • code/core/src/core-server/change-detection/change-detection-service.test.ts
  • code/core/src/core-server/change-detection/change-detection.test-helpers.ts
  • code/core/src/shared/open-service/services/module-graph/definition.ts
  • code/core/src/shared/open-service/services/module-graph/server.test.ts
  • code/core/src/shared/open-service/services/module-graph/types.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • code/core/src/shared/open-service/services/module-graph/types.ts
  • code/core/src/shared/open-service/services/module-graph/definition.ts
  • code/core/src/core-server/change-detection/change-detection.test-helpers.ts
  • code/core/src/core-server/change-detection/change-detection-service.test.ts

JReinhold and others added 3 commits June 9, 2026 08:57
Resolve docgen/module-graph conflicts after the service-registration split, store workingDir in module-graph state now that query handlers cannot be overridden at registration, and address CodeRabbit engine feedback for pre-patch dependents and early startup failure handling.

Co-authored-by: Cursor <cursoragent@cursor.com>
Index reconciliation and snapshot baseline must not leave prior story-file change lists attached to a newer graph revision.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move getStatus load into the service definition via a settlement bridge, and register module-graph before docgen in manifest tests that exercise docgen static output.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread code/core/src/shared/open-service/services/module-graph/definition.ts Outdated
Comment thread code/core/src/shared/open-service/services/module-graph/server.ts Outdated
…and.

getStatus.load now invokes a server-registered command so manager runtimes route settlement over the open-service channel instead of relying on a module-level promise that never resolves outside the dev server.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread code/core/src/shared/open-service/services/docgen/server.test.ts
Comment thread code/core/src/shared/open-service/services/docgen/server.ts Outdated
JReinhold and others added 3 commits June 9, 2026 21:09
Ignore Playwright output dirs in Vite watch so trace writes do not reload the dev server during internal e2e, and harden preview-ready waits for cold starts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid repeated index scans when mapping bumped story files to component IDs, and add focused unit coverage for the getLatestStoryChanges query contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JReinhold JReinhold mentioned this pull request Jun 9, 2026
54 tasks
@JReinhold JReinhold merged commit 01b3bc0 into next Jun 9, 2026
143 checks passed
@JReinhold JReinhold deleted the jeppe-cursor/4b72cdff branch June 9, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal Run our default set of CI jobs (choose this for most PRs). core maintenance User-facing maintenance tasks qa:needed Pull Requests that will need manual QA prior to release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants