diff --git a/code/core/src/cli/globalSettings.ts b/code/core/src/cli/globalSettings.ts index 935944795daa..6dcc016e92ba 100644 --- a/code/core/src/cli/globalSettings.ts +++ b/code/core/src/cli/globalSettings.ts @@ -29,7 +29,7 @@ const userSettingSchema = z.object({ items: z .object({ accessibilityTests: statusValue, - aiPrepare: statusValue, + aiSetup: statusValue, autodocs: statusValue, ciTests: statusValue, controls: statusValue, diff --git a/code/core/src/core-events/index.ts b/code/core/src/core-events/index.ts index 8126f8a158ea..b988a366256d 100644 --- a/code/core/src/core-events/index.ts +++ b/code/core/src/core-events/index.ts @@ -91,9 +91,9 @@ enum events { // Story discovery and testing flow GHOST_STORIES_REQUEST = 'ghostStoriesRequest', GHOST_STORIES_RESPONSE = 'ghostStoriesResponse', - // AI analytics - ai prepare command - AI_PREPARE_ANALYTICS_RESPONSE = 'aiPrepareAnalyticsResponse', - AI_PREPARE_ANALYTICS_REQUEST = 'aiPrepareAnalyticsRequest', + // AI analytics - ai setup command + AI_SETUP_ANALYTICS_RESPONSE = 'aiSetupAnalyticsResponse', + AI_SETUP_ANALYTICS_REQUEST = 'aiSetupAnalyticsRequest', // Open a file in the code editor OPEN_IN_EDITOR_REQUEST = 'openInEditorRequest', OPEN_IN_EDITOR_RESPONSE = 'openInEditorResponse', @@ -173,8 +173,8 @@ export const { ARGTYPES_INFO_RESPONSE, GHOST_STORIES_REQUEST, GHOST_STORIES_RESPONSE, - AI_PREPARE_ANALYTICS_RESPONSE, - AI_PREPARE_ANALYTICS_REQUEST, + AI_SETUP_ANALYTICS_RESPONSE, + AI_SETUP_ANALYTICS_REQUEST, OPEN_IN_EDITOR_REQUEST, OPEN_IN_EDITOR_RESPONSE, MANAGER_INERT_ATTRIBUTE_CHANGED, diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts index b534317e022e..d4fded28c652 100644 --- a/code/core/src/core-server/presets/common-preset.ts +++ b/code/core/src/core-server/presets/common-preset.ts @@ -41,7 +41,7 @@ import { initializeSaveStory } from '../utils/save-story/save-story.ts'; import { parseStaticDir } from '../utils/server-statics.ts'; import { type OptionsWithRequiredCache, initializeWhatsNew } from '../utils/whats-new.ts'; import { getWsToken } from './wsToken.ts'; -import { initAIAnalyticsChannel } from '../server-channel/ai-prepare-channel.ts'; +import { initAIAnalyticsChannel } from '../server-channel/ai-setup-channel.ts'; const interpolate = (string: string, data: Record = {}) => Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string); diff --git a/code/core/src/core-server/server-channel/ai-prepare-channel.ts b/code/core/src/core-server/server-channel/ai-setup-channel.ts similarity index 76% rename from code/core/src/core-server/server-channel/ai-prepare-channel.ts rename to code/core/src/core-server/server-channel/ai-setup-channel.ts index 0c08fed0f8d4..f5a45f9b1667 100644 --- a/code/core/src/core-server/server-channel/ai-prepare-channel.ts +++ b/code/core/src/core-server/server-channel/ai-setup-channel.ts @@ -1,12 +1,12 @@ import type { Channel } from 'storybook/internal/channels'; import { - AI_PREPARE_ANALYTICS_REQUEST, - AI_PREPARE_ANALYTICS_RESPONSE, + AI_SETUP_ANALYTICS_REQUEST, + AI_SETUP_ANALYTICS_RESPONSE, } from 'storybook/internal/core-events'; import { getLastEvents, getStorybookMetadata, - isStoryCreatedByAIPrepare, + isStoryCreatedByAISetup, telemetry, } from 'storybook/internal/telemetry'; import type { CoreConfig, Options } from 'storybook/internal/types'; @@ -26,8 +26,8 @@ export function initAIAnalyticsChannel( return channel; } - /** Send analytics about the ai prepare workflow when requested*/ - channel.on(AI_PREPARE_ANALYTICS_REQUEST, async () => { + /** Send analytics about the ai setup workflow when requested*/ + channel.on(AI_SETUP_ANALYTICS_REQUEST, async () => { const stats: { fileCount?: number; storyCount?: number; @@ -36,16 +36,16 @@ export function initAIAnalyticsChannel( try { const lastEvents = await getLastEvents(); - const lastAIPrepare = lastEvents?.['ai-prepare']; - const lastPrepareStoryScoringRun = lastEvents?.['ai-prepare-story-scoring']; + const lastAISetup = lastEvents?.['ai-setup']; + const lastSetupStoryScoringRun = lastEvents?.['ai-setup-story-scoring']; - // Only run if sb ai prepare has been called - if (!lastAIPrepare) { + // Only run if sb ai setup has been called + if (!lastAISetup) { return; } // Already ran once for this project — never run again - if (lastPrepareStoryScoringRun) { + if (lastSetupStoryScoringRun) { return; } @@ -66,7 +66,7 @@ export function initAIAnalyticsChannel( // disturb end user activities. const isIdle = await waitForIdleVitest(); if (!isIdle) { - logger.debug('AI_PREPARE_ANALYTICS_REQUEST timed out waiting for vitest to be available.'); + logger.debug('AI_SETUP_ANALYTICS_REQUEST timed out waiting for vitest to be available.'); return; } @@ -74,7 +74,7 @@ export function initAIAnalyticsChannel( const generatorPromise = getStoryIndexGeneratorPromise?.(); if (!generatorPromise) { logger.debug( - 'AI_PREPARE_ANALYTICS_REQUEST could not proceed as the index generator is not ready.' + 'AI_SETUP_ANALYTICS_REQUEST could not proceed as the index generator is not ready.' ); return; } @@ -82,14 +82,14 @@ export function initAIAnalyticsChannel( const generator = await generatorPromise; const indexAndStats = await generator.getIndexAndStats(); if (!indexAndStats) { - logger.debug('AI_PREPARE_ANALYTICS_REQUEST could not proceed as the index is not ready.'); + logger.debug('AI_SETUP_ANALYTICS_REQUEST could not proceed as the index is not ready.'); return; } const aiStoryFiles = new Set(); let aiStoryCount = 0; for (const entry of Object.values(indexAndStats.storyIndex.entries)) { - if (isStoryCreatedByAIPrepare(entry)) { + if (isStoryCreatedByAISetup(entry)) { aiStoryFiles.add(entry.importPath); aiStoryCount++; } @@ -97,7 +97,7 @@ export function initAIAnalyticsChannel( if (aiStoryFiles.size > 0) { const aiTestRunResult = await runGhostStories([...aiStoryFiles]); - telemetry('ai-prepare-story-scoring', { + telemetry('ai-setup-story-scoring', { stats: { fileCount: aiStoryFiles.size, storyCount: aiStoryCount, @@ -107,7 +107,7 @@ export function initAIAnalyticsChannel( ...(aiTestRunResult.runError ? { runError: aiTestRunResult.runError } : {}), }); } else { - telemetry('ai-prepare-story-scoring', { + telemetry('ai-setup-story-scoring', { stats: { fileCount: 0, storyCount: 0, @@ -117,13 +117,13 @@ export function initAIAnalyticsChannel( }); } } catch { - telemetry('ai-prepare-story-scoring', { + telemetry('ai-setup-story-scoring', { stats, runError: 'Unknown error during AI story scoring', }); } finally { // we don't currently do anything with this, but will be useful in the future - channel.emit(AI_PREPARE_ANALYTICS_RESPONSE); + channel.emit(AI_SETUP_ANALYTICS_RESPONSE); } }); diff --git a/code/core/src/core-server/server-channel/ghost-stories-channel.ts b/code/core/src/core-server/server-channel/ghost-stories-channel.ts index e09f574b6eb9..157d0144990c 100644 --- a/code/core/src/core-server/server-channel/ghost-stories-channel.ts +++ b/code/core/src/core-server/server-channel/ghost-stories-channel.ts @@ -38,11 +38,11 @@ export function initGhostStoriesChannel( const ghostRunStart = Date.now(); const lastEvents = await getLastEvents(); const lastInit = lastEvents?.init; - const lastAIPrepare = lastEvents?.['ai-prepare']; + const lastAISetup = lastEvents?.['ai-setup']; const lastGhostStoriesRun = lastEvents?.['ghost-stories']; - // We only want to run ghost stories immediately after init or ai prepare. - const lastRelevantEvent = lastAIPrepare ?? lastInit; + // We only want to run ghost stories immediately after init or ai setup. + const lastRelevantEvent = lastAISetup ?? lastInit; if (!lastRelevantEvent) { return; } @@ -53,7 +53,7 @@ export function initGhostStoriesChannel( } const sessionId = await getSessionId(); - // We only capture ghost stories in the first ever session since init or ai prepare. + // We only capture ghost stories in the first ever session since init or ai setup. if (lastRelevantEvent.body?.sessionId && lastRelevantEvent.body.sessionId !== sessionId) { return; } diff --git a/code/core/src/core-server/utils/checklist.test.ts b/code/core/src/core-server/utils/checklist.test.ts index 72dcb363e990..c961d83c5828 100644 --- a/code/core/src/core-server/utils/checklist.test.ts +++ b/code/core/src/core-server/utils/checklist.test.ts @@ -69,7 +69,7 @@ describe('initializeChecklist', () => { } as unknown as Awaited>); }); - it('keeps aiPrepare as open when no ai-prepare event exists in cache', async () => { + it('keeps aiSetup as open when no ai-setup event exists in cache', async () => { const { get: getEventCacheEntry } = await import('../../telemetry/event-cache.ts'); vi.mocked(getEventCacheEntry).mockResolvedValue(undefined); @@ -77,41 +77,41 @@ describe('initializeChecklist', () => { await initializeChecklist(); const state = mockStore.getState(); - expect(state.items.aiPrepare.status).toBe('open'); + expect(state.items.aiSetup.status).toBe('open'); }); - it('marks aiPrepare as done when ai-prepare event exists in cache', async () => { + it('marks aiSetup as done when ai-setup event exists in cache', async () => { const { get: getEventCacheEntry } = await import('../../telemetry/event-cache.ts'); vi.mocked(getEventCacheEntry).mockResolvedValue({ timestamp: Date.now(), - body: { eventType: 'ai-prepare' } as TelemetryEvent, + body: { eventType: 'ai-setup' } as TelemetryEvent, } satisfies CacheEntry); const { initializeChecklist } = await import('./checklist.ts'); await initializeChecklist(); const state = mockStore.getState(); - expect(state.items.aiPrepare.status).toBe('done'); + expect(state.items.aiSetup.status).toBe('done'); }); - it('does not overwrite aiPrepare status if already done from persisted state', async () => { - // Simulate persisted user state where aiPrepare is already 'skipped' + it('does not overwrite aiSetup status if already done from persisted state', async () => { + // Simulate persisted user state where aiSetup is already 'skipped' mockSettingsValue.checklist = { - items: { aiPrepare: { status: 'skipped' } }, + items: { aiSetup: { status: 'skipped' } }, widget: {}, }; const { get: getEventCacheEntry } = await import('../../telemetry/event-cache.ts'); vi.mocked(getEventCacheEntry).mockResolvedValue({ timestamp: Date.now(), - body: { eventType: 'ai-prepare' } as TelemetryEvent, + body: { eventType: 'ai-setup' } as TelemetryEvent, } satisfies CacheEntry); const { initializeChecklist } = await import('./checklist.ts'); await initializeChecklist(); const state = mockStore.getState(); - // The ai-prepare event was found, but status was 'skipped' (not 'open'), so it stays 'skipped' - expect(state.items.aiPrepare.status).toBe('skipped'); + // The ai-setup event was found, but status was 'skipped' (not 'open'), so it stays 'skipped' + expect(state.items.aiSetup.status).toBe('skipped'); }); }); diff --git a/code/core/src/core-server/utils/checklist.ts b/code/core/src/core-server/utils/checklist.ts index fbc8fdb9781a..53f939ad5b52 100644 --- a/code/core/src/core-server/utils/checklist.ts +++ b/code/core/src/core-server/utils/checklist.ts @@ -63,14 +63,14 @@ export async function initializeChecklist() { }) satisfies StoreState ); - // Check if ai-prepare has ever been run and mark it as done - const aiPrepareEvent = await getEventCacheEntry('ai-prepare'); - if (aiPrepareEvent) { + // Check if ai-setup has ever been run and mark it as done + const aiSetupEvent = await getEventCacheEntry('ai-setup'); + if (aiSetupEvent) { const currentState = store.getState(); - if (currentState.items.aiPrepare?.status === 'open') { + if (currentState.items.aiSetup?.status === 'open') { store.setState((state) => ({ ...state, - items: { ...state.items, aiPrepare: { ...state.items.aiPrepare, status: 'done' } }, + items: { ...state.items, aiSetup: { ...state.items.aiSetup, status: 'done' } }, })); } } diff --git a/code/core/src/core-server/utils/doTelemetry.ts b/code/core/src/core-server/utils/doTelemetry.ts index f1b3374ee2aa..7196568efa08 100644 --- a/code/core/src/core-server/utils/doTelemetry.ts +++ b/code/core/src/core-server/utils/doTelemetry.ts @@ -1,5 +1,5 @@ import { - collectAiPrepareEvidence, + collectAiSetupEvidence, getPrecedingUpgrade, telemetry, } from 'storybook/internal/telemetry'; @@ -45,7 +45,7 @@ export async function doTelemetry( // directly. This is the entry point for collecting evidence about those side effects and // recording them in telemetry. if (indexAndStats) { - collectAiPrepareEvidence('dev', options.configDir, indexAndStats.storyIndex); + collectAiSetupEvidence('dev', options.configDir, indexAndStats.storyIndex); } const { versionCheck, versionUpdates } = options; diff --git a/code/core/src/core-server/withTelemetry.test.ts b/code/core/src/core-server/withTelemetry.test.ts index 622b9c738738..e39a63ebcef0 100644 --- a/code/core/src/core-server/withTelemetry.test.ts +++ b/code/core/src/core-server/withTelemetry.test.ts @@ -4,7 +4,7 @@ import { cache, isCI, loadAllPresets } from 'storybook/internal/common'; import { prompt } from 'storybook/internal/node-logger'; import { ErrorCollector, - collectAiPrepareEvidence, + collectAiSetupEvidence, oneWayHash, telemetry, } from 'storybook/internal/telemetry'; @@ -34,7 +34,7 @@ describe('withTelemetry', () => { vi.resetAllMocks(); vi.mocked(ErrorCollector.getErrors).mockReturnValue([]); vi.mocked(telemetry).mockResolvedValue(undefined); - vi.mocked(collectAiPrepareEvidence).mockResolvedValue(undefined); + vi.mocked(collectAiSetupEvidence).mockResolvedValue(undefined); }); it('works in happy path', async () => { const run = vi.fn(); @@ -42,7 +42,7 @@ describe('withTelemetry', () => { await withTelemetry('dev', { cliOptions }, run); expect(telemetry).toHaveBeenCalledTimes(1); - expect(collectAiPrepareEvidence).toHaveBeenCalledTimes(1); + expect(collectAiSetupEvidence).toHaveBeenCalledTimes(1); expect(telemetry).toHaveBeenCalledWith('boot', { eventType: 'dev' }, { stripMetadata: true }); }); @@ -52,7 +52,7 @@ describe('withTelemetry', () => { await withTelemetry('dev', { cliOptions: { disableTelemetry: true } }, run); expect(telemetry).toHaveBeenCalledTimes(0); - expect(collectAiPrepareEvidence).toHaveBeenCalledTimes(0); + expect(collectAiSetupEvidence).toHaveBeenCalledTimes(0); }); describe('when command fails', () => { @@ -67,7 +67,7 @@ describe('withTelemetry', () => { ).rejects.toThrow(error); expect(telemetry).toHaveBeenCalledWith('boot', { eventType: 'dev' }, { stripMetadata: true }); - expect(collectAiPrepareEvidence).toHaveBeenCalledTimes(1); + expect(collectAiSetupEvidence).toHaveBeenCalledTimes(1); }); it('does not send boot when cli option is passed', async () => { @@ -76,7 +76,7 @@ describe('withTelemetry', () => { ).rejects.toThrow(error); expect(telemetry).toHaveBeenCalledTimes(0); - expect(collectAiPrepareEvidence).toHaveBeenCalledTimes(0); + expect(collectAiSetupEvidence).toHaveBeenCalledTimes(0); }); it('sends error message when no options are passed', async () => { @@ -85,7 +85,7 @@ describe('withTelemetry', () => { ).rejects.toThrow(error); expect(telemetry).toHaveBeenCalledTimes(2); - expect(collectAiPrepareEvidence).toHaveBeenCalledTimes(1); + expect(collectAiSetupEvidence).toHaveBeenCalledTimes(1); expect(telemetry).toHaveBeenCalledWith( 'error', expect.objectContaining({ @@ -149,7 +149,7 @@ describe('withTelemetry', () => { ).rejects.toThrow(error); expect(telemetry).toHaveBeenCalledTimes(0); - expect(collectAiPrepareEvidence).toHaveBeenCalledTimes(0); + expect(collectAiSetupEvidence).toHaveBeenCalledTimes(0); expect(telemetry).not.toHaveBeenCalledWith( 'error', expect.objectContaining({}), diff --git a/code/core/src/core-server/withTelemetry.ts b/code/core/src/core-server/withTelemetry.ts index 9903e34fc0a3..81162aaf900c 100644 --- a/code/core/src/core-server/withTelemetry.ts +++ b/code/core/src/core-server/withTelemetry.ts @@ -1,7 +1,7 @@ import { HandledError, cache, isCI, loadAllPresets } from 'storybook/internal/common'; import { logger, prompt } from 'storybook/internal/node-logger'; import { - collectAiPrepareEvidence, + collectAiSetupEvidence, ErrorCollector, getPrecedingUpgrade, oneWayHash, @@ -189,7 +189,7 @@ export async function withTelemetry( if (enableTelemetry) { // Fire-and-forget: don't await, don't block the command const configDir = options.cliOptions.configDir || options.presetOptions?.configDir; - collectAiPrepareEvidence(eventType, configDir); + collectAiSetupEvidence(eventType, configDir); } try { diff --git a/code/core/src/manager/components/sidebar/ChecklistWidget.stories.tsx b/code/core/src/manager/components/sidebar/ChecklistWidget.stories.tsx index 159204f39ec6..0783b17fe21f 100644 --- a/code/core/src/manager/components/sidebar/ChecklistWidget.stories.tsx +++ b/code/core/src/manager/components/sidebar/ChecklistWidget.stories.tsx @@ -95,14 +95,14 @@ export const Narrow = meta.story({ play, }); -export const WithAiPrepare = meta.story({ +export const WithAiSetup = meta.story({ beforeEach: async () => { mockStore.setState({ loaded: true, widget: {}, items: { ...initialState.items, - // aiPrepare is intentionally left 'open' so it appears in the widget's task list + // aiSetup is intentionally left 'open' so it appears in the widget's task list controls: { status: 'accepted' }, renderComponent: { status: 'done' }, }, diff --git a/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.test.ts b/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.test.ts index cb10dba6793e..f08f92153bb9 100644 --- a/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.test.ts +++ b/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.test.ts @@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { - AI_PREPARE_ANALYTICS_REQUEST, + AI_SETUP_ANALYTICS_REQUEST, GHOST_STORIES_REQUEST, PREVIEW_INITIALIZED, } from 'storybook/internal/core-events'; @@ -66,7 +66,7 @@ describe('useDelayedAnalyticsTrigger', () => { expect(api.emit).not.toHaveBeenCalled(); }); - it('emits GHOST_STORIES_REQUEST and AI_PREPARE_ANALYTICS_REQUEST after delay', () => { + it('emits GHOST_STORIES_REQUEST and AI_SETUP_ANALYTICS_REQUEST after delay', () => { const api = createMockApi(); mockUseStorybookApi.mockReturnValue(api as any); @@ -78,7 +78,7 @@ describe('useDelayedAnalyticsTrigger', () => { vi.advanceTimersByTime(4 * 60 * 1000); expect(api.emit).toHaveBeenCalledWith(GHOST_STORIES_REQUEST); - expect(api.emit).toHaveBeenCalledWith(AI_PREPARE_ANALYTICS_REQUEST); + expect(api.emit).toHaveBeenCalledWith(AI_SETUP_ANALYTICS_REQUEST); expect(api.emit).toHaveBeenCalledTimes(2); }); diff --git a/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.ts b/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.ts index 9458a5eaf642..d9a16706c275 100644 --- a/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.ts +++ b/code/core/src/manager/components/sidebar/useDelayedAnalyticsTrigger.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react'; import { - AI_PREPARE_ANALYTICS_REQUEST, + AI_SETUP_ANALYTICS_REQUEST, GHOST_STORIES_REQUEST, PREVIEW_INITIALIZED, } from 'storybook/internal/core-events'; @@ -33,12 +33,12 @@ export function useDelayedAnalyticsTrigger(): void { } fired.current = true; - // if `ai prepare` is in the same session, we run ghost stories and ai prepare analytics. + // if `ai setup` is in the same session, we run ghost stories and ai setup analytics. if ( - global.STORYBOOK_LAST_EVENTS?.['ai-prepare']?.body.sessionId === global.STORYBOOK_SESSION_ID + global.STORYBOOK_LAST_EVENTS?.['ai-setup']?.body.sessionId === global.STORYBOOK_SESSION_ID ) { api.emit(GHOST_STORIES_REQUEST); - api.emit(AI_PREPARE_ANALYTICS_REQUEST); + api.emit(AI_SETUP_ANALYTICS_REQUEST); } }; diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index c6df44d8b486..29dc2b59f4f4 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -574,9 +574,9 @@ export default { 'withReset', ], 'storybook/internal/core-events': [ - 'AI_PREPARE_ANALYTICS_REQUEST', - 'AI_PREPARE_ANALYTICS_RESPONSE', 'AI_PROMPT_NUDGE', + 'AI_SETUP_ANALYTICS_REQUEST', + 'AI_SETUP_ANALYTICS_RESPONSE', 'ARGTYPES_INFO_REQUEST', 'ARGTYPES_INFO_RESPONSE', 'CHANNEL_CREATED', diff --git a/code/core/src/manager/settings/Checklist/AiSetupBlock.stories.tsx b/code/core/src/manager/settings/Checklist/AiSetupBlock.stories.tsx index 4baa70c22c60..8cad1b1341d2 100644 --- a/code/core/src/manager/settings/Checklist/AiSetupBlock.stories.tsx +++ b/code/core/src/manager/settings/Checklist/AiSetupBlock.stories.tsx @@ -23,14 +23,14 @@ const managerContext: any = { }, }; -// Get the raw aiPrepare item. Cast through unknown to avoid deep discriminated-union issues +// Get the raw aiSetup item. Cast through unknown to avoid deep discriminated-union issues // with the as-const typed checklistData — the shape is correct at runtime. -const rawAiPrepareItem = checklistData.sections +const rawAiSetupItem = checklistData.sections .flatMap((s) => s.items as unknown as ChecklistItem[]) - .find((item) => item.id === 'aiPrepare')!; + .find((item) => item.id === 'aiSetup')!; const makeItem = (overrides: Partial = {}): ChecklistItem => ({ - ...rawAiPrepareItem, + ...rawAiSetupItem, itemIndex: 0, sectionId: 'basics', sectionIndex: 0, diff --git a/code/core/src/manager/settings/Checklist/AiSetupBlock.tsx b/code/core/src/manager/settings/Checklist/AiSetupBlock.tsx index 6d38a3467603..b4f3e3033214 100644 --- a/code/core/src/manager/settings/Checklist/AiSetupBlock.tsx +++ b/code/core/src/manager/settings/Checklist/AiSetupBlock.tsx @@ -8,7 +8,7 @@ import { CheckIcon, UndoIcon } from '@storybook/icons'; import { type API, useStorybookApi } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; -import { AI_PREPARE_PROMPT } from '../../../shared/constants/ai-prompts.ts'; +import { AI_SETUP_PROMPT } from '../../../shared/constants/ai-prompts.ts'; import { useCopyButton } from '../../../shared/useCopyButton.ts'; import type { ItemId } from '../../../shared/checklist-store/index.ts'; import type { ChecklistItem } from '../../components/sidebar/useChecklist.ts'; @@ -61,9 +61,9 @@ const CopyButton = ({ api }: { api: API }) => { Copied! ), - content: AI_PREPARE_PROMPT, + content: AI_SETUP_PROMPT, onCopy: () => { - api.emit(AI_PROMPT_NUDGE, { id: 'prepare', origin: 'onboarding-guide-page' }); + api.emit(AI_PROMPT_NUDGE, { id: 'setup', origin: 'onboarding-guide-page' }); }, }); diff --git a/code/core/src/manager/settings/Checklist/Checklist.stories.tsx b/code/core/src/manager/settings/Checklist/Checklist.stories.tsx index e19cdb66d033..c4381c950ec0 100644 --- a/code/core/src/manager/settings/Checklist/Checklist.stories.tsx +++ b/code/core/src/manager/settings/Checklist/Checklist.stories.tsx @@ -84,27 +84,27 @@ export const Default = meta.story({ args: { availableItems, ...checklistStore }, }); -export const WithAiPrepare = meta.story({ +export const WithAiSetup = meta.story({ args: { availableItems: buildItems({ controls: 'accepted', renderComponent: 'done', whatsNewStorybook10: 'done', viewports: 'skipped', - // aiPrepare is intentionally omitted → status 'open' + // aiSetup is intentionally omitted → status 'open' }), ...checklistStore, }, }); -export const WithAiPrepareSkipped = meta.story({ +export const WithAiSetupSkipped = meta.story({ args: { availableItems: buildItems({ controls: 'accepted', renderComponent: 'done', whatsNewStorybook10: 'done', viewports: 'skipped', - aiPrepare: 'skipped', + aiSetup: 'skipped', }), ...checklistStore, }, diff --git a/code/core/src/manager/settings/GuidePage.stories.tsx b/code/core/src/manager/settings/GuidePage.stories.tsx index 308c252e5711..c617468fdba7 100644 --- a/code/core/src/manager/settings/GuidePage.stories.tsx +++ b/code/core/src/manager/settings/GuidePage.stories.tsx @@ -58,7 +58,7 @@ export const AiCtaSkipped = meta.story({ widget: {}, items: { ...initialState.items, - aiPrepare: { status: 'skipped' }, + aiSetup: { status: 'skipped' }, controls: { status: 'accepted' }, renderComponent: { status: 'done' }, viewports: { status: 'skipped' }, @@ -74,7 +74,7 @@ export const AiCtaDone = meta.story({ widget: {}, items: { ...initialState.items, - aiPrepare: { status: 'done' }, + aiSetup: { status: 'done' }, controls: { status: 'accepted' }, renderComponent: { status: 'done' }, viewports: { status: 'skipped' }, diff --git a/code/core/src/manager/settings/GuidePage.tsx b/code/core/src/manager/settings/GuidePage.tsx index 6d7d28d9121a..9dae45ec510b 100644 --- a/code/core/src/manager/settings/GuidePage.tsx +++ b/code/core/src/manager/settings/GuidePage.tsx @@ -38,7 +38,7 @@ const Intro = styled.div(({ theme }) => ({ export const GuidePage = () => { const checklist = useChecklist(); - const aiPrepareItem = checklist.availableItems.find((item) => item.id === 'aiPrepare'); + const aiSetupItem = checklist.availableItems.find((item) => item.id === 'aiSetup'); return ( @@ -49,10 +49,10 @@ export const GuidePage = () => { will help you make the most of your Storybook.

- {aiPrepareItem && ( - + {aiSetupItem && ( + )} - + {global.FEATURES?.sidebarOnboardingChecklist !== false && ( <> {checklist.openItems.length === 0 ? ( diff --git a/code/core/src/shared/checklist-store/checklistData.state.ts b/code/core/src/shared/checklist-store/checklistData.state.ts index 39899531c972..0f85b44b26f9 100644 --- a/code/core/src/shared/checklist-store/checklistData.state.ts +++ b/code/core/src/shared/checklist-store/checklistData.state.ts @@ -3,7 +3,7 @@ import type { StoreState } from './index.ts'; export const initialState = { items: { accessibilityTests: { status: 'open' }, - aiPrepare: { status: 'open' }, + aiSetup: { status: 'open' }, autodocs: { status: 'open' }, ciTests: { status: 'open' }, controls: { status: 'open' }, diff --git a/code/core/src/shared/checklist-store/checklistData.tsx b/code/core/src/shared/checklist-store/checklistData.tsx index 7cc0e02061f9..0be79c578c2a 100644 --- a/code/core/src/shared/checklist-store/checklistData.tsx +++ b/code/core/src/shared/checklist-store/checklistData.tsx @@ -35,7 +35,7 @@ import { ADDON_ID as ADDON_DOCS_ID } from '../../docs-tools/shared.ts'; import { TourGuide } from '../../manager/components/TourGuide/TourGuide.tsx'; import { LocationMonitor } from '../../manager/hooks/useLocation.ts'; import type { initialState } from './checklistData.state.ts'; -import { AI_PREPARE_PROMPT } from '../constants/ai-prompts.ts'; +import { AI_SETUP_PROMPT } from '../constants/ai-prompts.ts'; const CodeWrapper = styled.div(({ theme }) => ({ alignSelf: 'stretch', @@ -163,16 +163,16 @@ export const checklistData = { title: 'Storybook basics', items: [ { - id: 'aiPrepare', + id: 'aiSetup', label: 'Set up with AI', icon: WandIcon, - criteria: 'ai prepare command has been run', + criteria: 'ai setup command has been run', showOnGuidePage: false, action: { label: 'Copy prompt', - copyContent: AI_PREPARE_PROMPT, + copyContent: AI_SETUP_PROMPT, onClick: ({ api }) => { - api.emit(AI_PROMPT_NUDGE, { id: 'prepare', origin: 'onboarding-checklist-side' }); + api.emit(AI_PROMPT_NUDGE, { id: 'setup', origin: 'onboarding-checklist-side' }); }, }, }, diff --git a/code/core/src/shared/constants/ai-prompts.ts b/code/core/src/shared/constants/ai-prompts.ts index 245769b65b12..0c1d6f904bd5 100644 --- a/code/core/src/shared/constants/ai-prompts.ts +++ b/code/core/src/shared/constants/ai-prompts.ts @@ -2,4 +2,4 @@ * Source of truth for AI prompts throughout the application. */ -export const AI_PREPARE_PROMPT = `Run \`npx storybook ai prepare\` and follow its instructions precisely.`; +export const AI_SETUP_PROMPT = `Run \`npx storybook ai setup\` and follow its instructions precisely.`; diff --git a/code/core/src/telemetry/ai-prepare-utils.test.ts b/code/core/src/telemetry/ai-setup-utils.test.ts similarity index 86% rename from code/core/src/telemetry/ai-prepare-utils.test.ts rename to code/core/src/telemetry/ai-setup-utils.test.ts index afd0384fcd03..184f34c1cfb3 100644 --- a/code/core/src/telemetry/ai-prepare-utils.test.ts +++ b/code/core/src/telemetry/ai-setup-utils.test.ts @@ -4,10 +4,10 @@ import type { IndexEntry, StoryIndex } from 'storybook/internal/types'; import { checkPreviewChanged, - collectAiPrepareEvidence, + collectAiSetupEvidence, countAiAuthoredStories, - isStoryCreatedByAIPrepare, -} from './ai-prepare-utils.ts'; + isStoryCreatedByAISetup, +} from './ai-setup-utils.ts'; // Mock modules with spy pattern vi.mock('storybook/internal/common', async (importOriginal) => { @@ -26,7 +26,7 @@ vi.mock('./event-cache.ts', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - getAiPreparePending: vi.fn(() => undefined), + getAiSetupPending: vi.fn(() => undefined), }; }); @@ -42,7 +42,7 @@ vi.mock('./index.ts', async (importOriginal) => { import { findConfigFile } from 'storybook/internal/common'; import { readFile } from 'node:fs/promises'; import { detectAgent } from './detect-agent.ts'; -import { getAiPreparePending } from './event-cache.ts'; +import { getAiSetupPending } from './event-cache.ts'; import { SESSION_TIMEOUT } from './session-id.ts'; import { telemetry } from './index.ts'; @@ -73,10 +73,10 @@ beforeEach(() => { vi.mocked(telemetry).mockResolvedValue(undefined); }); -describe('isStoryCreatedByAIPrepare', () => { +describe('isStoryCreatedByAISetup', () => { it('returns true for stories with the ai-generated tag', () => { expect( - isStoryCreatedByAIPrepare({ + isStoryCreatedByAISetup({ type: 'story', title: 'Foo', tags: ['ai-generated', 'dev', 'play-fn'], @@ -85,7 +85,7 @@ describe('isStoryCreatedByAIPrepare', () => { }); it('returns false for regular stories', () => { - expect(isStoryCreatedByAIPrepare({ type: 'story', title: 'Foo' } as IndexEntry)).toBe(false); + expect(isStoryCreatedByAISetup({ type: 'story', title: 'Foo' } as IndexEntry)).toBe(false); }); }); @@ -192,53 +192,53 @@ describe('checkPreviewChanged', () => { }); }); -describe('collectAiPrepareEvidence', () => { +describe('collectAiSetupEvidence', () => { it('does not fire when no agent detected', async () => { vi.mocked(detectAgent).mockReturnValue(undefined); - await collectAiPrepareEvidence('dev', '/test/config'); + await collectAiSetupEvidence('dev', '/test/config'); expect(telemetry).not.toHaveBeenCalled(); }); it('does not fire when no pending record', async () => { vi.mocked(detectAgent).mockReturnValue({ name: 'claude' }); - vi.mocked(getAiPreparePending).mockResolvedValue(undefined); + vi.mocked(getAiSetupPending).mockResolvedValue(undefined); - await collectAiPrepareEvidence('dev', '/test/config'); + await collectAiSetupEvidence('dev', '/test/config'); expect(telemetry).not.toHaveBeenCalled(); }); it('does not fire when pending record is expired', async () => { vi.mocked(detectAgent).mockReturnValue({ name: 'claude' }); - vi.mocked(getAiPreparePending).mockResolvedValue( + vi.mocked(getAiSetupPending).mockResolvedValue( makePendingRecord({ timestamp: Date.now() - SESSION_TIMEOUT - 1000 }) ); - await collectAiPrepareEvidence('dev', '/test/config'); + await collectAiSetupEvidence('dev', '/test/config'); expect(telemetry).not.toHaveBeenCalled(); }); it('does not fire when configDir does not match', async () => { vi.mocked(detectAgent).mockReturnValue({ name: 'claude' }); - vi.mocked(getAiPreparePending).mockResolvedValue( + vi.mocked(getAiSetupPending).mockResolvedValue( makePendingRecord({ configDir: '/other/project/.storybook' }) ); - await collectAiPrepareEvidence('dev', '/test/config'); + await collectAiSetupEvidence('dev', '/test/config'); expect(telemetry).not.toHaveBeenCalled(); }); it('fires event with correct payload when all gates pass', async () => { vi.mocked(detectAgent).mockReturnValue({ name: 'claude' }); const pending = makePendingRecord({ configDir: '/test/config' }); - vi.mocked(getAiPreparePending).mockResolvedValue(pending); + vi.mocked(getAiSetupPending).mockResolvedValue(pending); vi.mocked(findConfigFile).mockReturnValue(pending.previewPath); vi.mocked(readFile).mockRejectedValue(new Error('ENOENT')); - await collectAiPrepareEvidence('dev', '/test/config'); + await collectAiSetupEvidence('dev', '/test/config'); expect(telemetry).toHaveBeenCalledWith( - 'ai-prepare-evidence', + 'ai-setup-evidence', expect.objectContaining({ previewChanged: true, aiAuthoredStories: undefined, @@ -254,13 +254,13 @@ describe('collectAiPrepareEvidence', () => { it('reports aiAuthoredStories as undefined when no story index provided', async () => { vi.mocked(detectAgent).mockReturnValue({ name: 'claude' }); const pending = makePendingRecord({ configDir: '/test/config' }); - vi.mocked(getAiPreparePending).mockResolvedValue(pending); + vi.mocked(getAiSetupPending).mockResolvedValue(pending); vi.mocked(findConfigFile).mockReturnValue(null); - await collectAiPrepareEvidence('dev', '/test/config'); + await collectAiSetupEvidence('dev', '/test/config'); expect(telemetry).toHaveBeenCalledWith( - 'ai-prepare-evidence', + 'ai-setup-evidence', expect.objectContaining({ aiAuthoredStories: undefined, }), @@ -275,7 +275,7 @@ describe('collectAiPrepareEvidence', () => { previewFile: null, previewHash: null, }); - vi.mocked(getAiPreparePending).mockResolvedValue(pending); + vi.mocked(getAiSetupPending).mockResolvedValue(pending); vi.mocked(findConfigFile).mockReturnValue(null); const storyIndex = makeStoryIndex({ @@ -296,10 +296,10 @@ describe('collectAiPrepareEvidence', () => { }, }); - await collectAiPrepareEvidence('dev', '/test/config', storyIndex); + await collectAiSetupEvidence('dev', '/test/config', storyIndex); expect(telemetry).toHaveBeenCalledWith( - 'ai-prepare-evidence', + 'ai-setup-evidence', expect.objectContaining({ aiAuthoredStories: 1, }), diff --git a/code/core/src/telemetry/ai-prepare-utils.ts b/code/core/src/telemetry/ai-setup-utils.ts similarity index 81% rename from code/core/src/telemetry/ai-prepare-utils.ts rename to code/core/src/telemetry/ai-setup-utils.ts index 203c744c97d4..fac23a2098a8 100644 --- a/code/core/src/telemetry/ai-prepare-utils.ts +++ b/code/core/src/telemetry/ai-setup-utils.ts @@ -1,4 +1,4 @@ -import { flushAiPreparePending, getAiPreparePending } from './event-cache.ts'; +import { flushAiSetupPending, getAiSetupPending } from './event-cache.ts'; import { SESSION_TIMEOUT } from './session-id.ts'; import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; @@ -10,19 +10,19 @@ import type { EventType } from './types.ts'; import type { IndexEntry, StoryIndex } from 'storybook/internal/types'; /** - * Determines whether a story index entry was authored by the `sb ai prepare` flow. + * Determines whether a story index entry was authored by the `sb ai setup` flow. * Currently checks title prefix. When we migrate to a tag-based approach, * swap this to check for the tag instead — this is the single swap point. */ -export function isStoryCreatedByAIPrepare(entry: IndexEntry): boolean { +export function isStoryCreatedByAISetup(entry: IndexEntry): boolean { return entry.type === 'story' && (entry.tags?.includes('ai-generated') ?? false); } /** - * Count stories in the index that were created by `sb ai prepare`. + * Count stories in the index that were created by `sb ai setup`. */ export function countAiAuthoredStories(storyIndex: StoryIndex): number { - return Object.values(storyIndex.entries).filter(isStoryCreatedByAIPrepare).length; + return Object.values(storyIndex.entries).filter(isStoryCreatedByAISetup).length; } /** @@ -48,7 +48,7 @@ export async function snapshotPreviewFile( } /** - * Check whether the preview file has changed from an ai-prepare baseline. + * Check whether the preview file has changed from an ai-setup baseline. * Returns true if: hash differs, file appeared, file disappeared, or file is unreadable. */ export async function checkPreviewChanged( @@ -73,7 +73,7 @@ export async function checkPreviewChanged( } /** - * Check for a pending ai-prepare record and fire an evidence event if found. + * Check for a pending ai-setup record and fire an evidence event if found. * * Called from: * - `withTelemetry` after the boot event for non-dev/build CLI commands (no story index) @@ -81,7 +81,7 @@ export async function checkPreviewChanged( * * Gated on: agent detected → pending record exists → within session window → configDir matches. */ -export async function collectAiPrepareEvidence( +export async function collectAiSetupEvidence( eventType: EventType, configDir: string | undefined, storyIndex?: StoryIndex @@ -93,8 +93,8 @@ export async function collectAiPrepareEvidence( return; } - // Gate 2: Is there a pending ai-prepare record? - const pending = await getAiPreparePending(); + // Gate 2: Is there a pending ai-setup record? + const pending = await getAiSetupPending(); if (!pending) { return; } @@ -105,17 +105,17 @@ export async function collectAiPrepareEvidence( } // Gate 4: Is it within the session window? - const timeSincePrepare = Date.now() - pending.timestamp; - if (timeSincePrepare > SESSION_TIMEOUT) { + const timeSinceSetup = Date.now() - pending.timestamp; + if (timeSinceSetup > SESSION_TIMEOUT) { // Session expired, clean up pending record. - await flushAiPreparePending(); + await flushAiSetupPending(); return; } - // Don't fire evidence for ai-prepare itself — the prepare command gives the + // Don't fire evidence for ai-setup itself — the setup command gives the // prompt to the agent and exits, so we only expect changes after the agent // has started processing it. - if (eventType === 'ai-prepare') { + if (eventType === 'ai-setup') { return; } @@ -126,12 +126,12 @@ export async function collectAiPrepareEvidence( const aiAuthoredStories = storyIndex ? countAiAuthoredStories(storyIndex) : undefined; await telemetry( - 'ai-prepare-evidence', + 'ai-setup-evidence', { previewChanged, aiAuthoredStories, sessionId: pending.sessionId, - timeSincePrepare, + timeSinceSetup, }, { immediate: true, diff --git a/code/core/src/telemetry/event-cache.test.ts b/code/core/src/telemetry/event-cache.test.ts index 76b7a701e156..bd0f9494c7c9 100644 --- a/code/core/src/telemetry/event-cache.test.ts +++ b/code/core/src/telemetry/event-cache.test.ts @@ -5,8 +5,8 @@ import { cache } from 'storybook/internal/common'; import type { CacheEntry } from './event-cache.ts'; import { - flushAiPreparePending, - getAiPreparePending, + flushAiSetupPending, + getAiSetupPending, getLastEvents, getPrecedingUpgrade, set, @@ -354,7 +354,7 @@ describe('event-cache', () => { }); }); - describe('ai-prepare pending cache', () => { + describe('ai-setup pending cache', () => { let cacheGetMock: MockInstance; let cacheRemoveMock: MockInstance; @@ -364,7 +364,7 @@ describe('event-cache', () => { cacheRemoveMock = vi.mocked(cache.remove); }); - it('returns cached ai-prepare pending record when present', async () => { + it('returns cached ai-setup pending record when present', async () => { const pending = { timestamp: 123, sessionId: 'session-1', @@ -375,18 +375,18 @@ describe('event-cache', () => { cacheGetMock.mockResolvedValueOnce(pending); - await expect(getAiPreparePending()).resolves.toEqual(pending); - expect(cacheGetMock).toHaveBeenCalledWith('ai-prepare-pending'); + await expect(getAiSetupPending()).resolves.toEqual(pending); + expect(cacheGetMock).toHaveBeenCalledWith('ai-setup-pending'); }); - it('removes the cached ai-prepare pending record and returns undefined', async () => { + it('removes the cached ai-setup pending record and returns undefined', async () => { cacheRemoveMock.mockResolvedValueOnce(undefined); cacheGetMock.mockResolvedValueOnce(undefined); - await expect(flushAiPreparePending()).resolves.toBeUndefined(); - expect(cacheRemoveMock).toHaveBeenCalledWith('ai-prepare-pending'); - await expect(getAiPreparePending()).resolves.toBeUndefined(); - expect(cacheGetMock).toHaveBeenCalledWith('ai-prepare-pending'); + await expect(flushAiSetupPending()).resolves.toBeUndefined(); + expect(cacheRemoveMock).toHaveBeenCalledWith('ai-setup-pending'); + await expect(getAiSetupPending()).resolves.toBeUndefined(); + expect(cacheGetMock).toHaveBeenCalledWith('ai-setup-pending'); }); }); }); diff --git a/code/core/src/telemetry/event-cache.ts b/code/core/src/telemetry/event-cache.ts index ae3d265d10a1..84b1a98a889d 100644 --- a/code/core/src/telemetry/event-cache.ts +++ b/code/core/src/telemetry/event-cache.ts @@ -82,11 +82,11 @@ export const getPrecedingUpgrade = async ( : undefined; }; /** - * Record cached at ai-prepare time. + * Record cached at ai-setup time. * Read by subsequent CLI entry points for evidence collection. * Canonical definition — imported by event-cache.ts and prepare-requirements.ts. */ -export interface AiPreparePendingRecord { +export interface AiSetupPendingRecord { timestamp: number; sessionId: string; configDir: string; @@ -94,15 +94,15 @@ export interface AiPreparePendingRecord { previewHash: string | null; } -export const getAiPreparePending = async (): Promise => { +export const getAiSetupPending = async (): Promise => { // Wait for any pending set operations to complete before reading await processingPromise; - return (await cache.get('ai-prepare-pending')) ?? undefined; + return (await cache.get('ai-setup-pending')) ?? undefined; }; -export const flushAiPreparePending = async (): Promise => { +export const flushAiSetupPending = async (): Promise => { // Wait for any pending set operations to complete before removing await processingPromise; - await cache.remove('ai-prepare-pending'); + await cache.remove('ai-setup-pending'); return undefined; }; diff --git a/code/core/src/telemetry/index.ts b/code/core/src/telemetry/index.ts index a27b99e44e55..438b38799c71 100644 --- a/code/core/src/telemetry/index.ts +++ b/code/core/src/telemetry/index.ts @@ -16,21 +16,21 @@ export * from './sanitize.ts'; export * from './error-collector.ts'; -export * from './ai-prepare-utils.ts'; +export * from './ai-setup-utils.ts'; export { getPrecedingUpgrade, getLastEvents, type CacheEntry, - getAiPreparePending, - type AiPreparePendingRecord, + getAiSetupPending, + type AiSetupPendingRecord, } from './event-cache.ts'; export { getSessionId, SESSION_TIMEOUT } from './session-id.ts'; export { addToGlobalContext } from './telemetry.ts'; -export { detectAgent } from './detect-agent.ts'; +export { detectAgent, type AgentInfo } from './detect-agent.ts'; /** Is this story part of the CLI generated examples, including user-created stories in those files */ export const isExampleStoryId = (storyId: string) => diff --git a/code/core/src/telemetry/types.ts b/code/core/src/telemetry/types.ts index de4945851310..9b784604d6ab 100644 --- a/code/core/src/telemetry/types.ts +++ b/code/core/src/telemetry/types.ts @@ -45,9 +45,9 @@ export type EventType = | 'doctor' | 'share' | 'ghost-stories' - | 'ai-prepare' - | 'ai-prepare-evidence' - | 'ai-prepare-story-scoring' + | 'ai-setup' + | 'ai-setup-evidence' + | 'ai-setup-story-scoring' | 'ai-prompt-nudge'; export interface Dependency { diff --git a/code/lib/cli-storybook/src/ai/index.ts b/code/lib/cli-storybook/src/ai/index.ts index 5110b798b050..4107590ffc68 100644 --- a/code/lib/cli-storybook/src/ai/index.ts +++ b/code/lib/cli-storybook/src/ai/index.ts @@ -8,7 +8,7 @@ import { getSessionId, snapshotPreviewFile, telemetry, - type AiPreparePendingRecord, + type AiSetupPendingRecord, } from 'storybook/internal/telemetry'; import { SupportedLanguage } from 'storybook/internal/types'; @@ -16,9 +16,9 @@ import { ProjectTypeService } from '../../../create-storybook/src/services/Proje import { getStorybookData } from '../automigrate/helpers/mainConfigFile.ts'; import { generateMarkdownOutput } from './prompt.ts'; -import type { ProjectInfo, AiPrepareOptions } from './types.ts'; +import type { ProjectInfo, AiSetupOptions } from './types.ts'; -export async function aiPrepare(options: AiPrepareOptions): Promise { +export async function aiSetup(options: AiSetupOptions): Promise { const { configDir: userConfigDir, packageManager: packageManagerName, output } = options; let projectInfo: ProjectInfo; @@ -82,7 +82,7 @@ export async function aiPrepare(options: AiPrepareOptions): Promise { const result = generateMarkdownOutput(projectInfo); const markdownOutput = result.markdown; - await telemetry('ai-prepare', { + await telemetry('ai-setup', { cliOptions: { output: output ? 'file' : undefined, configDir: projectInfo.configDir, @@ -103,13 +103,13 @@ export async function aiPrepare(options: AiPrepareOptions): Promise { const resolvedConfigDir = resolve(projectInfo.configDir); const previewSnapshot = await snapshotPreviewFile(resolvedConfigDir); const sessionId = await getSessionId(); - const pendingRecord: AiPreparePendingRecord = { + const pendingRecord: AiSetupPendingRecord = { timestamp: Date.now(), sessionId, configDir: resolvedConfigDir, ...previewSnapshot, }; - await cache.set('ai-prepare-pending', pendingRecord); + await cache.set('ai-setup-pending', pendingRecord); if (output) { const outputPath = resolve(output); diff --git a/code/lib/cli-storybook/src/ai/types.ts b/code/lib/cli-storybook/src/ai/types.ts index 5d7e0abeb2ec..3a9a1b52be28 100644 --- a/code/lib/cli-storybook/src/ai/types.ts +++ b/code/lib/cli-storybook/src/ai/types.ts @@ -1,6 +1,6 @@ import type { SupportedRenderer } from 'storybook/internal/types'; -export interface AiPrepareOptions { +export interface AiSetupOptions { configDir?: string; packageManager?: string; output?: string; diff --git a/code/lib/cli-storybook/src/bin/run.ts b/code/lib/cli-storybook/src/bin/run.ts index 28ad72ca7ad8..bb96b8e94ac3 100644 --- a/code/lib/cli-storybook/src/bin/run.ts +++ b/code/lib/cli-storybook/src/bin/run.ts @@ -23,7 +23,7 @@ import { doctor } from '../doctor/index.ts'; import { link } from '../link.ts'; import { migrate } from '../migrate.ts'; import { sandbox } from '../sandbox.ts'; -import { aiPrepare } from '../ai/index.ts'; +import { aiSetup } from '../ai/index.ts'; import { type UpgradeOptions, upgrade } from '../upgrade.ts'; addToGlobalContext('cliVersion', versions.storybook); @@ -307,7 +307,7 @@ const aiCommand = command('ai') ); aiCommand - .command('prepare') + .command('setup') .description('Generate setup instructions to write stories for real components') .addOption( new Option('--package-manager ', 'Force package manager for installing deps').choices( @@ -318,8 +318,8 @@ aiCommand .action(async (options, cmd) => { const parentOptions = cmd.parent?.opts() ?? {}; const mergedOptions = { ...parentOptions, ...options }; - await withTelemetry('ai-prepare', { cliOptions: mergedOptions }, async () => { - await aiPrepare(mergedOptions); + await withTelemetry('ai-setup', { cliOptions: mergedOptions }, async () => { + await aiSetup(mergedOptions); }).catch(handleCommandFailure(mergedOptions.logfile)); }); diff --git a/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts b/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts index 1a45fd8601a0..7eec40c78880 100644 --- a/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts +++ b/code/lib/create-storybook/src/commands/FinalizationCommand.test.ts @@ -126,7 +126,7 @@ describe('FinalizationCommand', () => { expect(logger.step).toHaveBeenCalledWith( expect.stringContaining('is not entirely set up yet') ); - expect(logger.step).toHaveBeenCalledWith(expect.stringContaining('npx storybook ai prepare')); + expect(logger.step).toHaveBeenCalledWith(expect.stringContaining('npx storybook ai setup')); }); it('should show standard success message when showAgentFollowUp=false with AI instructions', async () => { @@ -180,7 +180,7 @@ describe('FinalizationCommand', () => { expect(logger.step).toHaveBeenCalledWith( expect.stringContaining('To finalize setting up with AI') ); - expect(logger.step).toHaveBeenCalledWith(expect.stringContaining('npx storybook ai prepare')); + expect(logger.step).toHaveBeenCalledWith(expect.stringContaining('npx storybook ai setup')); }); it('should NOT show AI instructions when showAiInstructions=false', async () => { diff --git a/code/lib/create-storybook/src/commands/FinalizationCommand.ts b/code/lib/create-storybook/src/commands/FinalizationCommand.ts index 78348f277fef..e8665ca1e907 100644 --- a/code/lib/create-storybook/src/commands/FinalizationCommand.ts +++ b/code/lib/create-storybook/src/commands/FinalizationCommand.ts @@ -9,7 +9,7 @@ import { dedent } from 'ts-dedent'; export type FinalizationCommandOptions = { logfile: string | boolean | undefined; - /** When true, show the agent follow-up message ("run npx storybook ai prepare") instead of the normal success message */ + /** When true, show the agent follow-up message ("run npx storybook ai setup") instead of the normal success message */ showAgentFollowUp: boolean; /** When true, show the "paste this prompt to your AI agent" instructions */ showAiInstructions: boolean; @@ -89,7 +89,7 @@ export class FinalizationCommand { logger.step( CLI_COLORS.storybook( dedent`Storybook is installed but is not entirely set up yet. - To finish setting up, now run \`npx storybook ai prepare\` and follow its instructions precisely.` + To finish setting up, now run \`npx storybook ai setup\` and follow its instructions precisely.` ) ); } else { @@ -111,7 +111,7 @@ export class FinalizationCommand { if (this.options.showAiInstructions) { logger.step(dedent`To finalize setting up with AI, paste this prompt to your AI agent: - ${CLI_COLORS.storybook(`Run \`npx storybook ai prepare\` and follow its instructions precisely.`)} + ${CLI_COLORS.storybook(`Run \`npx storybook ai setup\` and follow its instructions precisely.`)} `); } } diff --git a/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts b/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts index f62b63ce47c2..c7b2186b289a 100644 --- a/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts +++ b/code/lib/create-storybook/src/commands/UserPreferencesCommand.test.ts @@ -21,7 +21,7 @@ interface CommandWithPrivates { telemetryService: { trackNewUserCheck: ReturnType; trackInstallType: ReturnType; - trackAiPromptNudge: ReturnType; + trackAiSetupNudge: ReturnType; }; } @@ -35,7 +35,7 @@ describe('UserPreferencesCommand', () => { renderer: 'react' as SupportedRenderer, projectType: ProjectType.REACT, isTestFeatureAvailable: true, - isAiPrepareAvailable: false, + isAiSetupAvailable: false, }; afterAll(() => { @@ -93,7 +93,7 @@ describe('UserPreferencesCommand', () => { const mockTelemetryService = { trackNewUserCheck: vi.fn(), trackInstallType: vi.fn(), - trackAiPromptNudge: vi.fn(), + trackAiSetupNudge: vi.fn(), }; // Inject mocked services @@ -225,7 +225,7 @@ describe('UserPreferencesCommand', () => { const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(prompt.confirm).toHaveBeenCalledWith( @@ -249,7 +249,7 @@ describe('UserPreferencesCommand', () => { const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(result.selectedFeatures.has(Feature.AI)).toBe(false); @@ -263,7 +263,7 @@ describe('UserPreferencesCommand', () => { const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(prompt.confirm).not.toHaveBeenCalled(); @@ -287,19 +287,19 @@ describe('UserPreferencesCommand', () => { (yesCommand as unknown as CommandWithPrivates).telemetryService = { trackNewUserCheck: vi.fn(), trackInstallType: vi.fn(), - trackAiPromptNudge: vi.fn(), + trackAiSetupNudge: vi.fn(), }; const result = await yesCommand.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(prompt.confirm).not.toHaveBeenCalled(); expect(result.selectedFeatures.has(Feature.AI)).toBe(true); }); - it('should not prompt for AI setup when isAiPrepareAvailable is false', async () => { + it('should not prompt for AI setup when isAiSetupAvailable is false', async () => { Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true, @@ -309,7 +309,7 @@ describe('UserPreferencesCommand', () => { const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: false, + isAiSetupAvailable: false, }); expect(prompt.confirm).not.toHaveBeenCalled(); @@ -329,7 +329,7 @@ describe('UserPreferencesCommand', () => { const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(result.selectedFeatures.has(Feature.AI)).toBe(true); @@ -351,7 +351,7 @@ describe('UserPreferencesCommand', () => { const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(result.selectedFeatures.has(Feature.AI)).toBe(false); @@ -370,11 +370,11 @@ describe('UserPreferencesCommand', () => { await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); const telemetryService = (command as unknown as CommandWithPrivates).telemetryService; - expect(telemetryService.trackAiPromptNudge).toHaveBeenCalledWith({ skipPrompt: false }); + expect(telemetryService.trackAiSetupNudge).toHaveBeenCalledWith({ skipPrompt: false }); }); it('should not track ai-prompt-nudge telemetry when user declines AI setup', async () => { @@ -388,23 +388,23 @@ describe('UserPreferencesCommand', () => { await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); const telemetryService = (command as unknown as CommandWithPrivates).telemetryService; - expect(telemetryService.trackAiPromptNudge).not.toHaveBeenCalled(); + expect(telemetryService.trackAiSetupNudge).not.toHaveBeenCalled(); }); it('should track ai-prompt-nudge telemetry when AI is auto-accepted in non-interactive mode', async () => { // Non-interactive (no TTY) with AI available — auto-accepts const result = await command.execute({ ...defaultExecuteOptions, - isAiPrepareAvailable: true, + isAiSetupAvailable: true, }); expect(result.selectedFeatures.has(Feature.AI)).toBe(true); const telemetryService = (command as unknown as CommandWithPrivates).telemetryService; - expect(telemetryService.trackAiPromptNudge).toHaveBeenCalledWith({ skipPrompt: true }); + expect(telemetryService.trackAiSetupNudge).toHaveBeenCalledWith({ skipPrompt: true }); }); }); diff --git a/code/lib/create-storybook/src/commands/UserPreferencesCommand.ts b/code/lib/create-storybook/src/commands/UserPreferencesCommand.ts index f16a6af3c815..bc25b712bc7d 100644 --- a/code/lib/create-storybook/src/commands/UserPreferencesCommand.ts +++ b/code/lib/create-storybook/src/commands/UserPreferencesCommand.ts @@ -35,7 +35,7 @@ export interface UserPreferencesOptions { renderer: SupportedRenderer; projectType: ProjectType; isTestFeatureAvailable: boolean; - isAiPrepareAvailable: boolean; + isAiSetupAvailable: boolean; } /** @@ -80,9 +80,7 @@ export class UserPreferencesCommand { : 'recommended'; // Ask about AI setup (only available for compatible projects, e.g. React + Vite) - const useAiForSetup = options.isAiPrepareAvailable - ? await this.promptAiSetup(skipPrompt) - : false; + const useAiForSetup = options.isAiSetupAvailable ? await this.promptAiSetup(skipPrompt) : false; const selectedFeatures = this.determineFeatures( installType, @@ -228,7 +226,7 @@ export class UserPreferencesCommand { }); if (useAi) { - await this.telemetryService.trackAiPromptNudge({ skipPrompt }); + await this.telemetryService.trackAiSetupNudge({ skipPrompt }); } return useAi; diff --git a/code/lib/create-storybook/src/initiate.ts b/code/lib/create-storybook/src/initiate.ts index e8ec6c27e546..5b6452862004 100644 --- a/code/lib/create-storybook/src/initiate.ts +++ b/code/lib/create-storybook/src/initiate.ts @@ -29,7 +29,7 @@ import type { CommandOptions } from './generators/types.ts'; import { FeatureCompatibilityService } from './services/FeatureCompatibilityService.ts'; import { TelemetryService } from './services/TelemetryService.ts'; -/** Validate test feature compatibility and check AI prepare support */ +/** Validate test feature compatibility and check AI setup support */ async function checkFeatureSupport( packageManager: JsPackageManager, framework: SupportedFramework | null, @@ -37,7 +37,7 @@ async function checkFeatureSupport( renderer: SupportedRenderer ): Promise<{ isTestFeatureAvailable: boolean; - isAiPrepareAvailable: boolean; + isAiSetupAvailable: boolean; }> { const featureService = new FeatureCompatibilityService(packageManager); @@ -47,11 +47,11 @@ async function checkFeatureSupport( process.cwd() ); - const aiPrepare = FeatureCompatibilityService.supportsAIPrepareFeature(renderer, builder); + const aiSetup = FeatureCompatibilityService.supportsAISetupFeature(renderer, builder); return { isTestFeatureAvailable: result.compatible, - isAiPrepareAvailable: aiPrepare, + isAiSetupAvailable: aiSetup, }; } @@ -97,7 +97,7 @@ export async function doInitiate(options: CommandOptions): Promise< ); // Step 4: Get user preferences and feature selections (with framework/builder for validation) - const { isTestFeatureAvailable, isAiPrepareAvailable } = await checkFeatureSupport( + const { isTestFeatureAvailable, isAiSetupAvailable } = await checkFeatureSupport( packageManager, framework, builder, @@ -111,7 +111,7 @@ export async function doInitiate(options: CommandOptions): Promise< renderer, projectType, isTestFeatureAvailable, - isAiPrepareAvailable, + isAiSetupAvailable, }); // Step 5: Execute generator with dependency collector (now with frameworkInfo) diff --git a/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts b/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts index 3f6145b596d3..93e0b7b71042 100644 --- a/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts +++ b/code/lib/create-storybook/src/services/FeatureCompatibilityService.test.ts @@ -44,10 +44,10 @@ describe('FeatureCompatibilityService', () => { }); }); - describe('supportsAIPrepareFeature', () => { + describe('supportsAISetupFeature', () => { it('should return true for react renderer with vite builder', () => { expect( - FeatureCompatibilityService.supportsAIPrepareFeature( + FeatureCompatibilityService.supportsAISetupFeature( SupportedRenderer.REACT, SupportedBuilder.VITE ) @@ -56,7 +56,7 @@ describe('FeatureCompatibilityService', () => { it('should return false for vue3 renderer with vite builder', () => { expect( - FeatureCompatibilityService.supportsAIPrepareFeature( + FeatureCompatibilityService.supportsAISetupFeature( SupportedRenderer.VUE3, SupportedBuilder.VITE ) @@ -65,7 +65,7 @@ describe('FeatureCompatibilityService', () => { it('should return false for react renderer with webpack5 builder', () => { expect( - FeatureCompatibilityService.supportsAIPrepareFeature( + FeatureCompatibilityService.supportsAISetupFeature( SupportedRenderer.REACT, SupportedBuilder.WEBPACK5 ) @@ -74,14 +74,14 @@ describe('FeatureCompatibilityService', () => { it('should return false for non-react renderer with non-vite builder', () => { expect( - FeatureCompatibilityService.supportsAIPrepareFeature( + FeatureCompatibilityService.supportsAISetupFeature( SupportedRenderer.ANGULAR, SupportedBuilder.WEBPACK5 ) ).toBe(false); expect( - FeatureCompatibilityService.supportsAIPrepareFeature( + FeatureCompatibilityService.supportsAISetupFeature( SupportedRenderer.SVELTE, SupportedBuilder.WEBPACK5 ) diff --git a/code/lib/create-storybook/src/services/FeatureCompatibilityService.ts b/code/lib/create-storybook/src/services/FeatureCompatibilityService.ts index 7f78a1214027..3097b2406b2d 100644 --- a/code/lib/create-storybook/src/services/FeatureCompatibilityService.ts +++ b/code/lib/create-storybook/src/services/FeatureCompatibilityService.ts @@ -33,8 +33,8 @@ export class FeatureCompatibilityService { ); } - /** Check if AI-assisted setup (storybook ai prepare) is supported for this project configuration */ - static supportsAIPrepareFeature(renderer: SupportedRenderer, builder: SupportedBuilder): boolean { + /** Check if AI-assisted setup (storybook ai setup) is supported for this project configuration */ + static supportsAISetupFeature(renderer: SupportedRenderer, builder: SupportedBuilder): boolean { return renderer === SupportedRenderer.REACT && builder === SupportedBuilder.VITE; } diff --git a/code/lib/create-storybook/src/services/TelemetryService.test.ts b/code/lib/create-storybook/src/services/TelemetryService.test.ts index 404957f7d3be..b816482ce6be 100644 --- a/code/lib/create-storybook/src/services/TelemetryService.test.ts +++ b/code/lib/create-storybook/src/services/TelemetryService.test.ts @@ -73,20 +73,20 @@ describe('TelemetryService', () => { }); it('should track ai-prompt-nudge event with context when prompt was shown', async () => { - await telemetryService.trackAiPromptNudge({ skipPrompt: false }); + await telemetryService.trackAiSetupNudge({ skipPrompt: false }); expect(telemetry).toHaveBeenCalledWith('ai-prompt-nudge', { - id: 'prepare', + id: 'setup', origin: 'init', context: { skipPrompt: false }, }); }); it('should track ai-prompt-nudge event with context when prompt was skipped', async () => { - await telemetryService.trackAiPromptNudge({ skipPrompt: true }); + await telemetryService.trackAiSetupNudge({ skipPrompt: true }); expect(telemetry).toHaveBeenCalledWith('ai-prompt-nudge', { - id: 'prepare', + id: 'setup', origin: 'init', context: { skipPrompt: true }, }); @@ -137,7 +137,7 @@ describe('TelemetryService', () => { }); it('should not track ai-prompt-nudge event', async () => { - await telemetryService.trackAiPromptNudge({ skipPrompt: false }); + await telemetryService.trackAiSetupNudge({ skipPrompt: false }); expect(telemetry).not.toHaveBeenCalled(); }); diff --git a/code/lib/create-storybook/src/services/TelemetryService.ts b/code/lib/create-storybook/src/services/TelemetryService.ts index 56bc1ba6e9de..00f570f33857 100644 --- a/code/lib/create-storybook/src/services/TelemetryService.ts +++ b/code/lib/create-storybook/src/services/TelemetryService.ts @@ -33,9 +33,9 @@ export class TelemetryService { } /** Track when a user accepts the AI setup nudge prompt */ - async trackAiPromptNudge(context: { skipPrompt: boolean }): Promise { + async trackAiSetupNudge(context: { skipPrompt: boolean }): Promise { await this.runTelemetryIfEnabled('ai-prompt-nudge', { - id: 'prepare', + id: 'setup', origin: 'init', context, });