Skip to content

feat(docgen): re-extract docgen on source change (server-side)#34998

Closed
JReinhold wants to merge 4 commits into
jeppe-cursor/docgen-subscription-referential-equality-5a81from
jeppe-cursor/docgen-invalidation-fbe7
Closed

feat(docgen): re-extract docgen on source change (server-side)#34998
JReinhold wants to merge 4 commits into
jeppe-cursor/docgen-subscription-referential-equality-5a81from
jeppe-cursor/docgen-invalidation-fbe7

Conversation

@JReinhold

@JReinhold JReinhold commented May 31, 2026

Copy link
Copy Markdown
Contributor

Closes #

What I did

Server-side docgen invalidation (HMR) for the experimental docgen server: when a watched source file changes, the already-extracted docgen for affected components is re-extracted so core/docgen never serves stale data — with no flash to empty for current readers and no stale data for later navigations.

This is the server-side slice only (no manager transport, which doesn't exist yet). Plan: Server-side docgen invalidation (HMR) — plan (subpage of SB docgen server).

Design

Two halves:

  • Propagation is already solved by the open-service runtime: once a command overwrites state.components[componentId], deep-signal reactivity re-emits to subscribers (deduped by value), and future reads get fresh data via the normal sync-read + load. So we only have to update server state correctly.
  • Detection is centralized. Providers stay pure ((input) => Promise<DocgenPayload | undefined>); they get no change-notification API. The existing oxc dependency graph in change-detection/ maps a changed file to its affected story files.

Invalidation policy = re-extract-if-present (locked decision, "option b"): on invalidation for a component id, if it is already in state, re-run extractDocgen (atomic overwrite → reactive emit, deduped); if absent, do nothing (lazy on next read); on extraction error, keep last-good.

Changes

  • New core/module-graph open service — a thin facade that translates affected story files into component ids, backed by the change-detection dependency graph. Holds a monotonic revision + the latest affected component ids in state, exposed via the subscribable getLastAffected query.
  • core/docgen handleSourceChange command — re-extract-if-present + keep-last-good.
  • Open-service-native wiringcore/docgen subscribes to core/module-graph's getLastAffected (connectDocgenToModuleGraph, wired in the services preset) and re-extracts affected components. The dev server only feeds the module graph; it has no knowledge of docgen. The revision bump ensures repeated changes to the same component still emit (value-dedup doesn't swallow them). This is an explicit subscription because re-extraction is async and the runtime fires a query's load only once — a future reactive-load primitive would let getDocgen depend on the revision directly and drop the subscription.
  • ChangeDetectionService (see separated section in the plan; owned by another stakeholder) — two additive, gated changes:
    • onInvalidate callback emitting affected story files (fires on add/change/unlink, independent of git diff and of the "dep set unchanged" patch early-return).
    • publishStatuses: false graph-only mode so the dependency graph can power docgen without surfacing "review changes" sidebar statuses. Existing behavior is unchanged when publishStatuses is true (the default).
  • Composition root (dev-server.ts, common-preset.ts) — run the graph when features.changeDetection || features.experimentalDocgenServer; the dev server's onInvalidate calls only module-graph.resolveAffectedComponents (best-effort).

Non-regression

Builder-agnostic and Webpack-supported: both builder-vite (chokidar server.watcher) and builder-webpack5 (compiler.hooks.watchRun) ship change-detection adapters, so this does not regress Webpack docgen updates. The change is additive and behind experimentalDocgenServer.

Checklist for Contributors

Testing

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

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

Manual testing

