diff --git a/__tests__/ui/screens/InstallPluginsScreen.test.tsx b/__tests__/ui/screens/InstallPluginsScreen.test.tsx index dc6328e..b7b91dd 100644 --- a/__tests__/ui/screens/InstallPluginsScreen.test.tsx +++ b/__tests__/ui/screens/InstallPluginsScreen.test.tsx @@ -85,6 +85,25 @@ describe('InstallPluginsScreen', () => { }); }); + it('sorts detected IDEs above non-detected ones', async () => { + const { detectInstalledPlugins } = await import('../../../src/integrations/skills/plugin.js'); + vi.mocked(detectInstalledPlugins).mockResolvedValueOnce([ + { ide: 'claude', via: 'cli' }, + { ide: 'codex', via: 'cli' }, + ]); + + using sut = renderScreen(, { screen: ScreenId.InstallPlugins }); + + await waitFor(() => { + const frame = sut.lastFrame()!; + const codexPos = frame.indexOf('Codex'); + const cursorPos = frame.indexOf('Cursor'); + expect(codexPos).toBeGreaterThan(-1); + expect(cursorPos).toBeGreaterThan(-1); + expect(codexPos).toBeLessThan(cursorPos); + }); + }); + it('shows error and retry option on install failure', async () => { const { installPlugin } = await import('../../../src/integrations/skills/plugin.js'); vi.mocked(installPlugin).mockRejectedValueOnce(new Error('Installation failed')); diff --git a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx index 79f4ef0..6356bdb 100644 --- a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx +++ b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx @@ -1,16 +1,11 @@ -import { Box, Text } from 'ink'; -import { Spinner } from '@inkjs/ui'; -import { Colors, Icons } from '../../styles.js'; -import { MainLayout } from '../../components/MainLayout.js'; -import { TaskList } from '../../components/TaskList.js'; -import { buildWizardTasks } from '../../lib/wizard-tasks.js'; -import type { IdeId } from '@shared-kernel/types.js'; import { getIntegrations } from '@integrations/index.js'; -import { PLUGIN_REPO_URL } from '@lib/constants.js'; import { ScreenId } from '@lib/session.js'; +import { MainLayout } from '../../components/MainLayout.js'; +import { TaskList } from '../../components/TaskList.js'; import { useAutoAdvance } from '../../hooks/useAutoAdvance.js'; import { useLogger } from '../../hooks/useLog.js'; import { useNavigation } from '../../hooks/useNavigation.js'; +import { buildWizardTasks } from '../../lib/wizard-tasks.js'; import { $session, store } from '../../store.js'; import { usePluginInstall } from './usePluginInstall.js'; import { track } from '@lib/telemetry.js'; @@ -21,15 +16,10 @@ import { } from './log-messages.js'; import * as te from './telemetry-events.js'; import { type IdeSelectValue, type DetectedSelectValue, type ErrorAction } from './actions.js'; -import { BottomPrompt } from './components/index.js'; +import { BottomPrompt, MainContent } from './components/index.js'; const ALL_INTEGRATIONS = getIntegrations(); -const IDE_LABELS = Object.fromEntries(ALL_INTEGRATIONS.map((i) => [i.id, i.name])) as Record< - IdeId, - string ->; - export function InstallPluginsScreen() { const navigate = useNavigation(ScreenId.InstallPlugins); const log = useLogger(ScreenId.InstallPlugins); @@ -55,6 +45,7 @@ export function InstallPluginsScreen() { function handleDetectedSelect(value: DetectedSelectValue) { if (value === 'continue') { if (!preferredIndex) return; + store.setIde(preferredIndex); log(pluginsAlreadyInstalled(detected)); track(te.pluginsAlreadyDetected()); @@ -68,9 +59,9 @@ export function InstallPluginsScreen() { if (value === 'exit') { log(pluginExitedAfterError(error)); track(te.pluginExitedAfterError()); - process.exit(1); - return; + return process.exit(1); } + const ide = $session.get().ide; if (ide) selectIde(ide); } @@ -84,67 +75,14 @@ export function InstallPluginsScreen() { : 'active', ); - const main = ( - - - - Select agent to set up - - - - - Your agent will get Confidence skills for flag management, warehouse setup, migrations, - and onboarding — so it can help without searching the docs. - - - - {phase === 'detecting' && } - - {phase === 'already-installed' && ( - <> - Detected Confidence plugins for: - {detected.map((d) => ( - - {Icons.check} - {IDE_LABELS[d] ?? d} - - ))} - - )} - - {phase === 'installing' && } - - {phase === 'installed' && ( - - Plugin installed successfully. Continuing... - - )} - - {phase === 'error' && ( - - Failed to install plugin: {error} - - - You can install manually from: {PLUGIN_REPO_URL} - - - - )} - - ); - - const preferredLabel = preferredIndex ? IDE_LABELS[preferredIndex] : null; - const otherIntegrations = ALL_INTEGRATIONS.filter((i) => i.id !== preferredIndex); - return ( } aside={} prompt={ void; onDetectedSelect: (value: DetectedSelectValue) => void; onError: (value: ErrorAction) => void; @@ -20,8 +22,7 @@ type BottomPromptProps = { export function BottomPrompt({ phase, - preferredLabel, - otherIntegrations, + detected, onIdeSelect, onDetectedSelect, onError, @@ -46,17 +47,21 @@ export function BottomPrompt({ /> ); case 'already-installed': { - const otherOptions = otherIntegrations.map((i) => ({ - label: i.name, - value: i.id, - })); + const detectedSet = new Set(detected); + const preferred = ALL_INTEGRATIONS.find((i) => detectedSet.has(i.id)); + const otherOptions = ALL_INTEGRATIONS.filter((i) => i.id !== preferred?.id) + .toSorted((a, b) => Number(detectedSet.has(b.id)) - Number(detectedSet.has(a.id))) + .map((i) => ({ + label: i.name, + value: i.id, + })); return ( ; + +const IDE_LABELS = Object.fromEntries(getIntegrations().map((i) => [i.id, i.name])) as IdeLabels; + +type MainContentProps = { + phase: PluginPhase; + detected: IdeId[]; + error: string | null; +}; + +export function MainContent({ phase, detected, error }: MainContentProps) { + return ( + + + + Select agent to set up + + + + + Your agent will get Confidence skills for flag management, warehouse setup, migrations, + and onboarding — so it can help without searching the docs. + + + + {phase === 'detecting' && } + + {phase === 'already-installed' && ( + <> + Detected Confidence plugins for: + {detected.map((d) => ( + + {Icons.check} + {IDE_LABELS[d] ?? d} + + ))} + + )} + + {phase === 'installing' && } + + {phase === 'installed' && ( + + Plugin installed successfully. Continuing... + + )} + + {phase === 'error' && ( + + Failed to install plugin: {error} + + + You can install manually from: {PLUGIN_REPO_URL} + + + + )} + + ); +} diff --git a/src/ui/tui/screens/install-plugins/components/index.ts b/src/ui/tui/screens/install-plugins/components/index.ts index c9db3ca..8b8b563 100644 --- a/src/ui/tui/screens/install-plugins/components/index.ts +++ b/src/ui/tui/screens/install-plugins/components/index.ts @@ -1 +1,2 @@ export { BottomPrompt } from './BottomPrompt.js'; +export { MainContent } from './MainContent.js';