Agentic Review: Show banner when review is stale#34981
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughA staleness detection flow: core source-change notifications, server-side grace-window staleness marking that emits EVENTS.REVIEW_STALE and stores ReviewState.stale, a StaleBanner UI, and ReviewPage wiring that passes isStale to DetailsScreen and SummaryScreen. ChangesReview staleness detection
sequenceDiagram
participant StoryDependencyGraphService
participant SourceChangeRegistry
participant ReviewPreset
participant ReviewPage
participant DetailsScreen
participant SummaryScreen
StoryDependencyGraphService->>SourceChangeRegistry: notifySourceFileChange(FileChangeEvent)
SourceChangeRegistry->>ReviewPreset: subscribed listener(FileChangeEvent)
ReviewPreset->>ReviewPage: emit(EVENTS.REVIEW_STALE)
ReviewPage->>DetailsScreen: prop isStale=true
ReviewPage->>SummaryScreen: prop isStale=true
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
code/core/src/core-server/change-detection/source-changes.ts (1)
30-38: ⚡ Quick winConsider logging swallowed listener errors.
Isolating listener failures is correct, but a fully silent
catchmakes a misbehaving subscriber undiagnosable. Adebug-level log keeps the isolation guarantee while preserving observability.♻️ Proposed change
+import { logger } from 'storybook/internal/node-logger'; import type { FileChangeEvent } from './adapters/index.ts';export function notifySourceFileChange(event: FileChangeEvent): void { for (const listener of listeners) { try { listener(event); - } catch { - // A listener failure must never break change detection. + } catch (error) { + // A listener failure must never break change detection. + logger.debug( + `Source-file change listener threw: ${error instanceof Error ? error.message : String(error)}` + ); } } }As per coding guidelines: "Use
storybook/internal/node-loggerfor server-side logging instead of rawconsole.*".🤖 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/source-changes.ts` around lines 30 - 38, The notifySourceFileChange function currently swallows all listener errors silently; update it to catch the error and emit a debug-level log using storybook/internal/node-logger so failures are observable without breaking change detection: import the logger, then inside notifySourceFileChange's catch block (for the listeners iteration) call logger.debug or logger.error with a concise message including the listener identifier (if available) and the caught error/stack to aid diagnosis while preserving the isolation guarantee.
🤖 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/core-server/change-detection/source-changes.ts`:
- Around line 30-38: The notifySourceFileChange function currently swallows all
listener errors silently; update it to catch the error and emit a debug-level
log using storybook/internal/node-logger so failures are observable without
breaking change detection: import the logger, then inside
notifySourceFileChange's catch block (for the listeners iteration) call
logger.debug or logger.error with a concise message including the listener
identifier (if available) and the caught error/stack to aid diagnosis while
preserving the isolation guarantee.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e3c8373d-48df-4f57-a554-21f5371e61dd
📒 Files selected for processing (15)
code/addons/review/src/ReviewPage.tscode/addons/review/src/components/StaleBanner.tsxcode/addons/review/src/constants.tscode/addons/review/src/preset.test.tscode/addons/review/src/preset.tscode/addons/review/src/review-state.tscode/addons/review/src/screens/DetailsScreen.stories.tsxcode/addons/review/src/screens/DetailsScreen.tsxcode/addons/review/src/screens/SummaryScreen.stories.tsxcode/addons/review/src/screens/SummaryScreen.tsxcode/core/src/core-server/change-detection/ChangeDetectionService.tscode/core/src/core-server/change-detection/index.tscode/core/src/core-server/change-detection/source-changes.test.tscode/core/src/core-server/change-detection/source-changes.tscode/core/src/core-server/index.ts
Package BenchmarksCommit: No significant changes detected, all good. 👏 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/StoryDependencyGraphService.ts`:
- Around line 388-390: The call to notifySourceFileChange(event) in
handleFileChange is currently emitted for both real adapter filesystem events
and synthetic add/unlink replays from refreshStoryFiles, causing internal
reconciliations to be treated as external changes; update handleFileChange (and
its callers in refreshStoryFiles) to only call notifySourceFileChange when the
event is adapter-originated (e.g., check an origin/type property on the event)
or add a suppressNotification boolean parameter to handleFileChange that
refreshStoryFiles passes true for, and ensure notifySourceFileChange(event) is
skipped when suppressNotification is true so only real adapter events trigger
external notifications.
🪄 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: 3d4a1d62-7f1e-47fa-9238-8372f8d4162a
📒 Files selected for processing (4)
code/addons/review/src/screens/SummaryScreen.tsxcode/core/src/core-server/change-detection/StoryDependencyGraphService.tscode/core/src/core-server/change-detection/index.tscode/core/src/core-server/index.ts
✅ Files skipped from review due to trivial changes (1)
- code/core/src/core-server/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- code/core/src/core-server/change-detection/index.ts
- code/addons/review/src/screens/SummaryScreen.tsx
| // Surface the raw change to external subscribers (e.g. addon-review's | ||
| // staleness check) before patching — they only care that a file changed. | ||
| notifySourceFileChange(event); |
There was a problem hiding this comment.
Only notify external subscribers for adapter-originated file changes.
handleFileChange() is also used for the synthetic add/unlink replays queued by refreshStoryFiles() on Lines 314-322. Emitting notifySourceFileChange(event) here makes those internal reconciliation patches look like fresh filesystem events, so a review created after the real change can still be marked stale by the later replay. Please gate the notification to adapter-originated events only, or add a flag to suppress it for internal replays.
Suggested fix
- private async handleFileChange(event: FileChangeEvent): Promise<void> {
+ private async handleFileChange(
+ event: FileChangeEvent,
+ notifyExternalSubscribers = true
+ ): Promise<void> {
if (this.disposed || !this.incrementalPatcher) {
return;
}
- // Surface the raw change to external subscribers (e.g. addon-review's
- // staleness check) before patching — they only care that a file changed.
- notifySourceFileChange(event);
+ if (notifyExternalSubscribers) {
+ // Surface raw adapter events to external subscribers (e.g. addon-review's
+ // staleness check) before patching — they only care that a file changed.
+ notifySourceFileChange(event);
+ }
try {
await this.incrementalPatcher.patch(event);
} catch (error) {
logger.warn(
`Change detection: failed to apply ${event.kind} for ${event.path}: ${error instanceof Error ? error.message : String(error)}`
@@
for (const path of added) {
this.patchQueue = this.patchQueue
- .then(() => this.handleFileChange({ kind: 'add', path }))
+ .then(() => this.handleFileChange({ kind: 'add', path }, false))
.catch(() => undefined);
}
for (const path of removed) {
this.patchQueue = this.patchQueue
- .then(() => this.handleFileChange({ kind: 'unlink', path }))
+ .then(() => this.handleFileChange({ kind: 'unlink', path }, false))
.catch(() => 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/change-detection/StoryDependencyGraphService.ts`
around lines 388 - 390, The call to notifySourceFileChange(event) in
handleFileChange is currently emitted for both real adapter filesystem events
and synthetic add/unlink replays from refreshStoryFiles, causing internal
reconciliations to be treated as external changes; update handleFileChange (and
its callers in refreshStoryFiles) to only call notifySourceFileChange when the
event is adapter-originated (e.g., check an origin/type property on the event)
or add a suppressNotification boolean parameter to handleFileChange that
refreshStoryFiles passes true for, and ensure notifySourceFileChange(event) is
skipped when suppressNotification is true so only real adapter events trigger
external notifications.
…ion' into stale-review-banner Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a debug-level log in notifySourceFileChange's catch so a misbehaving subscriber is diagnosable while preserving the isolation guarantee. Also corrects the doc reference to StoryDependencyGraphService (the publisher after the dependency-graph refactor). Co-authored-by: Cursor <cursoragent@cursor.com>
138c0d8 to
40e1c24
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
code/core/src/core-server/change-detection/StoryDependencyGraphService.ts (1)
388-390:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftInternal reconciliation replays still trigger external staleness notifications.
notifySourceFileChange(event)fires for every call tohandleFileChange, including the syntheticadd/unlinkreplays enqueued byrefreshStoryFiles()(Lines 314-322). Those replays are internal story-index reconciliations, not fresh filesystem events, so a review created after the original change can still be marked stale by a later replay. Gate the notification to adapter-originated events only.Suggested fix
- private async handleFileChange(event: FileChangeEvent): Promise<void> { + private async handleFileChange( + event: FileChangeEvent, + notifyExternalSubscribers = true + ): Promise<void> { if (this.disposed || !this.incrementalPatcher) { return; } - // Surface the raw change to external subscribers (e.g. addon-review's - // staleness check) before patching — they only care that a file changed. - notifySourceFileChange(event); + if (notifyExternalSubscribers) { + // Surface raw adapter events to external subscribers (e.g. addon-review's + // staleness check) before patching — they only care that a file changed. + notifySourceFileChange(event); + }And update the
refreshStoryFilescallers:for (const path of added) { this.patchQueue = this.patchQueue - .then(() => this.handleFileChange({ kind: 'add', path })) + .then(() => this.handleFileChange({ kind: 'add', path }, false)) .catch(() => undefined); } for (const path of removed) { this.patchQueue = this.patchQueue - .then(() => this.handleFileChange({ kind: 'unlink', path })) + .then(() => this.handleFileChange({ kind: 'unlink', path }, false)) .catch(() => 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/change-detection/StoryDependencyGraphService.ts` around lines 388 - 390, handleFileChange currently calls notifySourceFileChange(event) for every event, which causes synthetic replays from refreshStoryFiles to trigger external staleness notifications; change handleFileChange to only call notifySourceFileChange for adapter-originated events (e.g., check a unique marker like event.origin === 'adapter' or event.isSynthetic === false) and ensure refreshStoryFiles enqueues/creates replay events with that marker set (e.g., event.origin = 'internal' or event.isSynthetic = true) so those synthetic add/unlink replays do not trigger notifySourceFileChange.
🤖 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/change-detection/StoryDependencyGraphService.ts`:
- Around line 388-390: handleFileChange currently calls
notifySourceFileChange(event) for every event, which causes synthetic replays
from refreshStoryFiles to trigger external staleness notifications; change
handleFileChange to only call notifySourceFileChange for adapter-originated
events (e.g., check a unique marker like event.origin === 'adapter' or
event.isSynthetic === false) and ensure refreshStoryFiles enqueues/creates
replay events with that marker set (e.g., event.origin = 'internal' or
event.isSynthetic = true) so those synthetic add/unlink replays do not trigger
notifySourceFileChange.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 61a9cabb-4675-4253-8f5f-adb930228c9b
📒 Files selected for processing (15)
code/addons/review/src/ReviewPage.tscode/addons/review/src/components/StaleBanner.tsxcode/addons/review/src/constants.tscode/addons/review/src/preset.test.tscode/addons/review/src/preset.tscode/addons/review/src/review-state.tscode/addons/review/src/screens/DetailsScreen.stories.tsxcode/addons/review/src/screens/DetailsScreen.tsxcode/addons/review/src/screens/SummaryScreen.stories.tsxcode/addons/review/src/screens/SummaryScreen.tsxcode/core/src/core-server/change-detection/StoryDependencyGraphService.tscode/core/src/core-server/change-detection/index.tscode/core/src/core-server/change-detection/source-changes.test.tscode/core/src/core-server/change-detection/source-changes.tscode/core/src/core-server/index.ts
🚧 Files skipped from review as they are similar to previous changes (13)
- code/addons/review/src/review-state.ts
- code/core/src/core-server/change-detection/index.ts
- code/addons/review/src/screens/DetailsScreen.stories.tsx
- code/addons/review/src/constants.ts
- code/core/src/core-server/index.ts
- code/addons/review/src/screens/SummaryScreen.stories.tsx
- code/addons/review/src/components/StaleBanner.tsx
- code/core/src/core-server/change-detection/source-changes.test.ts
- code/addons/review/src/preset.test.ts
- code/addons/review/src/preset.ts
- code/core/src/core-server/change-detection/source-changes.ts
- code/addons/review/src/screens/DetailsScreen.tsx
- code/addons/review/src/ReviewPage.ts
Update DetailsScreen/SummaryScreen Stale stories to assert the new banner wording, and drop the SummaryScreen Minimal assertion for branchName, which is no longer rendered in the summary header.
…ion' into stale-review-banner # Conflicts: # code/addons/review/src/ReviewPage.ts # code/addons/review/src/screens/DetailsScreen.tsx
…ion' into stale-review-banner # Conflicts: # code/addons/review/src/review-state.ts # code/addons/review/src/screens/DetailsScreen.tsx # code/addons/review/src/screens/SummaryScreen.tsx
bef69a8
into
yann/agentic-review-mcp-integration
What I did
Adds staleness detection for agentic reviews. When a review is pushed (via
@storybook/addon-mcp), the dev server now subscribes to source-file change events from core's change detection. If any watched source file changes after the review was created (past a 1s grace window that absorbs the agent's own edits, which land just after the review is cached), the review is marked stale and a banner — "New changes were made. This review may be stale." — is shown on both the summary and details screens. Staleness rides on the cached review state, so tabs that open after the change still see the banner.Implementation:
subscribeToSourceFileChangesnotifier (source-changes.ts), fired fromStoryDependencyGraphServiceon each file-change event and re-exported asexperimental_subscribeToSourceFileChanges.REVIEW_STALEevent;ReviewStategains astaleflag; a newStaleBannercomponent plusDetailsScreen/SummaryScreen/ReviewPagewiring surface it to users.Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
Caution
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
cd code && yarn storybook:uiDocumentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the
@storybookjs/coreteam here.core team members can create a canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>Summary by CodeRabbit
New Features
Tests