The change is server-side only — there is no manager transport yet, so there is no UI to exercise, and the phase-1 mock provider is path-deterministic (its output doesn't change on edits). Validation is via automated tests plus a live dev-server smoke test confirming a file edit drives the reactive chain (onInvalidatemodule-graph → docgen subscription → handleSourceChange):

  • core/docgen handleSourceChange: re-extracts present components and pushes the new value to a subscriber (content-sensitive provider); ignores absent components; keeps last-good on provider error.
  • core/module-graph: story files → component ids mapping; revision bump.
  • Open-service-native integration test: register both services, connectDocgenToModuleGraph, feed the module graph, and assert docgen re-extracts + the subscriber is notified reactively (incl. repeated same-component changes via the revision).
  • ChangeDetectionService: onInvalidate fires with affected story files; graph-only mode publishes no statuses and never calls git.

Commands run: yarn test --run open-service change-detection (237 passed), yarn nx check core (no type errors), lint + format.

Documentation

  • Add or update documentation reflecting your changes (experimental; deferred)

Checklist for Maintainers

  • When this PR is ready for testing, add ci:normal, ci:merged or ci:daily.
  • Make sure this PR contains one of the labels below: feature request.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Experimental docgen server: automatic, best-effort docgen re-extraction when related source/story files change.
    • Module-graph invalidation service: maps changed story files to affected component IDs and publishes revisioned invalidations.
    • Graph-only mode for change detection: emits invalidation notifications without publishing status entries or relying on git diffs.
  • Tests

    • Added end-to-end and unit tests covering invalidation paths, re-extraction behavior, and graph-only semantics.

…vice

Server-side docgen invalidation: when a watched source file changes, the
already-extracted docgen for affected components is re-extracted so the
core/docgen service never serves stale data, with no flash to empty for
current readers.

- Add core/module-graph open service: a thin facade that maps affected
  story files to component ids, backed by the existing change-detection
  dependency graph.
- Add core/docgen handleSourceChange command: re-extract-if-present
  (skip components not in state), keep-last-good on extraction error.
- ChangeDetectionService: add an additive onInvalidate callback and a
  publishStatuses=false graph-only mode so the dependency graph can power
  docgen without surfacing review-changes statuses; existing status
  behavior is unchanged when publishStatuses is true.
- Wire it in dev-server/common-preset: run the graph when changeDetection
  OR experimentalDocgenServer is enabled (server-side only; no manager
  transport).

Co-authored-by: Jeppe Reinhold <JReinhold@users.noreply.github.com>
@cursor

cursor Bot commented May 31, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@JReinhold JReinhold changed the base branch from main to jeppe-cursor/docgen-subscription-referential-equality-5a81 May 31, 2026 12:14
@github-actions

github-actions Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor
Fails
🚫

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

🚫

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

🚫 PR title must be in the format of "Area: Summary", With both Area and Summary starting with a capital letter Good examples: - "Docs: Describe Canvas Doc Block" - "Svelte: Support Svelte v4" Bad examples: - "add new api docs" - "fix: Svelte 4 support" - "Vue: improve docs"
Warnings
⚠️

This PR targets jeppe-cursor/docgen-subscription-referential-equality-5a81. The default branch for contributions is next. Please make sure you are targeting the correct branch.

Generated by 🚫 dangerJS against 2e0f7b2

cursoragent and others added 2 commits May 31, 2026 12:19
…-referential-equality-5a81' into jeppe-cursor/docgen-invalidation-fbe7

Co-authored-by: Jeppe Reinhold <JReinhold@users.noreply.github.com>
…back

Aligns the new module-graph service with the setState arg rename from the
base branch (draft -> state).

Co-authored-by: Jeppe Reinhold <JReinhold@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a module-graph open service to map changed story files to component IDs, wires it to the docgen open service to re-extract affected components, and extends ChangeDetectionService with a graph-only mode and an onInvalidate callback that emits affected story files.

Changes

Source Change Invalidation Pipeline

Layer / File(s) Summary
Module-graph service: types, schema, and definition
code/core/src/shared/open-service/services/module-graph/types.ts, code/core/src/shared/open-service/services/module-graph/definition.ts
Defines ModuleGraphInvalidation/ModuleGraphServiceState and Valibot schemas; declares core/module-graph service with getLastAffected and resolveAffectedComponents.
Module-graph service: server registration and implementation
code/core/src/shared/open-service/services/module-graph/server.ts
Implements registerModuleGraphService, path normalization helper, and resolveAffectedComponents command that maps story-file paths to component IDs, increments revision, and persists lastAffected.
Module-graph service: tests
code/core/src/shared/open-service/services/module-graph/server.test.ts
Tests mapping, deduplication, ignoring non-indexed stories, and revision tracking for resolveAffectedComponents.
ChangeDetectionService: graph-only mode and invalidation callback
code/core/src/core-server/change-detection/ChangeDetectionService.ts
Adds publishStatuses?: boolean and onInvalidate?: (affectedStoryFiles: string[]) => void; skips git/baseline wiring when publishing is disabled; computes affected story files via reverse index before/after patches and emits them via onInvalidate.
ChangeDetectionService: graph-only mode and invalidation tests
code/core/src/core-server/change-detection/ChangeDetectionService.test.ts
Tests that onInvalidate receives affected story files and that graph-only mode suppresses status publication and git-diff interactions while still emitting invalidations.
Docgen service: handleSourceChange command contract
code/core/src/shared/open-service/services/docgen/definition.ts
Adds handleSourceChangeInputSchema and registers handleSourceChange command on docgenServiceDef accepting { componentIds: string[] }.
Docgen service: handleSourceChange handler implementation
code/core/src/shared/open-service/services/docgen/server.ts
Implements handleSourceChange to re-extract only components present in state, preserve existing payloads on extraction failure, log warnings, and returns void; wires module-graph subscriptions to invoke the command.
Docgen service: handleSourceChange handler tests & integration
code/core/src/shared/open-service/services/docgen/server.test.ts, code/core/src/shared/open-service/services/docgen/invalidation.integration.test.ts
Adds unit tests for command behavior and integration tests that validate module-graph-driven invalidation, selective re-extraction, subscriber notifications, and repeat-emission behavior.
Dev server: experimental docgen invalidation wiring
code/core/src/core-server/dev-server.ts
Adds experimentalDocgenServer flag and recordAffectedStoryFiles helper that queries core/module-graph and triggers docgen updates; sets ChangeDetectionService.onInvalidate only when experimental docgen is enabled and computes needsChangeDetectionGraph for startup gating.
Common preset: module-graph service registration
code/core/src/core-server/presets/common-preset.ts
Registers module-graph service (with getIndex and workingDir) before docgen registration and connects docgen to the module graph when experimental docgen is active.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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: 2

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

142-227: ⚡ Quick win

Consider mocking the logger for better test isolation and guideline compliance.

The new test suite exercises code that calls logger.warn() (server.ts line 92), but the logger is not mocked. According to the coding guidelines, file dependencies should be mocked using vi.mock() with spy: true. Mocking the logger would:

  1. Allow verification that logger.warn() is called in the error case (lines 198-225)
  2. Prevent console output during test runs
  3. Improve test isolation
  4. Align with the guideline: "Mock all required dependencies that the test subject uses"

As per coding guidelines, mocks should be placed at the top of the file before test cases with the spy: true option, and accessed via vi.mocked().

Example implementation

Add at the top of the file (after imports, before afterEach):

+vi.mock('../../../../node-logger/index.ts', () => ({
+  spy: true,
+}));
+
+import { logger } from '../../../../node-logger/index.ts';
+
 afterEach(() => {
   clearRegistry();
+  vi.mocked(logger.warn).mockClear();
 });

Then verify logging in the error test:

     shouldThrow = false;
     expect(service.queries.getDocgen({ componentId: 'button' })).toMatchObject({
       description: 'good',
     });
+
+    expect(vi.mocked(logger.warn)).toHaveBeenCalledWith(
+      expect.stringContaining('failed to re-extract docgen for "button"')
+    );
   });
