Skip to content

UI: Fix showing and hiding copy prompt in the correct scenarios#34706

Merged
yannbf merged 9 commits into
nextfrom
yann/ai-feature-fixes
May 5, 2026
Merged

UI: Fix showing and hiding copy prompt in the correct scenarios#34706
yannbf merged 9 commits into
nextfrom
yann/ai-feature-fixes

Conversation

@yannbf
Copy link
Copy Markdown
Member

@yannbf yannbf commented May 4, 2026

Closes #

What I did

Decoupled the AI setup checklist UI from telemetry, and made the Copy prompt button stick around until the agent actually produces work.

  • aiOptIn no longer depends on telemetry. storybook init now writes a small ai-init-opt-in flag to the regular fs cache (in addition to the telemetry event), so users with STORYBOOK_DISABLE_TELEMETRY set still see the Copy prompt button after opting in.
  • The Copy prompt button stops hiding prematurely. Previously it disappeared the moment storybook ai setup ran, even if the user never pasted the prompt or the agent stalled. The dev server now requires both ai-setup-ran (new fs-cache flag, written by the CLI regardless of telemetry) and at least one story tagged ai-generated in the index before flipping the item to "done".
  • Extracted the cache reads into core/src/core-server/utils/ai-checklist-flags.ts and plumbed the story-index getter through initializeChecklist so the dev server can detect agent work.

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

  1. Generate a sandbox: yarn task sandbox --template react-vite/default-ts --start-from auto
  2. From the sandbox, run STORYBOOK_DISABLE_TELEMETRY=1 npx storybook init and accept AI features
  3. Run STORYBOOK_DISABLE_TELEMETRY=1 yarn storybook — confirm the "Set up Storybook with AI" card with Copy prompt button shows up in the Onboarding Checklist (this used to require telemetry to be enabled)
  4. Click Copy prompt — verify the prompt is on your clipboard
  5. In a separate terminal, run npx storybook ai setup (don't paste anywhere yet) — confirm the card stays visible in the UI (it used to hide here)
  6. Paste the prompt into Claude Code / Codex and let it generate stories with tags: ['ai-generated']
  7. After the agent finishes and the story index updates, confirm the card disappears
  8. As a sanity check, decline AI at init in a fresh project — confirm the card never appears (unchanged behavior)

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 PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team 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

    • Dev-server UI now reads project-scoped AI opt-in and setup flags (tied to project path) and checks the story index to detect AI-generated work.
    • CLI and project initialization persist small local cache flags to signal AI setup/opt-in.
  • Bug Fixes

    • AI setup is marked complete only when actual generated work is present, preventing premature "done" states.
    • Initialization proceeds and UI loads even when cache reads fail.
  • Tests

    • Expanded tests for flag-driven flows, idle analytics, dynamic setup scenarios, and resilience to cache failures.

@yannbf yannbf requested a review from Sidnioulz May 4, 2026 17:20
@yannbf yannbf self-assigned this May 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 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

Checklist initialization now reads project-scoped cache flags (ai-init-opt-in, ai-setup-ran) and inspects the story index to determine AI opt-in and whether AI setup produced work; server wiring passes a lazy story-index-generator promise and configDir into checklist initialization.

Changes

AI Setup Tracking & Checklist Initialization

Layer / File(s) Summary
Cache Writes (opt-in / setup)
code/lib/create-storybook/src/initiate.ts, code/lib/cli-storybook/src/ai/index.ts
Persist ai-init-opt-in during create-storybook init and ai-setup-ran during CLI ai setup into the local cache with { timestamp, configDir: resolve(configDir) } (best-effort; errors swallowed).
Flag Reader Utilities
code/core/src/core-server/utils/ai-checklist-flags.ts
Add project-scoped ProjectScopedFlag shape, runtime guard, and readProjectScopedFlag(key, configDir); export hasAiInitOptIn(configDir) and hasAiSetupRun(configDir) that return false on missing/invalid entries or errors.
Data / Detection Shape
code/core/src/core-server/utils/checklist.ts
Extend initializeChecklist signature to accept getStoryIndexGeneratorPromise?: () => Promise<StoryIndexGenerator> and configDir?: string; introduce isAiSetupCompleted() combining hasAiSetupRun(configDir) and story-index inspection for isStoryCreatedByAISetup.
Core Implementation / Wiring
code/core/src/core-server/utils/checklist.ts
Set aiOptIn asynchronously via hasAiInitOptIn(configDir); update markAiSetupDone() and idle analytics/ghost-story scheduling to rely on the flags+story-index completion check.
Integration / Preset Wiring
code/core/src/core-server/presets/common-preset.ts
Pass lazy thunk and config dir into checklist: initializeChecklist(channel, () => storyIndexGeneratorPromise, options.configDir) to defer access to the shared generator.
Tests / Coverage
code/core/src/core-server/utils/checklist.test.ts, code/core/src/core-server/utils/ai-checklist-flags.test.ts
Mock ai-checklist-flags.ts, add fakeStoryIndexGenerator and setAiFlags, refactor tests to assert loaded, aiSetup transitions, debounced analytics/ghost-story emissions, rescheduling behavior, and unit tests verifying hasAiInitOptIn / hasAiSetupRun behavior across resolved configDir values and cache shapes.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI aiSetup
    participant Init as create-storybook
    participant Cache as Local cache
    participant Server as Dev Server (checklist)
    participant IndexGen as StoryIndexGenerator

    CLI->>Cache: set('ai-setup-ran', {timestamp, configDir: resolve(...)})
    Init->>Cache: set('ai-init-opt-in', {timestamp, configDir: resolve(...)})
    Server->>Cache: hasAiInitOptIn(configDir)
    Cache-->>Server: boolean
    Server->>Server: initializeChecklist(getStoryIndexGeneratorPromise, configDir)
    Server->>IndexGen: (lazy) await storyIndexGeneratorPromise
    IndexGen-->>Server: story index entries
    Server->>Cache: hasAiSetupRun(configDir)
    Cache-->>Server: boolean
    Server->>Server: isAiSetupCompleted() checks setup flag + ai-generated entries
    Server-->>Server: mark aiSetup done/open and schedule idle analytics/ghost stories
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/utils/ai-checklist-flags.ts`:
- Around line 20-23: The current hasAiSetupRun() treats any cached
'ai-setup-ran' key as globally true; change it to read the cached value (not
coerce to Boolean) and verify the stored configDir matches the current project
configDir before returning true — i.e., fetch cache.get('ai-setup-ran'),
parse/check that result has a configDir field equal to the passed/current
configDir and only then return true; update the hasAiSetupRun signature to
accept a configDir parameter (or otherwise receive the current configDir) and
update callers (notably initializeChecklist()) to pass the project configDir
through when invoking hasAiSetupRun().
🪄 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: f14d85ea-2ab2-42e2-8e74-90476e333259

📥 Commits

Reviewing files that changed from the base of the PR and between fdcea98 and d572075.

📒 Files selected for processing (6)
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/core-server/utils/ai-checklist-flags.ts
  • code/core/src/core-server/utils/checklist.test.ts
  • code/core/src/core-server/utils/checklist.ts
  • code/lib/cli-storybook/src/ai/index.ts
  • code/lib/create-storybook/src/initiate.ts

Comment thread code/core/src/core-server/utils/ai-checklist-flags.ts Outdated
Comment thread code/core/src/core-server/utils/checklist.ts Outdated
Comment thread code/core/src/core-server/utils/checklist.ts Outdated
Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

LGTM except for the Promise.all which I think is not needed in our case. I added a commit to remove it and simplify some other code, feel free to make me revert that if I missed something!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/utils/ai-checklist-flags.test.ts`:
- Around line 5-20: Replace the current vi.hoisted + direct vi.mock usage with
the repo's spy-mocking pattern: call vi.mock('storybook/internal/common', () =>
({ cache: {} }), { spy: true }) at top-level, keep a top-level let
mockCacheStore: Map<string, unknown> and let mockedCache reference
(uninitialized), then in beforeEach initialize mockCacheStore = new Map() and
obtain the mock via mockedCache =
vi.mocked(require('storybook/internal/common').cache) (or import and then
vi.mocked(imported.cache)); set mockedCache.get = vi.fn(async (key:string) =>
mockCacheStore.get(key)) and mockedCache.set = vi.fn(async (key:string,
value:unknown) => mockCacheStore.set(key, value)); reset the map and call
mockedCache.mockReset() as needed in afterEach. Ensure all tests access the
mocked cache via vi.mocked(...) and that no behavior is defined outside
beforeEach.
🪄 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: 3b65870f-08f7-45e9-8635-95bd9d9ac0b7

