From 4441e8916c69833bdefda7f0c519ec535bcb1a3b Mon Sep 17 00:00:00 2001 From: "Miagkov, Oleg" <> Date: Thu, 7 May 2026 17:15:52 +0300 Subject: [PATCH 1/2] feat: add onboarding readiness summary Signed-off-by: Miagkov, Oleg <> --- .../onboarding/CompletionStep.test.tsx | 119 ++++++++++ .../components/onboarding/CompletionStep.tsx | 223 +++++++++++++++++- .../onboarding/OnboardingWizard.tsx | 1 + .../shared/i18n/locales/en/onboarding.json | 29 +++ .../shared/i18n/locales/fr/onboarding.json | 29 +++ 5 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx diff --git a/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx b/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx new file mode 100644 index 000000000..65a36e006 --- /dev/null +++ b/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx @@ -0,0 +1,119 @@ +/** + * @vitest-environment jsdom + */ +import { render, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { CompletionStep } from './CompletionStep'; + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + 'completion.title': "You're All Set!", + 'completion.subtitle': 'Auto-Coding is ready', + 'completion.setupComplete': 'Setup Complete', + 'completion.setupCompleteDescription': 'Your environment is configured.', + 'completion.whatsNext': "What's Next?", + 'completion.createTask.title': 'Create a Task', + 'completion.createTask.description': 'Start by creating your first task.', + 'completion.createTask.action': 'Open Task Creator', + 'completion.customizeSettings.title': 'Customize Settings', + 'completion.customizeSettings.description': 'Fine-tune your preferences.', + 'completion.customizeSettings.action': 'Open Settings', + 'completion.exploreDocs.title': 'Explore Documentation', + 'completion.exploreDocs.description': 'Learn more about advanced features.', + 'completion.finish': 'Finish & Start Building', + 'completion.rerunHint': 'You can re-run this wizard from Settings', + 'completion.readiness.title': 'Environment readiness', + 'completion.readiness.checking': 'Checking readiness...', + 'completion.readiness.codexAuth.ready': 'Codex account connected', + 'completion.readiness.codexAuth.issue': 'Connect a Codex account', + 'completion.readiness.codexCli.ready': 'Codex CLI ready', + 'completion.readiness.codexCli.issue': 'Install Codex CLI', + 'completion.readiness.memory.ready': 'Memory database ready', + 'completion.readiness.memory.issue': 'Memory needs attention', + 'completion.readiness.memory.skipped': 'Memory disabled' + }; + return translations[key] || key; + } + }) +})); + +vi.mock('../../stores/settings-store', () => ({ + useSettingsStore: () => ({ + settings: { memoryEnabled: true }, + profiles: [] + }) +})); + +vi.mock('../../stores/claude-profile-store', () => ({ + useClaudeProfileStore: () => ({ + profiles: [] + }) +})); + +const mockElectronAPI = { + checkCodexCodeVersion: vi.fn(), + getCodexProfiles: vi.fn(), + getMemoryInfrastructureStatus: vi.fn() +}; + +Object.defineProperty(window, 'electronAPI', { + value: mockElectronAPI, + writable: true +}); + +describe('CompletionStep readiness checks', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockElectronAPI.checkCodexCodeVersion.mockResolvedValue({ + success: true, + data: { + installed: '0.128.0', + latest: '0.128.0', + isOutdated: false, + path: '/usr/local/bin/codex' + } + }); + mockElectronAPI.getCodexProfiles.mockResolvedValue({ + success: true, + data: { + activeProfileId: 'codex-work', + profiles: [{ id: 'codex-work', name: 'Work', authenticated: true }] + } + }); + mockElectronAPI.getMemoryInfrastructureStatus.mockResolvedValue({ + success: true, + data: { + ready: true, + memory: { + kuzuInstalled: true, + databasePath: '/tmp/memory', + databaseExists: true, + databases: [] + } + } + }); + }); + + it('checks the selected Codex runtime and memory readiness before finish', async () => { + render( + + ); + + await waitFor(() => { + expect(mockElectronAPI.getCodexProfiles).toHaveBeenCalled(); + expect(mockElectronAPI.checkCodexCodeVersion).toHaveBeenCalled(); + expect(mockElectronAPI.getMemoryInfrastructureStatus).toHaveBeenCalled(); + }); + + expect(screen.getByText('Environment readiness')).toBeInTheDocument(); + expect(screen.getByText('Codex account connected')).toBeInTheDocument(); + expect(screen.getByText('Codex CLI ready')).toBeInTheDocument(); + expect(screen.getByText('Memory database ready')).toBeInTheDocument(); + }); +}); diff --git a/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx b/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx index bcbefb079..23831545e 100644 --- a/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx +++ b/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx @@ -4,13 +4,19 @@ import { FileText, Settings, BookOpen, - ArrowRight + ArrowRight, + AlertTriangle, + Loader2 } from 'lucide-react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '../ui/button'; import { Card, CardContent } from '../ui/card'; +import { useSettingsStore } from '../../stores/settings-store'; +import { useClaudeProfileStore } from '../../stores/claude-profile-store'; interface CompletionStepProps { + authRuntime?: 'anthropic' | 'codex'; onFinish: () => void; onOpenTaskCreator?: () => void; onOpenSettings?: () => void; @@ -53,12 +59,225 @@ function NextStepCard({ icon, title, description, action, actionLabel }: NextSte ); } +type ReadinessState = 'checking' | 'ready' | 'warning' | 'skipped'; + +interface ReadinessItem { + id: string; + state: ReadinessState; + label: string; +} + +function ReadinessRow({ item }: { item: ReadinessItem }) { + const icon = + item.state === 'checking' ? ( + + ) : item.state === 'ready' || item.state === 'skipped' ? ( + + ) : ( + + ); + + return ( +
+ {icon} + {item.label} +
+ ); +} + +function isCodexProfileAuthenticated(profile: unknown): boolean { + if (!profile || typeof profile !== 'object') return false; + const candidate = profile as { + authenticated?: boolean; + isAuthenticated?: boolean; + configDir?: string; + }; + return Boolean(candidate.authenticated || candidate.isAuthenticated || candidate.configDir); +} + +function CompletionReadiness({ authRuntime }: { authRuntime: 'anthropic' | 'codex' }) { + const { t } = useTranslation('onboarding'); + const { settings, profiles: apiProfiles } = useSettingsStore(); + const { profiles: claudeProfiles } = useClaudeProfileStore(); + const [items, setItems] = useState([ + { + id: 'auth', + state: 'checking', + label: t( + authRuntime === 'codex' + ? 'completion.readiness.codexAuth.checking' + : 'completion.readiness.claudeAuth.checking' + ) + }, + { + id: 'cli', + state: 'checking', + label: t( + authRuntime === 'codex' + ? 'completion.readiness.codexCli.checking' + : 'completion.readiness.claudeCli.checking' + ) + }, + { + id: 'memory', + state: settings.memoryEnabled === false ? 'skipped' : 'checking', + label: t( + settings.memoryEnabled === false + ? 'completion.readiness.memory.skipped' + : 'completion.readiness.memory.checking' + ) + } + ]); + + useEffect(() => { + let cancelled = false; + + const runChecks = async () => { + const nextItems: ReadinessItem[] = []; + + if (authRuntime === 'codex') { + try { + const result = await window.electronAPI.getCodexProfiles(); + const profiles = result.success && result.data ? result.data.profiles : []; + const hasCodexAuth = profiles.some(isCodexProfileAuthenticated); + nextItems.push({ + id: 'auth', + state: hasCodexAuth ? 'ready' : 'warning', + label: t( + hasCodexAuth + ? 'completion.readiness.codexAuth.ready' + : 'completion.readiness.codexAuth.issue' + ) + }); + } catch { + nextItems.push({ + id: 'auth', + state: 'warning', + label: t('completion.readiness.codexAuth.issue') + }); + } + + try { + const result = await window.electronAPI.checkCodexCodeVersion(); + const installed = Boolean(result.success && result.data?.installed); + nextItems.push({ + id: 'cli', + state: installed ? 'ready' : 'warning', + label: t( + installed + ? 'completion.readiness.codexCli.ready' + : 'completion.readiness.codexCli.issue' + ) + }); + } catch { + nextItems.push({ + id: 'cli', + state: 'warning', + label: t('completion.readiness.codexCli.issue') + }); + } + } else { + const hasApiProfile = apiProfiles.length > 0; + const hasClaudeProfile = claudeProfiles.some((profile) => + Boolean(profile.oauthToken || profile.configDir) + ); + const hasClaudeAuth = hasApiProfile || hasClaudeProfile; + + nextItems.push({ + id: 'auth', + state: hasClaudeAuth ? 'ready' : 'warning', + label: t( + hasClaudeAuth + ? 'completion.readiness.claudeAuth.ready' + : 'completion.readiness.claudeAuth.issue' + ) + }); + + try { + const result = await window.electronAPI.checkClaudeCodeVersion(); + const installed = Boolean(result.success && result.data?.installed); + nextItems.push({ + id: 'cli', + state: installed ? 'ready' : 'warning', + label: t( + installed + ? 'completion.readiness.claudeCli.ready' + : 'completion.readiness.claudeCli.issue' + ) + }); + } catch { + nextItems.push({ + id: 'cli', + state: 'warning', + label: t('completion.readiness.claudeCli.issue') + }); + } + } + + if (settings.memoryEnabled === false) { + nextItems.push({ + id: 'memory', + state: 'skipped', + label: t('completion.readiness.memory.skipped') + }); + } else { + try { + const result = await window.electronAPI.getMemoryInfrastructureStatus(); + const ready = Boolean(result.success && result.data?.ready); + nextItems.push({ + id: 'memory', + state: ready ? 'ready' : 'warning', + label: t( + ready + ? 'completion.readiness.memory.ready' + : 'completion.readiness.memory.issue' + ) + }); + } catch { + nextItems.push({ + id: 'memory', + state: 'warning', + label: t('completion.readiness.memory.issue') + }); + } + } + + if (!cancelled) { + setItems(nextItems); + } + }; + + runChecks(); + + return () => { + cancelled = true; + }; + }, [apiProfiles, authRuntime, claudeProfiles, settings.memoryEnabled, t]); + + return ( + + +
+ + {t('completion.readiness.title')} +
+
+ {items.map((item) => ( + + ))} +
+
+
+ ); +} + /** * Completion step component for the onboarding wizard. * Displays a success message with suggestions for next steps * and a prominent "Finish" button to complete the wizard. */ export function CompletionStep({ + authRuntime = 'anthropic', onFinish, onOpenTaskCreator, onOpenSettings @@ -127,6 +346,8 @@ export function CompletionStep({ + + {/* Next Steps Section */}
diff --git a/apps/frontend/src/renderer/components/onboarding/OnboardingWizard.tsx b/apps/frontend/src/renderer/components/onboarding/OnboardingWizard.tsx index 9b84b2c8d..456a2efeb 100644 --- a/apps/frontend/src/renderer/components/onboarding/OnboardingWizard.tsx +++ b/apps/frontend/src/renderer/components/onboarding/OnboardingWizard.tsx @@ -239,6 +239,7 @@ export function OnboardingWizard({ case 'completion': return ( Date: Thu, 7 May 2026 17:53:22 +0300 Subject: [PATCH 2/2] fix: address onboarding Sonar findings Signed-off-by: Miagkov, Oleg <> --- .../onboarding/CompletionStep.test.tsx | 26 +- .../components/onboarding/CompletionStep.tsx | 335 ++++++++++-------- .../shared/i18n/locales/en/onboarding.json | 35 +- .../shared/i18n/locales/fr/onboarding.json | 35 +- 4 files changed, 221 insertions(+), 210 deletions(-) diff --git a/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx b/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx index 65a36e006..4197f7e06 100644 --- a/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx +++ b/apps/frontend/src/renderer/components/onboarding/CompletionStep.test.tsx @@ -8,7 +8,7 @@ import { CompletionStep } from './CompletionStep'; vi.mock('react-i18next', () => ({ useTranslation: () => ({ - t: (key: string) => { + t: (key: string, options?: Record) => { const translations: Record = { 'completion.title': "You're All Set!", 'completion.subtitle': 'Auto-Coding is ready', @@ -26,16 +26,16 @@ vi.mock('react-i18next', () => ({ 'completion.finish': 'Finish & Start Building', 'completion.rerunHint': 'You can re-run this wizard from Settings', 'completion.readiness.title': 'Environment readiness', - 'completion.readiness.checking': 'Checking readiness...', - 'completion.readiness.codexAuth.ready': 'Codex account connected', - 'completion.readiness.codexAuth.issue': 'Connect a Codex account', - 'completion.readiness.codexCli.ready': 'Codex CLI ready', - 'completion.readiness.codexCli.issue': 'Install Codex CLI', - 'completion.readiness.memory.ready': 'Memory database ready', - 'completion.readiness.memory.issue': 'Memory needs attention', - 'completion.readiness.memory.skipped': 'Memory disabled' + 'completion.readiness.states.checking': 'Checking {{item}}...', + 'completion.readiness.states.ready': '{{item}} ready', + 'completion.readiness.states.warning': '{{item}} needs attention', + 'completion.readiness.states.skipped': '{{item}} disabled', + 'completion.readiness.items.codexAuth': 'Codex account', + 'completion.readiness.items.codexCli': 'Codex CLI', + 'completion.readiness.items.memory': 'Memory database' }; - return translations[key] || key; + const template = translations[key] || key; + return template.replace('{{item}}', options?.item ?? ''); } }) })); @@ -59,7 +59,7 @@ const mockElectronAPI = { getMemoryInfrastructureStatus: vi.fn() }; -Object.defineProperty(window, 'electronAPI', { +Object.defineProperty(globalThis, 'electronAPI', { value: mockElectronAPI, writable: true }); @@ -89,7 +89,7 @@ describe('CompletionStep readiness checks', () => { ready: true, memory: { kuzuInstalled: true, - databasePath: '/tmp/memory', + databasePath: '/Users/test/.auto-coding/memory', databaseExists: true, databases: [] } @@ -112,7 +112,7 @@ describe('CompletionStep readiness checks', () => { }); expect(screen.getByText('Environment readiness')).toBeInTheDocument(); - expect(screen.getByText('Codex account connected')).toBeInTheDocument(); + expect(screen.getByText('Codex account ready')).toBeInTheDocument(); expect(screen.getByText('Codex CLI ready')).toBeInTheDocument(); expect(screen.getByText('Memory database ready')).toBeInTheDocument(); }); diff --git a/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx b/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx index 23831545e..1efe6aa2f 100644 --- a/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx +++ b/apps/frontend/src/renderer/components/onboarding/CompletionStep.tsx @@ -16,18 +16,18 @@ import { useSettingsStore } from '../../stores/settings-store'; import { useClaudeProfileStore } from '../../stores/claude-profile-store'; interface CompletionStepProps { - authRuntime?: 'anthropic' | 'codex'; - onFinish: () => void; - onOpenTaskCreator?: () => void; - onOpenSettings?: () => void; + readonly authRuntime?: 'anthropic' | 'codex'; + readonly onFinish: () => void; + readonly onOpenTaskCreator?: () => void; + readonly onOpenSettings?: () => void; } interface NextStepCardProps { - icon: React.ReactNode; - title: string; - description: string; - action?: () => void; - actionLabel?: string; + readonly icon: React.ReactNode; + readonly title: string; + readonly description: string; + readonly action?: () => void; + readonly actionLabel?: string; } function NextStepCard({ icon, title, description, action, actionLabel }: NextStepCardProps) { @@ -60,6 +60,8 @@ function NextStepCard({ icon, title, description, action, actionLabel }: NextSte } type ReadinessState = 'checking' | 'ready' | 'warning' | 'skipped'; +type ReadinessSubject = 'claudeAuth' | 'claudeCli' | 'codexAuth' | 'codexCli' | 'memory'; +type Translate = (key: string, options?: Record) => string; interface ReadinessItem { id: string; @@ -67,15 +69,52 @@ interface ReadinessItem { label: string; } -function ReadinessRow({ item }: { item: ReadinessItem }) { - const icon = - item.state === 'checking' ? ( - - ) : item.state === 'ready' || item.state === 'skipped' ? ( - - ) : ( - - ); +interface RuntimeContext { + authRuntime: 'anthropic' | 'codex'; + apiProfiles: unknown[]; + claudeProfiles: Array<{ oauthToken?: string; configDir?: string }>; + memoryEnabled: boolean; + t: Translate; +} + +function getElectronAPI(): Window['electronAPI'] { + return (globalThis as typeof globalThis & { electronAPI: Window['electronAPI'] }).electronAPI; +} + +function translateReadiness(t: Translate, subject: ReadinessSubject, state: ReadinessState): string { + const item = t(`completion.readiness.items.${subject}`); + return t(`completion.readiness.states.${state}`, { item }); +} + +function buildItem( + id: string, + subject: ReadinessSubject, + ready: boolean, + t: Translate +): ReadinessItem { + const state: ReadinessState = ready ? 'ready' : 'warning'; + + return { + id, + state, + label: translateReadiness(t, subject, state) + }; +} + +function getReadinessIcon(state: ReadinessState) { + if (state === 'checking') { + return ; + } + + if (state === 'ready' || state === 'skipped') { + return ; + } + + return ; +} + +function ReadinessRow({ item }: Readonly<{ item: ReadinessItem }>) { + const icon = getReadinessIcon(item.state); return (
@@ -95,153 +134,151 @@ function isCodexProfileAuthenticated(profile: unknown): boolean { return Boolean(candidate.authenticated || candidate.isAuthenticated || candidate.configDir); } -function CompletionReadiness({ authRuntime }: { authRuntime: 'anthropic' | 'codex' }) { - const { t } = useTranslation('onboarding'); - const { settings, profiles: apiProfiles } = useSettingsStore(); - const { profiles: claudeProfiles } = useClaudeProfileStore(); - const [items, setItems] = useState([ +function createInitialReadinessItems( + authRuntime: 'anthropic' | 'codex', + memoryEnabled: boolean, + t: Translate +): ReadinessItem[] { + const authSubject = authRuntime === 'codex' ? 'codexAuth' : 'claudeAuth'; + const cliSubject = authRuntime === 'codex' ? 'codexCli' : 'claudeCli'; + + return [ { id: 'auth', state: 'checking', - label: t( - authRuntime === 'codex' - ? 'completion.readiness.codexAuth.checking' - : 'completion.readiness.claudeAuth.checking' - ) + label: translateReadiness(t, authSubject, 'checking') }, { id: 'cli', state: 'checking', - label: t( - authRuntime === 'codex' - ? 'completion.readiness.codexCli.checking' - : 'completion.readiness.claudeCli.checking' - ) + label: translateReadiness(t, cliSubject, 'checking') }, { id: 'memory', - state: settings.memoryEnabled === false ? 'skipped' : 'checking', - label: t( - settings.memoryEnabled === false - ? 'completion.readiness.memory.skipped' - : 'completion.readiness.memory.checking' - ) + state: memoryEnabled ? 'checking' : 'skipped', + label: translateReadiness(t, 'memory', memoryEnabled ? 'checking' : 'skipped') } + ]; +} + +async function getCodexAuthItem(t: Translate): Promise { + try { + const result = await getElectronAPI().getCodexProfiles(); + const profiles = result.success && result.data ? result.data.profiles : []; + return buildItem( + 'auth', + 'codexAuth', + profiles.some(isCodexProfileAuthenticated), + t + ); + } catch { + return buildItem('auth', 'codexAuth', false, t); + } +} + +function getClaudeAuthItem(context: RuntimeContext): ReadinessItem { + const hasApiProfile = context.apiProfiles.length > 0; + const hasClaudeProfile = context.claudeProfiles.some((profile) => + Boolean(profile.oauthToken || profile.configDir) + ); + + return buildItem( + 'auth', + 'claudeAuth', + hasApiProfile || hasClaudeProfile, + context.t + ); +} + +async function getCliItem( + id: string, + subject: ReadinessSubject, + checkVersion: () => Promise<{ success: boolean; data?: { installed?: string | null } }>, + t: Translate +): Promise { + try { + const result = await checkVersion(); + return buildItem(id, subject, Boolean(result.success && result.data?.installed), t); + } catch { + return buildItem(id, subject, false, t); + } +} + +async function getRuntimeItems(context: RuntimeContext): Promise { + if (context.authRuntime === 'codex') { + const [authItem, cliItem] = await Promise.all([ + getCodexAuthItem(context.t), + getCliItem( + 'cli', + 'codexCli', + () => getElectronAPI().checkCodexCodeVersion(), + context.t + ) + ]); + + return [authItem, cliItem]; + } + + const cliItem = await getCliItem( + 'cli', + 'claudeCli', + () => getElectronAPI().checkClaudeCodeVersion(), + context.t + ); + + return [getClaudeAuthItem(context), cliItem]; +} + +async function getMemoryItem( + memoryEnabled: boolean, + t: Translate +): Promise { + if (!memoryEnabled) { + return { + id: 'memory', + state: 'skipped', + label: translateReadiness(t, 'memory', 'skipped') + }; + } + + try { + const result = await getElectronAPI().getMemoryInfrastructureStatus(); + return buildItem('memory', 'memory', Boolean(result.success && result.data?.ready), t); + } catch { + return buildItem('memory', 'memory', false, t); + } +} + +async function collectReadinessItems(context: RuntimeContext): Promise { + const [runtimeItems, memoryItem] = await Promise.all([ + getRuntimeItems(context), + getMemoryItem(context.memoryEnabled, context.t) ]); + return [...runtimeItems, memoryItem]; +} + +function CompletionReadiness({ authRuntime }: Readonly<{ authRuntime: 'anthropic' | 'codex' }>) { + const { t } = useTranslation('onboarding'); + const { settings, profiles: apiProfiles } = useSettingsStore(); + const { profiles: claudeProfiles } = useClaudeProfileStore(); + const memoryEnabled = settings.memoryEnabled !== false; + const [items, setItems] = useState( + createInitialReadinessItems(authRuntime, memoryEnabled, t) + ); + useEffect(() => { let cancelled = false; + const context: RuntimeContext = { + authRuntime, + apiProfiles, + claudeProfiles, + memoryEnabled, + t + }; const runChecks = async () => { - const nextItems: ReadinessItem[] = []; - - if (authRuntime === 'codex') { - try { - const result = await window.electronAPI.getCodexProfiles(); - const profiles = result.success && result.data ? result.data.profiles : []; - const hasCodexAuth = profiles.some(isCodexProfileAuthenticated); - nextItems.push({ - id: 'auth', - state: hasCodexAuth ? 'ready' : 'warning', - label: t( - hasCodexAuth - ? 'completion.readiness.codexAuth.ready' - : 'completion.readiness.codexAuth.issue' - ) - }); - } catch { - nextItems.push({ - id: 'auth', - state: 'warning', - label: t('completion.readiness.codexAuth.issue') - }); - } - - try { - const result = await window.electronAPI.checkCodexCodeVersion(); - const installed = Boolean(result.success && result.data?.installed); - nextItems.push({ - id: 'cli', - state: installed ? 'ready' : 'warning', - label: t( - installed - ? 'completion.readiness.codexCli.ready' - : 'completion.readiness.codexCli.issue' - ) - }); - } catch { - nextItems.push({ - id: 'cli', - state: 'warning', - label: t('completion.readiness.codexCli.issue') - }); - } - } else { - const hasApiProfile = apiProfiles.length > 0; - const hasClaudeProfile = claudeProfiles.some((profile) => - Boolean(profile.oauthToken || profile.configDir) - ); - const hasClaudeAuth = hasApiProfile || hasClaudeProfile; - - nextItems.push({ - id: 'auth', - state: hasClaudeAuth ? 'ready' : 'warning', - label: t( - hasClaudeAuth - ? 'completion.readiness.claudeAuth.ready' - : 'completion.readiness.claudeAuth.issue' - ) - }); - - try { - const result = await window.electronAPI.checkClaudeCodeVersion(); - const installed = Boolean(result.success && result.data?.installed); - nextItems.push({ - id: 'cli', - state: installed ? 'ready' : 'warning', - label: t( - installed - ? 'completion.readiness.claudeCli.ready' - : 'completion.readiness.claudeCli.issue' - ) - }); - } catch { - nextItems.push({ - id: 'cli', - state: 'warning', - label: t('completion.readiness.claudeCli.issue') - }); - } - } - - if (settings.memoryEnabled === false) { - nextItems.push({ - id: 'memory', - state: 'skipped', - label: t('completion.readiness.memory.skipped') - }); - } else { - try { - const result = await window.electronAPI.getMemoryInfrastructureStatus(); - const ready = Boolean(result.success && result.data?.ready); - nextItems.push({ - id: 'memory', - state: ready ? 'ready' : 'warning', - label: t( - ready - ? 'completion.readiness.memory.ready' - : 'completion.readiness.memory.issue' - ) - }); - } catch { - nextItems.push({ - id: 'memory', - state: 'warning', - label: t('completion.readiness.memory.issue') - }); - } - } - + const nextItems = await collectReadinessItems(context); if (!cancelled) { setItems(nextItems); } @@ -252,7 +289,7 @@ function CompletionReadiness({ authRuntime }: { authRuntime: 'anthropic' | 'code return () => { cancelled = true; }; - }, [apiProfiles, authRuntime, claudeProfiles, settings.memoryEnabled, t]); + }, [apiProfiles, authRuntime, claudeProfiles, memoryEnabled, t]); return ( diff --git a/apps/frontend/src/shared/i18n/locales/en/onboarding.json b/apps/frontend/src/shared/i18n/locales/en/onboarding.json index 071a48bb8..ee8a4bf3e 100644 --- a/apps/frontend/src/shared/i18n/locales/en/onboarding.json +++ b/apps/frontend/src/shared/i18n/locales/en/onboarding.json @@ -202,31 +202,18 @@ }, "readiness": { "title": "Environment readiness", - "claudeAuth": { - "checking": "Checking Claude authentication...", - "ready": "Claude/API authentication configured", - "issue": "Configure Claude OAuth or an API profile" + "states": { + "checking": "Checking {{item}}...", + "ready": "{{item}} ready", + "warning": "{{item}} needs attention", + "skipped": "{{item}} disabled" }, - "claudeCli": { - "checking": "Checking Claude Code CLI...", - "ready": "Claude Code CLI ready", - "issue": "Install or update Claude Code CLI" - }, - "codexAuth": { - "checking": "Checking Codex account...", - "ready": "Codex account connected", - "issue": "Connect a Codex account" - }, - "codexCli": { - "checking": "Checking Codex CLI...", - "ready": "Codex CLI ready", - "issue": "Install or update Codex CLI" - }, - "memory": { - "checking": "Checking memory database...", - "ready": "Memory database ready", - "issue": "Memory needs attention", - "skipped": "Memory disabled" + "items": { + "claudeAuth": "Claude/API authentication", + "claudeCli": "Claude Code CLI", + "codexAuth": "Codex account", + "codexCli": "Codex CLI", + "memory": "Memory database" } }, "finish": "Finish & Start Building", diff --git a/apps/frontend/src/shared/i18n/locales/fr/onboarding.json b/apps/frontend/src/shared/i18n/locales/fr/onboarding.json index 3fc12fcf7..aaa5f1cb3 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/onboarding.json +++ b/apps/frontend/src/shared/i18n/locales/fr/onboarding.json @@ -192,31 +192,18 @@ }, "readiness": { "title": "Préparation de l'environnement", - "claudeAuth": { - "checking": "Vérification de l'authentification Claude...", - "ready": "Authentification Claude/API configurée", - "issue": "Configurez OAuth Claude ou un profil API" + "states": { + "checking": "Vérification de {{item}}...", + "ready": "{{item}} prêt", + "warning": "{{item}} nécessite une attention", + "skipped": "{{item}} désactivé" }, - "claudeCli": { - "checking": "Vérification du CLI Claude Code...", - "ready": "CLI Claude Code prêt", - "issue": "Installez ou mettez à jour le CLI Claude Code" - }, - "codexAuth": { - "checking": "Vérification du compte Codex...", - "ready": "Compte Codex connecté", - "issue": "Connectez un compte Codex" - }, - "codexCli": { - "checking": "Vérification du CLI Codex...", - "ready": "CLI Codex prêt", - "issue": "Installez ou mettez à jour le CLI Codex" - }, - "memory": { - "checking": "Vérification de la base mémoire...", - "ready": "Base mémoire prête", - "issue": "La mémoire nécessite une attention", - "skipped": "Mémoire désactivée" + "items": { + "claudeAuth": "l'authentification Claude/API", + "claudeCli": "le CLI Claude Code", + "codexAuth": "le compte Codex", + "codexCli": "le CLI Codex", + "memory": "la base mémoire" } }, "finish": "Terminer et commencer à construire",