🤖 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 142 - 227, Mock the logger used by the service so logger.warn calls are
spied and do not print during tests: add a vi.mock(...) with { spy: true } near
the top of the test file for the module that exports logger, then use
vi.mocked(logger).warn (or vi.mocked(logger)) in the failing re-extraction test
("keeps the last-good payload when re-extraction throws") to assert it was
called (and/or to silence output); ensure the mock is created before
registerDocgenService is invoked so the service picks up the mocked logger.
🤖 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/change-detection/ChangeDetectionService.ts`:
- Around line 572-576: The onInvalidate callback invocation can throw
synchronously and prevent scheduleScan from running, so wrap the call to
this.onInvalidate([...affectedStoryFiles]) in a try/catch and ensure
this.scheduleScan(this.debounceMs) runs in a finally block; specifically update
the block that checks if (this.onInvalidate && affectedStoryFiles.size > 0)
inside handleFileChange to catch any error from this.onInvalidate (log or ignore
the error) and then always call this.scheduleScan(this.debounceMs) in finally so
the rescan remains best-effort even if onInvalidate fails.

In `@code/core/src/core-server/dev-server.ts`:
- Around line 94-97: The onInvalidate handler currently fire-and-forgets
invalidateDocgenForStoryFiles, causing overlapping handleSourceChange runs and
order-dependent cache races; change the handler to serialize invalidations by
awaiting invalidateDocgenForStoryFiles (or enqueueing calls) so each batch
completes before the next starts—update the onInvalidate callback that currently
calls invalidateDocgenForStoryFiles to return/await its promise (or implement a
simple promise queue around invalidateDocgenForStoryFiles/handleSourceChange) to
ensure sequential execution and stable docgen cache updates.

---

Nitpick comments:
In `@code/core/src/shared/open-service/services/docgen/server.test.ts`:
- Around line 142-227: Mock the logger used by the service so logger.warn calls
are spied and do not print during tests: add a vi.mock(...) with { spy: true }
near the top of the test file for the module that exports logger, then use
vi.mocked(logger).warn (or vi.mocked(logger)) in the failing re-extraction test
("keeps the last-good payload when re-extraction throws") to assert it was
called (and/or to silence output); ensure the mock is created before
registerDocgenService is invoked so the service picks up the mocked logger.
🪄 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: ddac0362-f0b4-4aba-9b2d-ece2bf9c3e78

📥 Commits

Reviewing files that changed from the base of the PR and between 4177f0b and eefc9d0.

📒 Files selected for processing (12)
  • code/core/src/core-server/change-detection/ChangeDetectionService.test.ts
  • code/core/src/core-server/change-detection/ChangeDetectionService.ts
  • code/core/src/core-server/dev-server.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/invalidation.integration.test.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/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/server.ts
  • code/core/src/shared/open-service/services/module-graph/types.ts

Comment on lines +572 to 576
if (this.onInvalidate && affectedStoryFiles.size > 0) {
this.onInvalidate([...affectedStoryFiles]);
}

this.scheduleScan(this.debounceMs);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard onInvalidate so callback failures cannot skip the rescan.

If onInvalidate throws synchronously here, handleFileChange() exits before Line 576. The surrounding patchQueue.catch(() => undefined) then swallows the rejection, so that file change is neither rescanned nor logged. Wrap the callback in its own try/catch and keep scheduleScan() in a finally so invalidation stays best-effort.

Suggested fix
-    if (this.onInvalidate && affectedStoryFiles.size > 0) {
-      this.onInvalidate([...affectedStoryFiles]);
-    }
-
-    this.scheduleScan(this.debounceMs);
+    try {
+      if (this.onInvalidate && affectedStoryFiles.size > 0) {
+        this.onInvalidate([...affectedStoryFiles]);
+      }
+    } catch (error) {
+      logger.warn(
+        `Change detection: onInvalidate failed for ${path}: ${error instanceof Error ? error.message : String(error)}`
+      );
+    } finally {
+      this.scheduleScan(this.debounceMs);
+    }
🤖 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/ChangeDetectionService.ts` around
lines 572 - 576, The onInvalidate callback invocation can throw synchronously
and prevent scheduleScan from running, so wrap the call to
this.onInvalidate([...affectedStoryFiles]) in a try/catch and ensure
this.scheduleScan(this.debounceMs) runs in a finally block; specifically update
the block that checks if (this.onInvalidate && affectedStoryFiles.size > 0)
inside handleFileChange to catch any error from this.onInvalidate (log or ignore
the error) and then always call this.scheduleScan(this.debounceMs) in finally so
the rescan remains best-effort even if onInvalidate fails.