📥 Commits

Reviewing files that changed from the base of the PR and between 8114355 and 92c9ab5.

📒 Files selected for processing (6)
  • code/core/src/core-server/presets/common-preset.ts
  • code/core/src/core-server/utils/ai-checklist-flags.test.ts
  • code/core/src/core-server/utils/ai-checklist-flags.ts
  • code/core/src/core-server/utils/checklist.test.ts
  • code/core/src/core-server/utils/checklist.ts
  • code/lib/create-storybook/src/initiate.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • code/core/src/core-server/presets/common-preset.ts
  • code/lib/create-storybook/src/initiate.ts
  • code/core/src/core-server/utils/checklist.ts
  • code/core/src/core-server/utils/checklist.test.ts

Comment on lines +5 to +20
const { mockCacheStore, mockCache } = vi.hoisted(() => {
const store = new Map<string, unknown>();
return {
mockCacheStore: store,
mockCache: {
get: async (key: string) => store.get(key),
set: async (key: string, value: unknown) => {
store.set(key, value);
},
},
};
});

vi.mock('storybook/internal/common', () => ({
cache: mockCache,
}));
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Vitest (current docs), what is the recommended pattern for module mocks using vi.mock(..., { spy: true })and typed access viavi.mocked()?

💡 Result:

The recommended pattern in current Vitest documentation (as of 2026) for module mocks using vi.mock(..., { spy: true }) combined with typed access via vi.mocked() is as follows: 1. Call vi.mock('./modulePath', { spy: true }) at the top of your test file. This hoists the mock, spies on all exports (replacing them with trackable spies), but preserves their original implementations. This is particularly useful in browser mode where direct vi.spyOn on imports isn't possible, or when you want to track calls across the entire module without replacing behavior [1][2]. Example setup: import { vi, expect, test } from 'vitest' import { calculator } from './src/calculator.ts' // Assume calculator is a function export vi.mock('./src/calculator.ts', { spy: true }) 2. Import the module exports as usual. The spies are automatically applied due to hoisting. 3. For TypeScript typed access to mock methods (enabling .mockX assertions and configurations), wrap the import or specific export with vi.mocked(). Example test: test('verifies calculator behavior', () => { const result = calculator(1, 2) expect(result).toBe(3) expect(vi.mocked(calculator)).toHaveBeenCalledWith(1, 2)[10][23] expect(vi.mocked(calculator)).toHaveReturnedWith(3)[10][23] }) This pattern allows asserting call details while the original logic runs, unlike full mocks with factories that replace implementations [3][1]. For partial mocks preserving some original behavior, use factory functions with importOriginal instead [4][5]. Use vi.mocked(export, { partial: true }) or { deep: true } for complex objects/classes [6][1][7]. Note: There was a Vitest 4 bug with deep spying (fixed later), but current docs confirm the pattern works [8].

