Skip to content

Core: Show "new" status on newly added individual stories#34504

Merged
ghengeveld merged 19 commits intonextfrom
story-new-status
Apr 17, 2026
Merged

Core: Show "new" status on newly added individual stories#34504
ghengeveld merged 19 commits intonextfrom
story-new-status

Conversation

@ghengeveld
Copy link
Copy Markdown
Member

@ghengeveld ghengeveld commented Apr 8, 2026

What I did

Enhanced the change detection mechanism with story index change detection, to mark newly added stories with status-value:new. To determine whether new stories were added, an initial snapshot of the story index is taken at startup, and used as a baseline for future comparisons. If the Git repository is clean at that time, the snapshot is persisted on disk and reused across runs, otherwise the baseline is reset whenever the Storybook server restarts.

Screenshot 2026-04-08 at 23 14 14

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

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

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

  • 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-34504-sha-8dd6d4b4. Try it out in a new sandbox by running npx storybook@0.0.0-pr-34504-sha-8dd6d4b4 sandbox or in an existing project with npx storybook@0.0.0-pr-34504-sha-8dd6d4b4 upgrade.

More information
Published version 0.0.0-pr-34504-sha-8dd6d4b4
Triggered by @ghengeveld
Repository storybookjs/storybook
Branch story-new-status
Commit 8dd6d4b4
Datetime Thu Apr 16 06:39:27 UTC 2026 (1776321567)
Workflow run 24495998166

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=34504

Summary by CodeRabbit

  • New Features
    • Baseline-aware change detection with an index-baseline service and an index-invalidated callback to trigger scans; scans now replace prior status data instead of merging payloads.
  • Bug Fixes
    • Fixed status label capitalization in accessibility attributes ("Success"/"Error").
  • Tests
    • Added unit and integration tests covering baseline handling, git-state scenarios, status-merge behavior, and scan-replace semantics.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds IndexBaselineService to maintain story-index baselines keyed by Git HEAD, extends GitDiffProvider with HEAD and working-tree queries, integrates baseline merging and merge helpers into ChangeDetectionService, and wires story-index invalidation callbacks through the dev-server and index.json route. Tests added/updated across services and UI.

Changes

Cohort / File(s) Summary
Index baseline service & tests
code/core/src/core-server/change-detection/IndexBaselineService.ts, code/core/src/core-server/change-detection/IndexBaselineService.test.ts
New IndexBaselineService and extractBaselineEntryIds() build/maintain baseline entry IDs, persist keyed by HEAD, refresh on git-state changes, and expose getBaselineEntryIds(); comprehensive tests for cache hit/miss and dirty/clean transitions.
Change detection service & tests
code/core/src/core-server/change-detection/ChangeDetectionService.ts, code/core/src/core-server/change-detection/ChangeDetectionService.test.ts
Exports helpers mergeStatusValues, mergeChangeDetectionStatuses, buildIndexBaselineStatuses; accepts optional indexBaselineService in constructor; adds onStoryIndexInvalidated() and baseline merging into buildStatuses; tests for merge semantics and scan replacement behavior.
Git diff provider & tests
code/core/src/core-server/change-detection/GitDiffProvider.ts, code/core/src/core-server/change-detection/GitDiffProvider.test.ts
Centralized git command runner and new methods getHeadCommit() and isWorkingTreeClean(); tests added for HEAD parsing and working-tree cleanliness.
Dev-server / index route & tests
code/core/src/core-server/dev-server.ts, code/core/src/core-server/utils/index-json.ts, code/core/src/core-server/utils/index-json.test.ts
Instantiates ChangeDetectionService earlier, forwards onStoryIndexInvalidated to route; registerIndexJsonRoute accepts optional onStoryIndexInvalidated and invokes it when index invalidation is emitted (tests updated).
Status UI / sidebar changes
code/core/src/manager/components/sidebar/Tree.tsx, code/core/src/manager/components/sidebar/useFilterData.tsx, code/core/src/manager/components/sidebar/Filter.stories.tsx
Introduces getStatusLabel() and adjusts aria-label casing/label generation; useStatusFilterEntries returns full status list (zero defaults) and respects feature flag; adds decorator to Filter stories.
E2E tests updates
test-storybooks/.../e2e-tests/component-testing.spec.ts
Playwright test expectations updated to match new capitalized status aria-labels ("Success"/"Error").