Comment thread code/core/src/core-server/dev-server.ts
…scription

Replace the dev-server orchestration (which called module-graph then docgen)
with an open-service-native link: docgen subscribes to core/module-graph's
getLastAffected query and re-extracts affected components on change. The dev
server now only feeds the module graph (the one imperative edge from the
non-open-service change detector); it no longer references docgen.

The module-graph revision (bumped on every change) ensures repeated changes to
the same component still emit distinct values, so subscription value-dedup does
not swallow a repeat change.

This is an explicit subscription because re-extraction is async and the runtime
fires a query's load only once (it does not re-run load on a tracked-dependency
change). A future reactive-load primitive would let getDocgen depend on the
revision directly and drop this wiring.

Co-authored-by: Jeppe Reinhold <JReinhold@users.noreply.github.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.

♻️ Duplicate comments (1)
code/core/src/core-server/dev-server.ts (1)

88-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize invalidation batches.

This still fire-and-forgets a state-mutating async invalidation. If two source-change batches arrive close together, resolveAffectedComponents() and the downstream docgen refresh can complete out of order and leave the cache on an older extraction.

Suggested fix
+  let invalidationQueue = Promise.resolve();
+
   const changeDetectionService = new ChangeDetectionService({
     storyIndexGeneratorPromise,
     statusStore: getStatusStoreByTypeId(CHANGE_DETECTION_STATUS_TYPE_ID),
     workingDir,
@@
     onInvalidate: experimentalDocgenServer
       ? (affectedStoryFiles) => {
-          void recordAffectedStoryFiles(affectedStoryFiles);
+          invalidationQueue = invalidationQueue
+            .then(() => recordAffectedStoryFiles(affectedStoryFiles))
+            .catch(() => undefined);
         }
       : undefined,
   });