Citations:


🏁 Script executed:

cd /repo && find . -path "*/code/core/src/core-server/utils/ai-checklist-flags.test.ts" -type f

Repository: storybookjs/storybook

Length of output: 121


🏁 Script executed:

head -50 code/core/src/core-server/utils/ai-checklist-flags.test.ts

Repository: storybookjs/storybook

Length of output: 1593


Align Vitest mocks with repo spy-mocking rules.

This test file must switch to the required spy: true + vi.mocked() pattern. The coding guidelines explicitly mandate using vi.mock() with the spy: true option for all package and file mocks, accessing mocks via vi.mocked(), and implementing mock behaviors in beforeEach blocks—all three are currently violated.

Required refactor
 import { resolve } from 'node:path';
+import * as common from 'storybook/internal/common';

 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

 const { mockCacheStore } = vi.hoisted(() => {
   const store = new Map<string, unknown>();
-  return {
-    mockCacheStore: store,
-    mockCache: {
-      get: async (key: string) => store.get(key),
-      set: async (key: string, value: unknown) => {
-        store.set(key, value);
-      },
-    },
-  };
+  return { mockCacheStore: store };
 });

-vi.mock('storybook/internal/common', () => ({
-  cache: mockCache,
-}));
+vi.mock('storybook/internal/common', { spy: true });

 describe('ai-checklist-flags', () => {
   beforeEach(() => {
     mockCacheStore.clear();
+    vi.mocked(common.cache.get).mockImplementation(async (key: string) => mockCacheStore.get(key));
+    vi.mocked(common.cache.set).mockImplementation(async (key: string, value: unknown) => {
+      mockCacheStore.set(key, value);
+    });
   });

   afterEach(() => {
+    vi.clearAllMocks();
     vi.resetModules();
   });
🤖 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/utils/ai-checklist-flags.test.ts` around lines 5 -
20, Replace the current vi.hoisted + direct vi.mock usage with the repo's
spy-mocking pattern: call vi.mock('storybook/internal/common', () => ({ cache:
{} }), { spy: true }) at top-level, keep a top-level let mockCacheStore:
Map<string, unknown> and let mockedCache reference (uninitialized), then in
beforeEach initialize mockCacheStore = new Map() and obtain the mock via
mockedCache = vi.mocked(require('storybook/internal/common').cache) (or import
and then vi.mocked(imported.cache)); set mockedCache.get = vi.fn(async
(key:string) => mockCacheStore.get(key)) and mockedCache.set = vi.fn(async
(key:string, value:unknown) => mockCacheStore.set(key, value)); reset the map
and call mockedCache.mockReset() as needed in afterEach. Ensure all tests access
the mocked cache via vi.mocked(...) and that no behavior is defined outside
beforeEach.

@yannbf yannbf merged commit 1d84ef6 into next May 5, 2026
123 checks passed
@yannbf yannbf deleted the yann/ai-feature-fixes branch May 5, 2026 06:14
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