From bdb361bd43876e78975b3494e28676e3b6dd7a7f Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Thu, 3 Sep 2026 12:50:58 +0200 Subject: [PATCH 1/5] fix: ensure configured IDEs render first --- .../install-plugins/InstallPluginsScreen.tsx | 1 + .../install-plugins/components/BottomPrompt.tsx | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx index 79f4ef0..c451bd3 100644 --- a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx +++ b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx @@ -145,6 +145,7 @@ export function InstallPluginsScreen() { phase={phase} preferredLabel={preferredLabel} otherIntegrations={otherIntegrations} + detected={detected} onIdeSelect={handleIdeSelect} onDetectedSelect={handleDetectedSelect} onError={handleError} diff --git a/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx b/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx index 9cad2b2..2b0c437 100644 --- a/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx +++ b/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx @@ -1,3 +1,4 @@ +import type { IdeId } from '@shared-kernel/types.js'; import type { IdeIntegration } from '@integrations/index.js'; import { PromptPanel } from '../../../components/PromptPanel.js'; import type { PluginPhase } from '../usePluginInstall.js'; @@ -13,6 +14,7 @@ type BottomPromptProps = { phase: PluginPhase; preferredLabel: string | null; otherIntegrations: IdeIntegration[]; + detected: IdeId[]; onIdeSelect: (value: IdeSelectValue) => void; onDetectedSelect: (value: DetectedSelectValue) => void; onError: (value: ErrorAction) => void; @@ -22,6 +24,7 @@ export function BottomPrompt({ phase, preferredLabel, otherIntegrations, + detected, onIdeSelect, onDetectedSelect, onError, @@ -46,10 +49,13 @@ export function BottomPrompt({ /> ); case 'already-installed': { - const otherOptions = otherIntegrations.map((i) => ({ - label: i.name, - value: i.id, - })); + const detectedSet = new Set(detected); + const otherOptions = otherIntegrations + .toSorted((a, b) => Number(detectedSet.has(b.id)) - Number(detectedSet.has(a.id))) + .map((i) => ({ + label: i.name, + value: i.id, + })); return ( Date: Thu, 3 Sep 2026 12:57:04 +0200 Subject: [PATCH 2/5] refactor: reduce number of props --- .../install-plugins/InstallPluginsScreen.tsx | 18 ++++++------------ .../components/BottomPrompt.tsx | 15 +++++++-------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx index c451bd3..756f804 100644 --- a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx +++ b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx @@ -23,12 +23,10 @@ import * as te from './telemetry-events.js'; import { type IdeSelectValue, type DetectedSelectValue, type ErrorAction } from './actions.js'; import { BottomPrompt } from './components/index.js'; -const ALL_INTEGRATIONS = getIntegrations(); +type IdeLabels = Record; -const IDE_LABELS = Object.fromEntries(ALL_INTEGRATIONS.map((i) => [i.id, i.name])) as Record< - IdeId, - string ->; +const ALL_INTEGRATIONS = getIntegrations(); +const IDE_LABELS = Object.fromEntries(ALL_INTEGRATIONS.map((i) => [i.id, i.name])) as IdeLabels; export function InstallPluginsScreen() { const navigate = useNavigation(ScreenId.InstallPlugins); @@ -55,6 +53,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 +67,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); } @@ -133,9 +132,6 @@ export function InstallPluginsScreen() { ); - const preferredLabel = preferredIndex ? IDE_LABELS[preferredIndex] : null; - const otherIntegrations = ALL_INTEGRATIONS.filter((i) => i.id !== preferredIndex); - return ( void; onDetectedSelect: (value: DetectedSelectValue) => void; @@ -22,8 +22,6 @@ type BottomPromptProps = { export function BottomPrompt({ phase, - preferredLabel, - otherIntegrations, detected, onIdeSelect, onDetectedSelect, @@ -50,7 +48,8 @@ export function BottomPrompt({ ); case 'already-installed': { const detectedSet = new Set(detected); - const otherOptions = otherIntegrations + 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, @@ -60,9 +59,9 @@ export function BottomPrompt({ return ( Date: Thu, 3 Sep 2026 13:00:43 +0200 Subject: [PATCH 3/5] refactor: extract main content --- .../install-plugins/InstallPluginsScreen.tsx | 67 ++---------------- .../components/MainContent.tsx | 68 +++++++++++++++++++ .../install-plugins/components/index.ts | 1 + 3 files changed, 74 insertions(+), 62 deletions(-) create mode 100644 src/ui/tui/screens/install-plugins/components/MainContent.tsx diff --git a/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx b/src/ui/tui/screens/install-plugins/InstallPluginsScreen.tsx index 756f804..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,12 +16,9 @@ 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'; - -type IdeLabels = Record; +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 IdeLabels; export function InstallPluginsScreen() { const navigate = useNavigation(ScreenId.InstallPlugins); @@ -83,58 +75,9 @@ 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} - - - - )} - - ); - return ( } aside={} prompt={ ; + +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'; From 7e0884de9c5632e877f300d2496d8c8f36c86b4b Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Thu, 3 Sep 2026 13:07:32 +0200 Subject: [PATCH 4/5] chore: shorten phrasing --- src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx b/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx index 98ae355..65e4018 100644 --- a/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx +++ b/src/ui/tui/screens/install-plugins/components/BottomPrompt.tsx @@ -59,7 +59,7 @@ export function BottomPrompt({ return ( Date: Thu, 3 Sep 2026 13:09:52 +0200 Subject: [PATCH 5/5] test: cover option sorting with tests --- .../ui/screens/InstallPluginsScreen.test.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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'));