🤖 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/dev-server.ts` around lines 88 - 92, The
onInvalidate handler currently fire-and-forgets recordAffectedStoryFiles which
can run concurrently and reorder async state mutations; change the onInvalidate
callback to serialize batches by awaiting prior work before starting the next:
make recordAffectedStoryFiles return a Promise if it doesn't already, add a
small per-handler queue/lock (e.g., a pendingPromise or mutex) inside the
dev-server scope and have onInvalidate chain/await that pending promise before
invoking recordAffectedStoryFiles, and ensure resolveAffectedComponents()/docgen
refresh are awaited so each invalidation completes in order.
🧹 Nitpick comments (2)
code/core/src/shared/open-service/services/docgen/invalidation.integration.test.ts (2)

124-149: ⚡ Quick win

Make the repeat-emission test keep the payload constant.

Right now this changes description on every invalidation, so the subscriber would update even without the revision-based dedup bypass. Keeping the returned docgen payload stable and asserting the emission count grows would actually cover the behavior named in the test.

Suggested adjustment
-    let version = 1;
     const provider: DocgenProvider = async () => ({
       componentId: 'button',
       name: 'Button',
-      description: `v${version}`,
+      description: 'stable',
       props: [],
     });
@@
-    await vi.waitFor(() => expect(emitted.at(-1)).toBe('v1'));
+    await vi.waitFor(() => expect(emitted).toEqual(['stable']));
 
-    version = 2;
     await moduleGraph.commands.resolveAffectedComponents({ storyFiles: [absStoryFile('button')] });
-    await vi.waitFor(() => expect(emitted.at(-1)).toBe('v2'));
+    await vi.waitFor(() => expect(emitted).toHaveLength(2));
 
-    version = 3;
     await moduleGraph.commands.resolveAffectedComponents({ storyFiles: [absStoryFile('button')] });
-    await vi.waitFor(() => expect(emitted.at(-1)).toBe('v3'));
+    await vi.waitFor(() => expect(emitted).toHaveLength(3));
🤖 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/invalidation.integration.test.ts`
around lines 124 - 149, The test should keep the docgen payload identical across
invalidations and assert that the subscriber still re-emits; change the
DocgenProvider (the provider variable) to always return the same
description/props/name (e.g., description: 'stable') regardless of the version
counter, continue bumping version to simulate internal revision changes, then
replace the assertions that check emitted.at(-1) values with assertions that the
subscriber emission count increases (e.g., check emitted.length increments or
equals the expected number after each resolveAffectedComponents call) while
using the same subscribe call to docgen.queries.getDocgen and triggers via
moduleGraph.commands.resolveAffectedComponents.