Sequence Diagram

sequenceDiagram
    participant GitRepo as Git Repository
    participant GDP as GitDiffProvider
    participant IBS as IndexBaselineService
    participant SIG as StoryIndexGenerator
    participant Cache as BaselineCache
    participant CDS as ChangeDetectionService
    participant Store as StatusStore

    Note over IBS,CDS: startup / invalidation flow
    CDS->>GDP: isWorkingTreeClean()
    GDP->>GitRepo: git status --porcelain
    GitRepo-->>GDP: stdout
    alt working tree clean
        CDS->>GDP: getHeadCommit()
        GDP->>GitRepo: git rev-parse HEAD
        GitRepo-->>GDP: HEAD SHA
        IBS->>Cache: lookup HEAD SHA
        alt cache hit
            Cache-->>IBS: entryIds
        else cache miss
            IBS->>SIG: generate story index
            SIG-->>IBS: StoryIndex
            IBS->>IBS: extractBaselineEntryIds()
            IBS->>Cache: persist {entryIds} by SHA
        end
        IBS->>CDS: onBaselineUpdated()
        CDS->>CDS: buildIndexBaselineStatuses() & merge into statuses
        CDS->>Store: apply status patch
    else working tree dirty
        IBS->>SIG: generate story index (in-memory)
        SIG-->>IBS: StoryIndex
        IBS->>IBS: extractBaselineEntryIds() (no persist)
        CDS->>CDS: merge in-memory baseline (no cache)
        CDS->>Store: apply status patch
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
code/core/src/manager/components/sidebar/Tree.tsx (1)

216-222: Consider exporting getDisplayStatus for direct testability.

This utility function contains conditional logic that could benefit from unit testing. As per coding guidelines, functions that need direct tests should be exported.

♻️ Suggested change
-const getDisplayStatus = (
+export const getDisplayStatus = (
   itemType: Item['type'],
   status: StatusValue
 ): { status: StatusValue; label: string } =>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/manager/components/sidebar/Tree.tsx` around lines 216 - 222,
Export the getDisplayStatus utility so it can be imported in unit tests: change
its declaration to a named export (e.g., export const getDisplayStatus = ... or
export function getDisplayStatus(...)) and update any internal references or
imports that rely on the previous non-exported symbol (ensure consuming
components still import it by name). Keep the implementation unchanged so tests
can directly import getDisplayStatus to assert the conditional mapping for
story/docs hidden statuses and the default label formatting.
code/core/src/core-server/change-detection/GitDiffProvider.test.ts (1)

148-168: Consider simplifying the nested ternary dispatch.

The mock dispatch table is becoming deeply nested (7 levels). While functional, a Map or object lookup would improve readability.

♻️ Optional refactor using a lookup object
-    vi.mocked(execa).mockImplementation(((_command: string | URL, ...rest: unknown[]) => {
-      const args = Array.isArray(rest[0]) ? rest[0] : [];
-      const gitArgs = args.join(' ');
-      const result =
-        gitArgs === 'rev-parse --show-toplevel'
-          ? repoRootResult
-          : gitArgs === 'diff --name-only --diff-filter=ad --cached'
-            ? stagedResult
-            : gitArgs === 'diff --name-only --diff-filter=ad'
-              ? unstagedResult
-              : gitArgs === 'ls-files --others --exclude-standard'
-                ? untrackedResult
-                : gitArgs === 'diff --name-only --diff-filter=A --cached'
-                  ? stagedAddedResult
-                  : gitArgs === 'diff --name-only --diff-filter=A'
-                    ? intentToAddResult
-                    : gitArgs === 'rev-parse HEAD'
-                      ? headCommitResult
-                      : gitArgs === 'status --porcelain'
-                        ? statusPorcelainResult
-                        : undefined;
+    vi.mocked(execa).mockImplementation(((_command: string | URL, ...rest: unknown[]) => {
+      const args = Array.isArray(rest[0]) ? rest[0] : [];
+      const gitArgs = args.join(' ');
+      const resultMap: Record<string, ExecaMockResult | undefined> = {
+        'rev-parse --show-toplevel': repoRootResult,
+        'diff --name-only --diff-filter=ad --cached': stagedResult,
+        'diff --name-only --diff-filter=ad': unstagedResult,
+        'ls-files --others --exclude-standard': untrackedResult,
+        'diff --name-only --diff-filter=A --cached': stagedAddedResult,
+        'diff --name-only --diff-filter=A': intentToAddResult,
+        'rev-parse HEAD': headCommitResult,
+        'status --porcelain': statusPorcelainResult,
+      };
+      const result = resultMap[gitArgs];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/change-detection/GitDiffProvider.test.ts` around
lines 148 - 168, The nested ternary inside the
vi.mocked(execa).mockImplementation block (which inspects gitArgs and returns
repoRootResult, stagedResult, unstagedResult, untrackedResult,
stagedAddedResult, intentToAddResult, headCommitResult, statusPorcelainResult)
should be replaced with a clear lookup table: build a Map or plain object keyed
by the gitArgs strings and return map.get(gitArgs) (or map[gitArgs]) instead of
the long nested ternary; ensure you keep the existing handling for
Array.isArray(rest[0]) to derive args and preserve returning undefined for
unmatched keys.
code/core/src/core-server/change-detection/ChangeDetectionService.ts (2)

391-403: Minor redundancy in status value merging.

The value is merged twice: first explicitly on line 394 via mergeStatusValues(existingStatus?.value, value), then again inside mergeChangeDetectionStatuses on line 403. Since mergeStatusValues is idempotent for its priority logic, this works correctly but is slightly redundant.

Consider removing the explicit merge on line 394 and letting mergeChangeDetectionStatuses handle it:

Proposed simplification
          const nextStatus: Status = {
            storyId,
            typeId: CHANGE_DETECTION_STATUS_TYPE_ID,
-           value: mergeStatusValues(existingStatus?.value, value),
+           value,
            title: '',
            description: '',
            data: {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/change-detection/ChangeDetectionService.ts` around
lines 391 - 403, The code builds nextStatus with value already merged via
mergeStatusValues(existingStatus?.value, value) and then calls
statuses.set(storyId, mergeChangeDetectionStatuses(existingStatus, nextStatus))
which re-merges the values; remove the redundant merge by assigning
nextStatus.value = value (the incoming value) and let
mergeChangeDetectionStatuses perform the merge using existingStatus, ensuring
you update the construction of nextStatus (the const nextStatus object) and
leave the call to mergeChangeDetectionStatuses(existingStatus, nextStatus)
unchanged.

236-238: Consider logging suppressed errors for debugging.

The .catch(() => undefined) silently discards any errors from handleGitStateChange(). While this prevents unhandled rejections, it may hide issues during debugging. Consider logging at debug level.

Proposed enhancement
       this.scheduleScan(this.debounceMs);
       void this.getIndexBaselineService()
         .handleGitStateChange()
-        .catch(() => undefined);
+        .catch((error) => {
+          logger.debug('IndexBaselineService.handleGitStateChange failed: %s', error);
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/change-detection/ChangeDetectionService.ts` around
lines 236 - 238, The current call to
this.getIndexBaselineService().handleGitStateChange().catch(() => undefined)
swallows errors; update the catch to log the error at debug level instead of
discarding it so issues are visible during debugging. Locate the call in
ChangeDetectionService (the getIndexBaselineService().handleGitStateChange
invocation) and replace the empty catch with one that logs the caught error via
the class logger (e.g., this.logger.debug or the existing logger instance)
including context like "handleGitStateChange error" and the error object.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/core-server/change-detection/ChangeDetectionService.test.ts`:
- Around line 137-139: The test mock incorrectly overrides getHeadSha() while
the GitDiffProvider interface defines getHeadCommit(); update the mock to
implement async getHeadCommit(): Promise<string> { return this.getHeadShaMock();
} (or rename getHeadShaMock to getHeadCommitMock and wire it into getHeadCommit)
so the mock class implements GitDiffProvider correctly and the override and
helper names (getHeadShaMock / getHeadSha) are aligned with getHeadCommit.

In `@code/core/src/core-server/change-detection/ChangeDetectionService.ts`:
- Around line 180-186: The constructor parameter is named with PascalCase
("IndexBaselineService") instead of camelCase; rename the parameter to
indexBaselineService in the ChangeDetectionService constructor signature and
update the assignment inside the constructor (currently
this.indexBaselineService = options.IndexBaselineService) to use the new
camelCase name (this.indexBaselineService = options.indexBaselineService) so
parameter naming follows JS/TS conventions and matches the existing property.

---

Nitpick comments:
In `@code/core/src/core-server/change-detection/ChangeDetectionService.ts`:
- Around line 391-403: The code builds nextStatus with value already merged via
mergeStatusValues(existingStatus?.value, value) and then calls
statuses.set(storyId, mergeChangeDetectionStatuses(existingStatus, nextStatus))
which re-merges the values; remove the redundant merge by assigning
nextStatus.value = value (the incoming value) and let
mergeChangeDetectionStatuses perform the merge using existingStatus, ensuring
you update the construction of nextStatus (the const nextStatus object) and
leave the call to mergeChangeDetectionStatuses(existingStatus, nextStatus)
unchanged.
- Around line 236-238: The current call to
this.getIndexBaselineService().handleGitStateChange().catch(() => undefined)
swallows errors; update the catch to log the error at debug level instead of
discarding it so issues are visible during debugging. Locate the call in
ChangeDetectionService (the getIndexBaselineService().handleGitStateChange
invocation) and replace the empty catch with one that logs the caught error via
the class logger (e.g., this.logger.debug or the existing logger instance)
including context like "handleGitStateChange error" and the error object.

In `@code/core/src/core-server/change-detection/GitDiffProvider.test.ts`:
- Around line 148-168: The nested ternary inside the
vi.mocked(execa).mockImplementation block (which inspects gitArgs and returns
repoRootResult, stagedResult, unstagedResult, untrackedResult,
stagedAddedResult, intentToAddResult, headCommitResult, statusPorcelainResult)
should be replaced with a clear lookup table: build a Map or plain object keyed
by the gitArgs strings and return map.get(gitArgs) (or map[gitArgs]) instead of
the long nested ternary; ensure you keep the existing handling for
Array.isArray(rest[0]) to derive args and preserve returning undefined for
unmatched keys.

In `@code/core/src/manager/components/sidebar/Tree.tsx`:
- Around line 216-222: Export the getDisplayStatus utility so it can be imported
in unit tests: change its declaration to a named export (e.g., export const
getDisplayStatus = ... or export function getDisplayStatus(...)) and update any
internal references or imports that rely on the previous non-exported symbol
(ensure consuming components still import it by name). Keep the implementation
unchanged so tests can directly import getDisplayStatus to assert the
conditional mapping for story/docs hidden statuses and the default label
formatting.
🪄 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: 84a7a20e-82af-49b7-8c20-da8bbd1c7b89

📥 Commits

Reviewing files that changed from the base of the PR and between 7022915 and 2295b1f.

📒 Files selected for processing (12)
  • code/builders/builder-vite/src/index.test.ts
  • code/builders/builder-vite/src/index.ts
  • code/core/src/core-server/change-detection/ChangeDetectionService.test.ts
  • code/core/src/core-server/change-detection/ChangeDetectionService.ts
  • code/core/src/core-server/change-detection/GitDiffProvider.test.ts
  • code/core/src/core-server/change-detection/GitDiffProvider.ts
  • code/core/src/core-server/change-detection/IndexBaselineService.test.ts
  • code/core/src/core-server/change-detection/IndexBaselineService.ts
  • code/core/src/core-server/dev-server.ts
  • code/core/src/core-server/utils/index-json.test.ts
  • code/core/src/core-server/utils/index-json.ts
  • code/core/src/manager/components/sidebar/Tree.tsx

Comment thread code/core/src/core-server/change-detection/ChangeDetectionService.test.ts Outdated
Comment thread code/core/src/core-server/change-detection/ChangeDetectionService.ts Outdated
Comment thread code/core/src/core-server/dev-server.ts Outdated
Comment thread code/core/src/core-server/dev-server.ts Outdated
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Apr 15, 2026

View your CI Pipeline Execution ↗ for commit 41b7afb

Command Status Duration Result
nx run-many -t compile,check,knip,test,lint,fmt... ✅ Succeeded 4m 30s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-17 11:20:33 UTC

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/core-server/change-detection/ChangeDetectionService.ts`:
- Around line 408-415: The applyStatusStorePatch currently re-merges fresh scan
results with the existing status store (via this.options.statusStore.getAll()
and mergeChangeDetectionStatuses), which causes old changedFiles/values to
persist and stale "new" statuses to override fresh "modified" results; change
applyStatusStorePatch to stop reading/merging with allCurrentStatuses and
instead accept the scan's built results as authoritative (just use the incoming
nextStatuses entries directly when populating mergedNextStatuses or write
nextStatuses straight into the status store), and remove calls to
mergeChangeDetectionStatuses here (also remove the same merge logic at the
similar block around the other occurrence noted).

In `@code/core/src/core-server/dev-server.ts`:
- Around line 51-55: The code creates two ChangeDetectionService instances
causing registerIndexJsonRoute to close over the dormant one while a different
instance is started/disposed later; update the code so a single
ChangeDetectionService instance (the variable changeDetectionService created
near the top) is reused everywhere: remove the second instantiation at lines
creating/starting/disposing a new ChangeDetectionService and instead call
start/stop on the original changeDetectionService, ensure
registerIndexJsonRoute, onStoryIndexInvalidated and any lifecycle start/stop
logic reference that same changeDetectionService variable so its module-graph
and baseline state remain consistent.
🪄 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: 297404ef-bd60-4050-90b3-27237379f934

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42838 and 380ec23.

📒 Files selected for processing (8)
  • code/core/src/core-server/change-detection/ChangeDetectionService.test.ts
  • code/core/src/core-server/change-detection/ChangeDetectionService.ts
  • code/core/src/core-server/change-detection/GitDiffProvider.ts
  • code/core/src/core-server/dev-server.ts
  • code/core/src/manager/components/sidebar/Filter.stories.tsx
  • code/core/src/manager/components/sidebar/Tree.tsx
  • test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts
  • test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/core-server/change-detection/GitDiffProvider.ts

Comment thread code/core/src/core-server/change-detection/ChangeDetectionService.ts Outdated
Comment thread code/core/src/core-server/dev-server.ts
tmeasday added a commit to tmeasday/mealdrop that referenced this pull request Apr 16, 2026
@ghengeveld ghengeveld merged commit ec48db0 into next Apr 17, 2026
99 checks passed
@ghengeveld ghengeveld deleted the story-new-status branch April 17, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants