From 5bfffcd4457bf0cfb9cca84a3fc9eae5c78089ef Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 20 Jul 2026 18:35:56 -0500 Subject: [PATCH] =?UTF-8?q?refactor(ui-tui):=20every=20selectable=20list?= =?UTF-8?q?=20rides=20the=20shared=20selection=20chip=20=E2=80=94=20zero?= =?UTF-8?q?=20inverse=20left?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /agents, /journey, model picker, skills hub, plugins hub, pet picker, and the approval/confirm prompts all used SGR inverse for the active row — terminal-interpreted against unknowable defaults (black slab on transparent profiles) and visually divergent from completions/session-switcher. New spreadable chipRowProps(t, active) in overlayPrimitives (chip bg + lifted ink + bold; spread after `color` so chip ink wins) converts each site to the one selection treatment. rg confirms zero inverse={} remaining in components/. --- ui-tui/src/components/agentsOverlay.tsx | 10 +++++++--- ui-tui/src/components/journey.tsx | 9 ++++++--- ui-tui/src/components/modelPicker.tsx | 11 +++++------ ui-tui/src/components/overlayPrimitives.tsx | 10 ++++++++++ ui-tui/src/components/petPicker.tsx | 3 ++- ui-tui/src/components/pluginsHub.tsx | 6 +++--- ui-tui/src/components/prompts.tsx | 5 +++-- ui-tui/src/components/skillsHub.tsx | 17 +++-------------- 8 files changed, 39 insertions(+), 32 deletions(-) diff --git a/ui-tui/src/components/agentsOverlay.tsx b/ui-tui/src/components/agentsOverlay.tsx index ba9d4b74bb7c..cecfa0ef6ba8 100644 --- a/ui-tui/src/components/agentsOverlay.tsx +++ b/ui-tui/src/components/agentsOverlay.tsx @@ -32,6 +32,7 @@ import { compactPreview } from '../lib/text.js' import type { Theme } from '../theme.js' import type { SubagentNode, SubagentProgress } from '../types.js' +import { listRowStyle } from './overlayPrimitives.js' import { OverlayScrollbar } from './overlayScrollbar.js' // ── Types + lookup tables ──────────────────────────────────────────── @@ -464,14 +465,17 @@ function ListRow({ const paren = line ? line.indexOf('(') : -1 const toolShort = line ? (paren > 0 ? line.slice(0, paren) : line).trim() : '' const trailing = toolShort ? ` · ${compactPreview(toolShort, 14)}` : '' - const fg = active ? t.color.accent : t.color.text + // Selection chip, not `inverse` — inverse swaps against the terminal's + // unknowable defaults (black slab on transparent profiles). + const row = listRowStyle(t, active) + const fg = active ? (row.color ?? t.color.accent) : t.color.text return ( - + {' '} {formatRowId(index)} {indentFor(node.item.depth)} - {heatMarker ? : null} + {heatMarker ? : null} {glyph} {goal} {toolsCount} diff --git a/ui-tui/src/components/journey.tsx b/ui-tui/src/components/journey.tsx index d1aa2c7daaa3..f6e1b0790206 100644 --- a/ui-tui/src/components/journey.tsx +++ b/ui-tui/src/components/journey.tsx @@ -7,6 +7,7 @@ import { rpcErrorMessage } from '../lib/rpc.js' import { deriveStarmapPalette, fadeHex, fadeInk, type StarmapPalette } from '../lib/starmapPalette.js' import type { Theme } from '../theme.js' +import { listRowStyle } from './overlayPrimitives.js' import { OverlayScrollbar } from './overlayScrollbar.js' interface MutationResult { @@ -115,12 +116,14 @@ function ChartRow({ palette, row }: { palette: StarmapPalette; row: Run[] }) { } // Full-width selectable row, matching the /agents list treatment: the active -// row inverts and collapses every segment onto the accent foreground. +// row carries the shared selection chip (never `inverse`, which swaps against +// the terminal's unknowable defaults) and collapses onto the chip ink. function ListRow({ active, cells, t }: { active: boolean; cells: Cell[]; t: Theme }) { - const fg = active ? t.color.accent : t.color.text + const row = listRowStyle(t, active) + const fg = active ? (row.color ?? t.color.accent) : t.color.text return ( - + {cells.map((c, i) => ( {c.text} diff --git a/ui-tui/src/components/modelPicker.tsx b/ui-tui/src/components/modelPicker.tsx index 9983743ac9b2..75bdbe7bd39c 100644 --- a/ui-tui/src/components/modelPicker.tsx +++ b/ui-tui/src/components/modelPicker.tsx @@ -10,6 +10,7 @@ import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js' import type { Theme } from '../theme.js' import { OverlayHint, useOverlayKeys, windowItems } from './overlayControls.js' +import { chipRowProps } from './overlayPrimitives.js' const VISIBLE = 12 const MIN_WIDTH = 40 @@ -596,9 +597,8 @@ export function ModelPicker({ return row ? ( @@ -669,9 +669,8 @@ export function ModelPicker({ return ( diff --git a/ui-tui/src/components/overlayPrimitives.tsx b/ui-tui/src/components/overlayPrimitives.tsx index 1884841d5d25..805ffdbe8652 100644 --- a/ui-tui/src/components/overlayPrimitives.tsx +++ b/ui-tui/src/components/overlayPrimitives.tsx @@ -86,6 +86,16 @@ export function listRowStyle(t: Theme, active: boolean): { backgroundColor?: str return { backgroundColor, color: liftForContrast(t.color.text, backgroundColor, 4.5) } } +/** Spreadable props for a selectable row: chip bg + ink + bold when active. + * Spread AFTER `color` so the chip ink wins on the active row. Replaces + * `inverse`, which swaps against the terminal's unknowable default colors + * (a black slab on transparent profiles). */ +export function chipRowProps(t: Theme, active: boolean): { backgroundColor?: string; bold: boolean; color?: string } { + const row = listRowStyle(t, active) + + return { backgroundColor: row.backgroundColor, bold: active, ...(row.color ? { color: row.color } : {}) } +} + /** A numbered menu row with the ▸ cursor (mirrors ClarifyPrompt). Active rows * carry the shared list-row selection chip — same treatment as completions * and the session switcher — instead of `inverse`, whose contrast depends on diff --git a/ui-tui/src/components/petPicker.tsx b/ui-tui/src/components/petPicker.tsx index f8ba4594362c..142139e37da4 100644 --- a/ui-tui/src/components/petPicker.tsx +++ b/ui-tui/src/components/petPicker.tsx @@ -6,6 +6,7 @@ import { rpcErrorMessage } from '../lib/rpc.js' import type { Theme } from '../theme.js' import { OverlayHint, windowItems } from './overlayControls.js' +import { chipRowProps } from './overlayPrimitives.js' const VISIBLE = 10 const MIN_WIDTH = 40 @@ -155,7 +156,7 @@ export function PetPicker({ gw, maxWidth, onClose, t }: PetPickerProps) { const tag = pet.installed ? '' : pet.curated ? ' · official' : '' return ( - + {at ? '▸ ' : ' '} {mark} {pet.displayName} diff --git a/ui-tui/src/components/pluginsHub.tsx b/ui-tui/src/components/pluginsHub.tsx index 3876c831299a..358878010535 100644 --- a/ui-tui/src/components/pluginsHub.tsx +++ b/ui-tui/src/components/pluginsHub.tsx @@ -6,6 +6,7 @@ import { rpcErrorMessage } from '../lib/rpc.js' import type { Theme } from '../theme.js' import { OverlayHint, useOverlayKeys, windowItems, windowOffset } from './overlayControls.js' +import { chipRowProps } from './overlayPrimitives.js' const VISIBLE = 12 const MIN_WIDTH = 44 @@ -209,9 +210,8 @@ export function PluginsHub({ gw, maxWidth, onClose, t }: PluginsHubProps) { return ( diff --git a/ui-tui/src/components/prompts.tsx b/ui-tui/src/components/prompts.tsx index bfd7bf62d278..614be012aaad 100644 --- a/ui-tui/src/components/prompts.tsx +++ b/ui-tui/src/components/prompts.tsx @@ -5,6 +5,7 @@ import { isMac } from '../lib/platform.js' import type { Theme } from '../theme.js' import type { ApprovalReq, ClarifyReq, ConfirmReq } from '../types.js' +import { chipRowProps } from './overlayPrimitives.js' import { TextInput } from './textInput.js' const APPROVAL_OPTS = ['once', 'session', 'always', 'deny'] as const @@ -129,7 +130,7 @@ export function ApprovalPrompt({ cols = 80, onChoice, req, t }: ApprovalPromptPr {opts.map((o, i) => ( - + {sel === i ? '▸ ' : ' '} {i + 1}. {LABELS[o]} @@ -208,7 +209,7 @@ export function ClarifyPrompt({ cols = 80, onAnswer, onCancel, req, t }: Clarify {[...choices, 'Other (type your answer)'].map((c, i) => ( - + {sel === i ? '▸ ' : ' '} {i + 1}. {c} diff --git a/ui-tui/src/components/skillsHub.tsx b/ui-tui/src/components/skillsHub.tsx index 31c8f355f007..dec053db27e4 100644 --- a/ui-tui/src/components/skillsHub.tsx +++ b/ui-tui/src/components/skillsHub.tsx @@ -6,6 +6,7 @@ import { rpcErrorMessage } from '../lib/rpc.js' import type { Theme } from '../theme.js' import { OverlayHint, useOverlayKeys, windowItems, windowOffset } from './overlayControls.js' +import { chipRowProps } from './overlayPrimitives.js' const VISIBLE = 12 const MIN_WIDTH = 40 @@ -220,13 +221,7 @@ export function SkillsHub({ gw, maxWidth, onClose, t }: SkillsHubProps) { const idx = offset + i return ( - + {catIdx === idx ? '▸ ' : ' '} {i + 1}. {row} @@ -256,13 +251,7 @@ export function SkillsHub({ gw, maxWidth, onClose, t }: SkillsHubProps) { const idx = offset + i return ( - + {skillIdx === idx ? '▸ ' : ' '} {i + 1}. {row}