111-116: 🏗️ Heavy lift

Replace the timer tick with a real synchronization point.

setTimeout(0) does not prove the module-graph → docgen subscription finished. This can pass even if handleSourceChange runs later and incorrectly calls the provider, so the test can miss regressions.

🤖 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/invalidation.integration.test.ts`
around lines 111 - 116, The test's final setTimeout(0) is an unreliable sync
point; replace it with a deterministic wait for the docgen
subscription/handleSourceChange to finish by awaiting a real observable or mock
invocation instead: after calling moduleGraph.commands.resolveAffectedComponents
and asserting getLastAffected, wait for the docgen provider to be invoked (or
for a completion promise/event exposed by the docgen
subscription/handleSourceChange) using vi.waitFor or awaiting that promise so
the test only continues once handleSourceChange has truly run and the provider
call (the action you care about) has occurred.
🤖 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.

Duplicate comments:
In `@code/core/src/core-server/dev-server.ts`:
- Around line 88-92: The onInvalidate handler currently fire-and-forgets
recordAffectedStoryFiles which can run concurrently and reorder async state
mutations; change the onInvalidate callback to serialize batches by awaiting
prior work before starting the next: make recordAffectedStoryFiles return a
Promise if it doesn't already, add a small per-handler queue/lock (e.g., a
pendingPromise or mutex) inside the dev-server scope and have onInvalidate
chain/await that pending promise before invoking recordAffectedStoryFiles, and
ensure resolveAffectedComponents()/docgen refresh are awaited so each
invalidation completes in order.

---

Nitpick comments:
In
`@code/core/src/shared/open-service/services/docgen/invalidation.integration.test.ts`:
- Around line 124-149: The test should keep the docgen payload identical across
invalidations and assert that the subscriber still re-emits; change the
DocgenProvider (the provider variable) to always return the same
description/props/name (e.g., description: 'stable') regardless of the version
counter, continue bumping version to simulate internal revision changes, then
replace the assertions that check emitted.at(-1) values with assertions that the
subscriber emission count increases (e.g., check emitted.length increments or
equals the expected number after each resolveAffectedComponents call) while
using the same subscribe call to docgen.queries.getDocgen and triggers via
moduleGraph.commands.resolveAffectedComponents.
- Around line 111-116: The test's final setTimeout(0) is an unreliable sync
point; replace it with a deterministic wait for the docgen
subscription/handleSourceChange to finish by awaiting a real observable or mock
invocation instead: after calling moduleGraph.commands.resolveAffectedComponents
and asserting getLastAffected, wait for the docgen provider to be invoked (or
for a completion promise/event exposed by the docgen
subscription/handleSourceChange) using vi.waitFor or awaiting that promise so
the test only continues once handleSourceChange has truly run and the provider
call (the action you care about) has occurred.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 189fe21e-7e39-4aad-a222-b4772d39fa3f

📥 Commits

Reviewing files that changed from the base of the PR and between eefc9d0 and 2e0f7b2.

📒 Files selected for processing (4)
  • code/core/src/core-server/dev-server.ts
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/shared/open-service/services/docgen/invalidation.integration.test.ts
  • code/core/src/shared/open-service/services/docgen/server.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/core/src/shared/open-service/services/docgen/server.ts
  • code/core/src/core-server/presets/common-preset.ts

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants