diff --git a/packages/cli/src/ui/components/ColorsDisplay.test.tsx b/packages/cli/src/ui/components/ColorsDisplay.test.tsx index 2a470e3317..795982d261 100644 --- a/packages/cli/src/ui/components/ColorsDisplay.test.tsx +++ b/packages/cli/src/ui/components/ColorsDisplay.test.tsx @@ -25,6 +25,7 @@ describe('ColorsDisplay', () => { primary: '#000000', message: '#111111', input: '#222222', + selection: '#333333', diff: { added: '#003300', removed: '#330000', diff --git a/packages/cli/src/ui/components/ColorsDisplay.tsx b/packages/cli/src/ui/components/ColorsDisplay.tsx index ecc60cdd30..75f9d39c1a 100644 --- a/packages/cli/src/ui/components/ColorsDisplay.tsx +++ b/packages/cli/src/ui/components/ColorsDisplay.tsx @@ -32,6 +32,30 @@ type ColorRow = StandardColorRow | GradientColorRow | BackgroundColorRow; const VALUE_COLUMN_WIDTH = 10; +const COLOR_DESCRIPTIONS: Record = { + 'text.primary': 'Primary text color (uses terminal default if blank)', + 'text.secondary': 'Secondary/dimmed text color', + 'text.link': 'Hyperlink and highlighting color', + 'text.accent': 'Accent color for emphasis', + 'text.response': + 'Color for model response text (uses terminal default if blank)', + 'background.primary': 'Main terminal background color', + 'background.message': 'Subtle background for message blocks', + 'background.input': 'Background for the input prompt', + 'background.selection': 'Background highlight for selected/focused items', + 'background.diff.added': 'Background for added lines in diffs', + 'background.diff.removed': 'Background for removed lines in diffs', + 'border.default': 'Standard border color', + 'border.focused': 'Border color when an element is focused', + 'ui.comment': 'Color for code comments and metadata', + 'ui.symbol': 'Color for technical symbols and UI icons', + 'ui.dark': 'Deeply dimmed color for subtle UI elements', + 'ui.focus': 'Color for focused or selected UI elements (e.g. menu items)', + 'status.error': 'Color for error messages and critical status', + 'status.success': 'Color for success messages and positive status', + 'status.warning': 'Color for warnings and cautionary status', +}; + interface ColorsDisplayProps { activeTheme: Theme; } @@ -179,20 +203,28 @@ export const ColorsDisplay: React.FC = ({ function renderStandardRow({ name, value }: StandardColorRow) { const isHex = value.startsWith('#'); const displayColor = isHex ? value : theme.text.primary; + const description = COLOR_DESCRIPTIONS[name] || ''; return ( {value || '(blank)'} - - {name} + + + {name} + + + {description} + ); } function renderGradientRow({ name, value }: GradientColorRow) { + const description = COLOR_DESCRIPTIONS[name] || ''; + return ( @@ -202,16 +234,23 @@ function renderGradientRow({ name, value }: GradientColorRow) { ))} - - - {name} - + + + + {name} + + + + {description} + ); } function renderBackgroundRow({ name, value }: BackgroundColorRow) { + const description = COLOR_DESCRIPTIONS[name] || ''; + return ( - - {name} + + + {name} + + + {description} + ); diff --git a/packages/cli/src/ui/components/Header.test.tsx b/packages/cli/src/ui/components/Header.test.tsx index 759ce04bb9..b01ef6aeab 100644 --- a/packages/cli/src/ui/components/Header.test.tsx +++ b/packages/cli/src/ui/components/Header.test.tsx @@ -98,6 +98,7 @@ describe('
', () => { primary: '', message: '', input: '', + selection: '', diff: { added: '', removed: '' }, }, border: { diff --git a/packages/cli/src/ui/components/SessionBrowser.tsx b/packages/cli/src/ui/components/SessionBrowser.tsx index f1dabc9548..71399fb2f1 100644 --- a/packages/cli/src/ui/components/SessionBrowser.tsx +++ b/packages/cli/src/ui/components/SessionBrowser.tsx @@ -484,7 +484,10 @@ const SessionItem = ({ )); return ( - + {prefix} diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.tsx index f9470ea840..3465917d30 100644 --- a/packages/cli/src/ui/components/SuggestionsDisplay.tsx +++ b/packages/cli/src/ui/components/SuggestionsDisplay.tsx @@ -97,7 +97,11 @@ export function SuggestionsDisplay({ ); return ( - + should default to a dark theme when terminal │ 11. Ayu Light │ │ │ │ 12. Default Light └─────────────────────────────────────────────────┘ │ │ ▼ │ -│ ╭────────────────────────────────────────────────╮ │ -│ │ DEVELOPER TOOLS (Not visible to users) │ │ -│ │ │ │ -│ │ How do colors get applied? │ │ -│ │ • Hex: Rendered exactly by modern terminals. │ │ -│ │ Not overridden by app themes. │ │ -│ │ • Blank: Uses your terminal's default │ │ -│ │ foreground/background. │ │ -│ │ • Compatibility: On older terminals, hex is │ │ -│ │ approximated to the nearest ANSI color. │ │ -│ │ • ANSI Names: 'red', 'green', etc. are │ │ -│ │ mapped to your terminal app's palette. │ │ -│ │ │ │ -│ │ Value Name │ │ -│ │ #1E1E2E background.primary │ │ -│ │ #2a2b3c background.message │ │ -│ │ #313243 background.input │ │ -│ │ #28350B background.diff.added │ │ -│ │ #430000 background.diff.removed │ │ -│ │ (blank) text.primary │ │ -│ │ #6C7086 text.secondary │ │ -│ │ #89B4FA text.link │ │ -│ │ #CBA6F7 text.accent │ │ -│ │ (blank) text.response │ │ -│ │ #3d3f51 border.default │ │ -│ │ #89B4FA border.focused │ │ -│ │ #6C7086 ui.comment │ │ -│ │ #89DCEB ui.symbol │ │ -│ │ #3d3f51 ui.dark │ │ -│ │ #89B4FA ui.focus │ │ -│ │ #F38BA8 status.error │ │ -│ │ #A6E3A1 status.success │ │ -│ │ #F9E2AF status.warning │ │ -│ │ #4796E4 ui.gradient │ │ -│ │ #847ACE │ │ -│ │ #C3677F │ │ -│ ╰────────────────────────────────────────────────╯ │ +│ ╭─────────────────────────────────────────────────╮ │ +│ │ DEVELOPER TOOLS (Not visible to users) │ │ +│ │ │ │ +│ │ How do colors get applied? │ │ +│ │ • Hex: Rendered exactly by modern terminals. │ │ +│ │ Not overridden by app themes. │ │ +│ │ • Blank: Uses your terminal's default │ │ +│ │ foreground/background. │ │ +│ │ • Compatibility: On older terminals, hex is │ │ +│ │ approximated to the nearest ANSI color. │ │ +│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │ +│ │ to your terminal app's palette. │ │ +│ │ │ │ +│ │ Value Name │ │ +│ │ #1E1E… backgroun Main terminal background │ │ +│ │ d.primary color │ │ +│ │ #2a2… backgroun Subtle background for │ │ +│ │ d.message message blocks │ │ +│ │ #313… backgroun Background for the input │ │ +│ │ d.input prompt │ │ +│ │ #39… background. Background highlight for │ │ +│ │ selection selected/focused items │ │ +│ │ #283… backgrou Background for added lines │ │ +│ │ nd.diff. in diffs │ │ +│ │ added │ │ +│ │ #430… backgroun Background for removed │ │ +│ │ d.diff.re lines in diffs │ │ +│ │ moved │ │ +│ │ (blank text.prim Primary text color (uses │ │ +│ │ ) ary terminal default if blank) │ │ +│ │ #6C7086 text.secon Secondary/dimmed text │ │ +│ │ dary color │ │ +│ │ #89B4FA text.link Hyperlink and highlighting │ │ +│ │ color │ │ +│ │ #CBA6F7 text.accen Accent color for │ │ +│ │ t emphasis │ │ +│ │ (blank) text.res Color for model response │ │ +│ │ ponse text (uses terminal default │ │ +│ │ if blank) │ │ +│ │ #3d3f51 border.def Standard border color │ │ +│ │ ault │ │ +│ │ #89B4FAborder.fo Border color when an │ │ +│ │ cused element is focused │ │ +│ │ #6C7086ui.comme Color for code comments and │ │ +│ │ nt metadata │ │ +│ │ #89DCE ui.symbol Color for technical symbols │ │ +│ │ B and UI icons │ │ +│ │ #3d3f5 ui.dark Deeply dimmed color for │ │ +│ │ 1 subtle UI elements │ │ +│ │ #89B4F ui.focus Color for focused or │ │ +│ │ A selected UI elements (e.g. │ │ +│ │ menu items) │ │ +│ │ #F38BA8status.err Color for error messages │ │ +│ │ or and critical status │ │ +│ │ #A6E3A1status.suc Color for success messages │ │ +│ │ cess and positive status │ │ +│ │ #F9E2A status.wa Color for warnings and │ │ +│ │ F rning cautionary status │ │ +│ │ #4796E4 ui.gradien │ │ +│ │ #847ACE t │ │ +│ │ #C3677F │ │ +│ ╰─────────────────────────────────────────────────╯ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ │ │ @@ -80,43 +105,68 @@ exports[`Initial Theme Selection > should default to a light theme when terminal │ 11. Default Dark (Incompatible) │ │ │ │ 12. Dracula Dark (Incompatible) └─────────────────────────────────────────────────┘ │ │ ▼ │ -│ ╭────────────────────────────────────────────────╮ │ -│ │ DEVELOPER TOOLS (Not visible to users) │ │ -│ │ │ │ -│ │ How do colors get applied? │ │ -│ │ • Hex: Rendered exactly by modern terminals. │ │ -│ │ Not overridden by app themes. │ │ -│ │ • Blank: Uses your terminal's default │ │ -│ │ foreground/background. │ │ -│ │ • Compatibility: On older terminals, hex is │ │ -│ │ approximated to the nearest ANSI color. │ │ -│ │ • ANSI Names: 'red', 'green', etc. are │ │ -│ │ mapped to your terminal app's palette. │ │ -│ │ │ │ -│ │ Value Name │ │ -│ │ #FAFAFA background.primary │ │ -│ │ #eaecee background.message │ │ -│ │ #e2e4e8 background.input │ │ -│ │ #C6EAD8 background.diff.added │ │ -│ │ #FFCCCC background.diff.removed │ │ -│ │ (blank) text.primary │ │ -│ │ #97a0b0 text.secondary │ │ -│ │ #3B82F6 text.link │ │ -│ │ #8B5CF6 text.accent │ │ -│ │ (blank) text.response │ │ -│ │ #d2d6dc border.default │ │ -│ │ #3B82F6 border.focused │ │ -│ │ #97a0b0 ui.comment │ │ -│ │ #06B6D4 ui.symbol │ │ -│ │ #d2d6dc ui.dark │ │ -│ │ #3B82F6 ui.focus │ │ -│ │ #DD4C4C status.error │ │ -│ │ #3CA84B status.success │ │ -│ │ #D5A40A status.warning │ │ -│ │ #4796E4 ui.gradient │ │ -│ │ #847ACE │ │ -│ │ #C3677F │ │ -│ ╰────────────────────────────────────────────────╯ │ +│ ╭─────────────────────────────────────────────────╮ │ +│ │ DEVELOPER TOOLS (Not visible to users) │ │ +│ │ │ │ +│ │ How do colors get applied? │ │ +│ │ • Hex: Rendered exactly by modern terminals. │ │ +│ │ Not overridden by app themes. │ │ +│ │ • Blank: Uses your terminal's default │ │ +│ │ foreground/background. │ │ +│ │ • Compatibility: On older terminals, hex is │ │ +│ │ approximated to the nearest ANSI color. │ │ +│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │ +│ │ to your terminal app's palette. │ │ +│ │ │ │ +│ │ Value Name │ │ +│ │ #FAFA… backgroun Main terminal background │ │ +│ │ d.primary color │ │ +│ │ #eae… backgroun Subtle background for │ │ +│ │ d.message message blocks │ │ +│ │ #e2e… backgroun Background for the input │ │ +│ │ d.input prompt │ │ +│ │ #d4… background. Background highlight for │ │ +│ │ selection selected/focused items │ │ +│ │ #C6E… backgrou Background for added lines │ │ +│ │ nd.diff. in diffs │ │ +│ │ added │ │ +│ │ #FFC… backgroun Background for removed │ │ +│ │ d.diff.re lines in diffs │ │ +│ │ moved │ │ +│ │ (blank text.prim Primary text color (uses │ │ +│ │ ) ary terminal default if blank) │ │ +│ │ #97a0b0 text.secon Secondary/dimmed text │ │ +│ │ dary color │ │ +│ │ #3B82F6 text.link Hyperlink and highlighting │ │ +│ │ color │ │ +│ │ #8B5CF6 text.accen Accent color for │ │ +│ │ t emphasis │ │ +│ │ (blank) text.res Color for model response │ │ +│ │ ponse text (uses terminal default │ │ +│ │ if blank) │ │ +│ │ #d2d6dc border.def Standard border color │ │ +│ │ ault │ │ +│ │ #3B82F6border.fo Border color when an │ │ +│ │ cused element is focused │ │ +│ │ #97a0b0ui.comme Color for code comments and │ │ +│ │ nt metadata │ │ +│ │ #06B6D ui.symbol Color for technical symbols │ │ +│ │ 4 and UI icons │ │ +│ │ #d2d6d ui.dark Deeply dimmed color for │ │ +│ │ c subtle UI elements │ │ +│ │ #3B82F ui.focus Color for focused or │ │ +│ │ 6 selected UI elements (e.g. │ │ +│ │ menu items) │ │ +│ │ #DD4C4Cstatus.err Color for error messages │ │ +│ │ or and critical status │ │ +│ │ #3CA84Bstatus.suc Color for success messages │ │ +│ │ cess and positive status │ │ +│ │ #D5A40 status.wa Color for warnings and │ │ +│ │ A rning cautionary status │ │ +│ │ #4796E4 ui.gradien │ │ +│ │ #847ACE t │ │ +│ │ #C3677F │ │ +│ ╰─────────────────────────────────────────────────╯ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ │ │ @@ -142,43 +192,68 @@ exports[`Initial Theme Selection > should use the theme from settings even if te │ 11. Ayu Light │ │ │ │ 12. Default Light └─────────────────────────────────────────────────┘ │ │ ▼ │ -│ ╭────────────────────────────────────────────────╮ │ -│ │ DEVELOPER TOOLS (Not visible to users) │ │ -│ │ │ │ -│ │ How do colors get applied? │ │ -│ │ • Hex: Rendered exactly by modern terminals. │ │ -│ │ Not overridden by app themes. │ │ -│ │ • Blank: Uses your terminal's default │ │ -│ │ foreground/background. │ │ -│ │ • Compatibility: On older terminals, hex is │ │ -│ │ approximated to the nearest ANSI color. │ │ -│ │ • ANSI Names: 'red', 'green', etc. are │ │ -│ │ mapped to your terminal app's palette. │ │ -│ │ │ │ -│ │ Value Name │ │ -│ │ #1E1E2E background.primary │ │ -│ │ #2a2b3c background.message │ │ -│ │ #313243 background.input │ │ -│ │ #28350B background.diff.added │ │ -│ │ #430000 background.diff.removed │ │ -│ │ (blank) text.primary │ │ -│ │ #6C7086 text.secondary │ │ -│ │ #89B4FA text.link │ │ -│ │ #CBA6F7 text.accent │ │ -│ │ (blank) text.response │ │ -│ │ #3d3f51 border.default │ │ -│ │ #89B4FA border.focused │ │ -│ │ #6C7086 ui.comment │ │ -│ │ #89DCEB ui.symbol │ │ -│ │ #3d3f51 ui.dark │ │ -│ │ #89B4FA ui.focus │ │ -│ │ #F38BA8 status.error │ │ -│ │ #A6E3A1 status.success │ │ -│ │ #F9E2AF status.warning │ │ -│ │ #4796E4 ui.gradient │ │ -│ │ #847ACE │ │ -│ │ #C3677F │ │ -│ ╰────────────────────────────────────────────────╯ │ +│ ╭─────────────────────────────────────────────────╮ │ +│ │ DEVELOPER TOOLS (Not visible to users) │ │ +│ │ │ │ +│ │ How do colors get applied? │ │ +│ │ • Hex: Rendered exactly by modern terminals. │ │ +│ │ Not overridden by app themes. │ │ +│ │ • Blank: Uses your terminal's default │ │ +│ │ foreground/background. │ │ +│ │ • Compatibility: On older terminals, hex is │ │ +│ │ approximated to the nearest ANSI color. │ │ +│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │ +│ │ to your terminal app's palette. │ │ +│ │ │ │ +│ │ Value Name │ │ +│ │ #1E1E… backgroun Main terminal background │ │ +│ │ d.primary color │ │ +│ │ #2a2… backgroun Subtle background for │ │ +│ │ d.message message blocks │ │ +│ │ #313… backgroun Background for the input │ │ +│ │ d.input prompt │ │ +│ │ #39… background. Background highlight for │ │ +│ │ selection selected/focused items │ │ +│ │ #283… backgrou Background for added lines │ │ +│ │ nd.diff. in diffs │ │ +│ │ added │ │ +│ │ #430… backgroun Background for removed │ │ +│ │ d.diff.re lines in diffs │ │ +│ │ moved │ │ +│ │ (blank text.prim Primary text color (uses │ │ +│ │ ) ary terminal default if blank) │ │ +│ │ #6C7086 text.secon Secondary/dimmed text │ │ +│ │ dary color │ │ +│ │ #89B4FA text.link Hyperlink and highlighting │ │ +│ │ color │ │ +│ │ #CBA6F7 text.accen Accent color for │ │ +│ │ t emphasis │ │ +│ │ (blank) text.res Color for model response │ │ +│ │ ponse text (uses terminal default │ │ +│ │ if blank) │ │ +│ │ #3d3f51 border.def Standard border color │ │ +│ │ ault │ │ +│ │ #89B4FAborder.fo Border color when an │ │ +│ │ cused element is focused │ │ +│ │ #6C7086ui.comme Color for code comments and │ │ +│ │ nt metadata │ │ +│ │ #89DCE ui.symbol Color for technical symbols │ │ +│ │ B and UI icons │ │ +│ │ #3d3f5 ui.dark Deeply dimmed color for │ │ +│ │ 1 subtle UI elements │ │ +│ │ #89B4F ui.focus Color for focused or │ │ +│ │ A selected UI elements (e.g. │ │ +│ │ menu items) │ │ +│ │ #F38BA8status.err Color for error messages │ │ +│ │ or and critical status │ │ +│ │ #A6E3A1status.suc Color for success messages │ │ +│ │ cess and positive status │ │ +│ │ #F9E2A status.wa Color for warnings and │ │ +│ │ F rning cautionary status │ │ +│ │ #4796E4 ui.gradien │ │ +│ │ #847ACE t │ │ +│ │ #C3677F │ │ +│ ╰─────────────────────────────────────────────────╯ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ │ │ @@ -218,43 +293,66 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode │ 11. Ayu Light │ │ │ │ 12. Default Light └─────────────────────────────────────────────────┘ │ │ ▼ │ -│ ╭────────────────────────────────────────────────╮ │ -│ │ DEVELOPER TOOLS (Not visible to users) │ │ -│ │ │ │ -│ │ How do colors get applied? │ │ -│ │ • Hex: Rendered exactly by modern terminals. │ │ -│ │ Not overridden by app themes. │ │ -│ │ • Blank: Uses your terminal's default │ │ -│ │ foreground/background. │ │ -│ │ • Compatibility: On older terminals, hex is │ │ -│ │ approximated to the nearest ANSI color. │ │ -│ │ • ANSI Names: 'red', 'green', etc. are │ │ -│ │ mapped to your terminal app's palette. │ │ -│ │ │ │ -│ │ Value Name │ │ -│ │ #1E1E2E background.primary │ │ -│ │ #2a2b3c background.message │ │ -│ │ #313243 background.input │ │ -│ │ #28350B background.diff.added │ │ -│ │ #430000 background.diff.removed │ │ -│ │ (blank) text.primary │ │ -│ │ #6C7086 text.secondary │ │ -│ │ #89B4FA text.link │ │ -│ │ #CBA6F7 text.accent │ │ -│ │ (blank) text.response │ │ -│ │ #3d3f51 border.default │ │ -│ │ #89B4FA border.focused │ │ -│ │ #6C7086 ui.comment │ │ -│ │ #6C7086 ui.symbol │ │ -│ │ #3d3f51 ui.dark │ │ -│ │ #89B4FA ui.focus │ │ -│ │ #F38BA8 status.error │ │ -│ │ #A6E3A1 status.success │ │ -│ │ #F9E2AF status.warning │ │ -│ │ #4796E4 ui.gradient │ │ -│ │ #847ACE │ │ -│ │ #C3677F │ │ -│ ╰────────────────────────────────────────────────╯ │ +│ ╭─────────────────────────────────────────────────╮ │ +│ │ DEVELOPER TOOLS (Not visible to users) │ │ +│ │ │ │ +│ │ How do colors get applied? │ │ +│ │ • Hex: Rendered exactly by modern terminals. │ │ +│ │ Not overridden by app themes. │ │ +│ │ • Blank: Uses your terminal's default │ │ +│ │ foreground/background. │ │ +│ │ • Compatibility: On older terminals, hex is │ │ +│ │ approximated to the nearest ANSI color. │ │ +│ │ • ANSI Names: 'red', 'green', etc. are mapped │ │ +│ │ to your terminal app's palette. │ │ +│ │ │ │ +│ │ Value Name │ │ +│ │ #1E1E… backgroun Main terminal background │ │ +│ │ d.primary color │ │ +│ │ #2a2… backgroun Subtle background for │ │ +│ │ d.message message blocks │ │ +│ │ #313… backgroun Background for the input │ │ +│ │ d.input prompt │ │ +│ │ #283… backgrou Background for added lines │ │ +│ │ nd.diff. in diffs │ │ +│ │ added │ │ +│ │ #430… backgroun Background for removed │ │ +│ │ d.diff.re lines in diffs │ │ +│ │ moved │ │ +│ │ (blank text.prim Primary text color (uses │ │ +│ │ ) ary terminal default if blank) │ │ +│ │ #6C7086 text.secon Secondary/dimmed text │ │ +│ │ dary color │ │ +│ │ #89B4FA text.link Hyperlink and highlighting │ │ +│ │ color │ │ +│ │ #CBA6F7 text.accen Accent color for │ │ +│ │ t emphasis │ │ +│ │ (blank) text.res Color for model response │ │ +│ │ ponse text (uses terminal default │ │ +│ │ if blank) │ │ +│ │ #3d3f51 border.def Standard border color │ │ +│ │ ault │ │ +│ │ #89B4FAborder.fo Border color when an │ │ +│ │ cused element is focused │ │ +│ │ #6C7086ui.comme Color for code comments and │ │ +│ │ nt metadata │ │ +│ │ #6C708 ui.symbol Color for technical symbols │ │ +│ │ 6 and UI icons │ │ +│ │ #3d3f5 ui.dark Deeply dimmed color for │ │ +│ │ 1 subtle UI elements │ │ +│ │ #89B4F ui.focus Color for focused or │ │ +│ │ A selected UI elements (e.g. │ │ +│ │ menu items) │ │ +│ │ #F38BA8status.err Color for error messages │ │ +│ │ or and critical status │ │ +│ │ #A6E3A1status.suc Color for success messages │ │ +│ │ cess and positive status │ │ +│ │ #F9E2A status.wa Color for warnings and │ │ +│ │ F rning cautionary status │ │ +│ │ #4796E4 ui.gradien │ │ +│ │ #847ACE t │ │ +│ │ #C3677F │ │ +│ ╰─────────────────────────────────────────────────╯ │ │ │ │ (Use Enter to select, Tab to configure scope, Esc to close) │ │ │ diff --git a/packages/cli/src/ui/components/shared/BaseSelectionList.tsx b/packages/cli/src/ui/components/shared/BaseSelectionList.tsx index 117ed57e97..4ab6f18c67 100644 --- a/packages/cli/src/ui/components/shared/BaseSelectionList.tsx +++ b/packages/cli/src/ui/components/shared/BaseSelectionList.tsx @@ -137,7 +137,13 @@ export function BaseSelectionList< )}.`; return ( - + {/* Radio button indicator */} - + !INK_SUPPORTED_NAMES.has(name)) - .map(([name, hex]) => [name, `#${hex}`]), -); +export { + resolveColor, + interpolateColor, + getThemeTypeFromBackgroundColor, + INK_SUPPORTED_NAMES, + INK_NAME_TO_HEX_MAP, + getLuminance, + CSS_NAME_TO_HEX_MAP, +}; /** * Checks if a color string is valid (hex, Ink-supported color name, or CSS color name). @@ -66,45 +53,6 @@ export function isValidColor(color: string): boolean { return false; } -/** - * Resolves a CSS color value (name or hex) into an Ink-compatible color string. - * @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki'). - * @returns An Ink-compatible color string (hex or name), or undefined if not resolvable. - */ -export function resolveColor(colorValue: string): string | undefined { - const lowerColor = colorValue.toLowerCase(); - - // 1. Check if it's already a hex code and valid - if (lowerColor.startsWith('#')) { - if (/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) { - return lowerColor; - } else { - return undefined; - } - } - - // Handle hex codes without # - if (/^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) { - return `#${lowerColor}`; - } - - // 2. Check if it's an Ink supported name (lowercase) - if (INK_SUPPORTED_NAMES.has(lowerColor)) { - return lowerColor; // Use Ink name directly - } - - // 3. Check if it's a known CSS name we can map to hex - if (CSS_NAME_TO_HEX_MAP[lowerColor]) { - return CSS_NAME_TO_HEX_MAP[lowerColor]; // Use mapped hex - } - - // 4. Could not resolve - debugLogger.warn( - `[ColorUtils] Could not resolve color "${colorValue}" to an Ink-compatible format.`, - ); - return undefined; -} - /** * Returns a "safe" background color to use in low-color terminals if the * terminal background is a standard black or white. @@ -132,73 +80,6 @@ export function getSafeLowColorBackground( return undefined; } -export function interpolateColor( - color1: string, - color2: string, - factor: number, -) { - if (factor <= 0 && color1) { - return color1; - } - if (factor >= 1 && color2) { - return color2; - } - if (!color1 || !color2) { - return ''; - } - const gradient = tinygradient(color1, color2); - const color = gradient.rgbAt(factor); - return color.toHexString(); -} - -export function getThemeTypeFromBackgroundColor( - backgroundColor: string | undefined, -): 'light' | 'dark' | undefined { - if (!backgroundColor) { - return undefined; - } - - const resolvedColor = resolveColor(backgroundColor); - if (!resolvedColor) { - return undefined; - } - - const luminance = getLuminance(resolvedColor); - return luminance > 128 ? 'light' : 'dark'; -} - -// Mapping for ANSI bright colors that are not in tinycolor's standard CSS names -export const INK_NAME_TO_HEX_MAP: Readonly> = { - blackbright: '#555555', - redbright: '#ff5555', - greenbright: '#55ff55', - yellowbright: '#ffff55', - bluebright: '#5555ff', - magentabright: '#ff55ff', - cyanbright: '#55ffff', - whitebright: '#ffffff', -}; - -/** - * Calculates the relative luminance of a color. - * See https://www.w3.org/TR/WCAG20/#relativeluminancedef - * - * @param color Color string (hex or Ink-supported name) - * @returns Luminance value (0-255) - */ -export function getLuminance(color: string): number { - const resolved = color.toLowerCase(); - const hex = INK_NAME_TO_HEX_MAP[resolved] || resolved; - - const colorObj = tinycolor(hex); - if (!colorObj.isValid()) { - return 0; - } - - // tinycolor returns 0-1, we need 0-255 - return colorObj.getLuminance() * 255; -} - // Hysteresis thresholds to prevent flickering when the background color // is ambiguous (near the midpoint). export const LIGHT_THEME_LUMINANCE_THRESHOLD = 140; diff --git a/packages/cli/src/ui/themes/no-color.ts b/packages/cli/src/ui/themes/no-color.ts index 1af71fd52a..3baeaca4cb 100644 --- a/packages/cli/src/ui/themes/no-color.ts +++ b/packages/cli/src/ui/themes/no-color.ts @@ -26,6 +26,7 @@ const noColorColorsTheme: ColorsTheme = { DarkGray: '', InputBackground: '', MessageBackground: '', + SelectionBackground: '', }; const noColorSemanticColors: SemanticColors = { @@ -40,6 +41,7 @@ const noColorSemanticColors: SemanticColors = { primary: '', message: '', input: '', + selection: '', diff: { added: '', removed: '', diff --git a/packages/cli/src/ui/themes/semantic-tokens.ts b/packages/cli/src/ui/themes/semantic-tokens.ts index 825ed4955b..dbdb903540 100644 --- a/packages/cli/src/ui/themes/semantic-tokens.ts +++ b/packages/cli/src/ui/themes/semantic-tokens.ts @@ -18,6 +18,7 @@ export interface SemanticColors { primary: string; message: string; input: string; + selection: string; diff: { added: string; removed: string; diff --git a/packages/cli/src/ui/themes/solarized-dark.ts b/packages/cli/src/ui/themes/solarized-dark.ts index e21422faf0..a30a25e2a5 100644 --- a/packages/cli/src/ui/themes/solarized-dark.ts +++ b/packages/cli/src/ui/themes/solarized-dark.ts @@ -4,8 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { type ColorsTheme, Theme } from './theme.js'; +import { type ColorsTheme, Theme, interpolateColor } from './theme.js'; import { type SemanticColors } from './semantic-tokens.js'; +import { DEFAULT_SELECTION_OPACITY } from '../constants.js'; const solarizedDarkColors: ColorsTheme = { type: 'dark', @@ -38,6 +39,11 @@ const semanticColors: SemanticColors = { primary: '#002b36', message: '#073642', input: '#073642', + selection: interpolateColor( + '#002b36', + '#859900', + DEFAULT_SELECTION_OPACITY, + ), diff: { added: '#00382f', removed: '#3d0115', diff --git a/packages/cli/src/ui/themes/solarized-light.ts b/packages/cli/src/ui/themes/solarized-light.ts index e18e5da136..c08f6705a6 100644 --- a/packages/cli/src/ui/themes/solarized-light.ts +++ b/packages/cli/src/ui/themes/solarized-light.ts @@ -4,8 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { type ColorsTheme, Theme } from './theme.js'; +import { type ColorsTheme, Theme, interpolateColor } from './theme.js'; import { type SemanticColors } from './semantic-tokens.js'; +import { DEFAULT_SELECTION_OPACITY } from '../constants.js'; const solarizedLightColors: ColorsTheme = { type: 'light', @@ -38,6 +39,11 @@ const semanticColors: SemanticColors = { primary: '#fdf6e3', message: '#eee8d5', input: '#eee8d5', + selection: interpolateColor( + '#fdf6e3', + '#859900', + DEFAULT_SELECTION_OPACITY, + ), diff: { added: '#d7f2d7', removed: '#f2d7d7', diff --git a/packages/cli/src/ui/themes/theme-manager.ts b/packages/cli/src/ui/themes/theme-manager.ts index da54ba5d3e..412643bbd6 100644 --- a/packages/cli/src/ui/themes/theme-manager.ts +++ b/packages/cli/src/ui/themes/theme-manager.ts @@ -22,16 +22,16 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import type { Theme, ThemeType, ColorsTheme } from './theme.js'; import type { CustomTheme } from '@google/gemini-cli-core'; -import { createCustomTheme, validateCustomTheme } from './theme.js'; -import type { SemanticColors } from './semantic-tokens.js'; -import { +import { createCustomTheme, validateCustomTheme , interpolateColor, getThemeTypeFromBackgroundColor, resolveColor, -} from './color-utils.js'; +} from './theme.js'; +import type { SemanticColors } from './semantic-tokens.js'; import { DEFAULT_BACKGROUND_OPACITY, DEFAULT_INPUT_BACKGROUND_OPACITY, + DEFAULT_SELECTION_OPACITY, DEFAULT_BORDER_OPACITY, } from '../constants.js'; import { ANSI } from './ansi.js'; @@ -369,6 +369,11 @@ class ThemeManager { colors.Gray, DEFAULT_BACKGROUND_OPACITY, ), + SelectionBackground: interpolateColor( + this.terminalBackground, + colors.AccentGreen, + DEFAULT_SELECTION_OPACITY, + ), }; } else { this.cachedColors = colors; @@ -402,6 +407,7 @@ class ThemeManager { primary: this.terminalBackground, message: colors.MessageBackground!, input: colors.InputBackground!, + selection: colors.SelectionBackground!, }, border: { ...semanticColors.border, diff --git a/packages/cli/src/ui/themes/theme.test.ts b/packages/cli/src/ui/themes/theme.test.ts index da6bd0cbc5..db083d95b5 100644 --- a/packages/cli/src/ui/themes/theme.test.ts +++ b/packages/cli/src/ui/themes/theme.test.ts @@ -32,6 +32,7 @@ describe('createCustomTheme', () => { DiffRemoved: '#FF0000', Comment: '#808080', Gray: '#cccccc', + SelectionBackground: '#004000', // DarkGray intentionally omitted to test fallback }; @@ -103,6 +104,7 @@ describe('validateCustomTheme', () => { DiffRemoved: '#FF0000', Comment: '#808080', Gray: '#808080', + SelectionBackground: '#004000', }; it('should return isValid: true for a valid theme', () => { @@ -153,6 +155,7 @@ describe('themeManager.loadCustomThemes', () => { AccentRed: '#F00', Comment: '#888', Gray: '#888', + SelectionBackground: '#040', }; it('should use values from DEFAULT_THEME when DiffAdded and DiffRemoved are not provided', () => { diff --git a/packages/cli/src/ui/themes/theme.ts b/packages/cli/src/ui/themes/theme.ts index 0167f1623c..88894a2c89 100644 --- a/packages/cli/src/ui/themes/theme.ts +++ b/packages/cli/src/ui/themes/theme.ts @@ -18,8 +18,150 @@ import type { CustomTheme } from '@google/gemini-cli-core'; import { DEFAULT_BACKGROUND_OPACITY, DEFAULT_INPUT_BACKGROUND_OPACITY, + DEFAULT_SELECTION_OPACITY, DEFAULT_BORDER_OPACITY, } from '../constants.js'; +import tinygradient from 'tinygradient'; +import tinycolor from 'tinycolor2'; + +// Define the set of Ink's named colors for quick lookup +export const INK_SUPPORTED_NAMES = new Set([ + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'cyan', + 'magenta', + 'white', + 'gray', + 'grey', + 'blackbright', + 'redbright', + 'greenbright', + 'yellowbright', + 'bluebright', + 'cyanbright', + 'magentabright', + 'whitebright', +]); + +// Use tinycolor's built-in names map for CSS colors, excluding ones Ink supports +export const CSS_NAME_TO_HEX_MAP = Object.fromEntries( + Object.entries(tinycolor.names) + .filter(([name]) => !INK_SUPPORTED_NAMES.has(name)) + .map(([name, hex]) => [name, `#${hex}`]), +); + +// Mapping for ANSI bright colors that are not in tinycolor's standard CSS names +export const INK_NAME_TO_HEX_MAP: Readonly> = { + blackbright: '#555555', + redbright: '#ff5555', + greenbright: '#55ff55', + yellowbright: '#ffff55', + bluebright: '#5555ff', + magentabright: '#ff55ff', + cyanbright: '#55ffff', + whitebright: '#ffffff', +}; + +/** + * Calculates the relative luminance of a color. + * See https://www.w3.org/TR/WCAG20/#relativeluminancedef + * + * @param color Color string (hex or Ink-supported name) + * @returns Luminance value (0-255) + */ +export function getLuminance(color: string): number { + const resolved = color.toLowerCase(); + const hex = INK_NAME_TO_HEX_MAP[resolved] || resolved; + + const colorObj = tinycolor(hex); + if (!colorObj.isValid()) { + return 0; + } + + // tinycolor returns 0-1, we need 0-255 + return colorObj.getLuminance() * 255; +} + +/** + * Resolves a CSS color value (name or hex) into an Ink-compatible color string. + * @param colorValue The raw color string (e.g., 'blue', '#ff0000', 'darkkhaki'). + * @returns An Ink-compatible color string (hex or name), or undefined if not resolvable. + */ +export function resolveColor(colorValue: string): string | undefined { + const lowerColor = colorValue.toLowerCase(); + + // 1. Check if it's already a hex code and valid + if (lowerColor.startsWith('#')) { + if (/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) { + return lowerColor; + } else { + return undefined; + } + } + + // Handle hex codes without # + if (/^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/.test(colorValue)) { + return `#${lowerColor}`; + } + + // 2. Check if it's an Ink supported name (lowercase) + if (INK_SUPPORTED_NAMES.has(lowerColor)) { + return lowerColor; // Use Ink name directly + } + + // 3. Check if it's a known CSS name we can map to hex + // We can't import CSS_NAME_TO_HEX_MAP here due to circular deps, + // but we can use tinycolor directly for named colors. + const colorObj = tinycolor(lowerColor); + if (colorObj.isValid()) { + return colorObj.toHexString(); + } + + // 4. Could not resolve + return undefined; +} + +export function interpolateColor( + color1: string, + color2: string, + factor: number, +) { + if (factor <= 0 && color1) { + return color1; + } + if (factor >= 1 && color2) { + return color2; + } + if (!color1 || !color2) { + return ''; + } + try { + const gradient = tinygradient(color1, color2); + const color = gradient.rgbAt(factor); + return color.toHexString(); + } catch (_e) { + return color1; + } +} + +export function getThemeTypeFromBackgroundColor( + backgroundColor: string | undefined, +): 'light' | 'dark' | undefined { + if (!backgroundColor) { + return undefined; + } + + const resolvedColor = resolveColor(backgroundColor); + if (!resolvedColor) { + return undefined; + } + + const luminance = getLuminance(resolvedColor); + return luminance > 128 ? 'light' : 'dark'; +} export type { CustomTheme }; @@ -43,6 +185,7 @@ export interface ColorsTheme { DarkGray: string; InputBackground?: string; MessageBackground?: string; + SelectionBackground?: string; GradientColors?: string[]; } @@ -72,6 +215,11 @@ export const lightTheme: ColorsTheme = { '#97a0b0', DEFAULT_BACKGROUND_OPACITY, ), + SelectionBackground: interpolateColor( + '#FAFAFA', + '#3CA84B', + DEFAULT_SELECTION_OPACITY, + ), GradientColors: ['#4796E4', '#847ACE', '#C3677F'], }; @@ -101,6 +249,11 @@ export const darkTheme: ColorsTheme = { '#6C7086', DEFAULT_BACKGROUND_OPACITY, ), + SelectionBackground: interpolateColor( + '#1E1E2E', + '#A6E3A1', + DEFAULT_SELECTION_OPACITY, + ), GradientColors: ['#4796E4', '#847ACE', '#C3677F'], }; @@ -122,6 +275,7 @@ export const ansiTheme: ColorsTheme = { DarkGray: 'gray', InputBackground: 'black', MessageBackground: 'black', + SelectionBackground: 'black', }; export class Theme { @@ -173,6 +327,13 @@ export class Theme { this.colors.Gray, DEFAULT_INPUT_BACKGROUND_OPACITY, ), + selection: + this.colors.SelectionBackground ?? + interpolateColor( + this.colors.Background, + this.colors.AccentGreen, + DEFAULT_SELECTION_OPACITY, + ), diff: { added: this.colors.DiffAdded, removed: this.colors.DiffRemoved, @@ -295,6 +456,11 @@ export function createCustomTheme(customTheme: CustomTheme): Theme { customTheme.text?.secondary ?? customTheme.Gray ?? '', DEFAULT_BACKGROUND_OPACITY, ), + SelectionBackground: interpolateColor( + customTheme.background?.primary ?? customTheme.Background ?? '', + customTheme.status?.success ?? customTheme.AccentGreen ?? '#3CA84B', // Fallback to a default green if not found + DEFAULT_SELECTION_OPACITY, + ), GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors, }; @@ -451,6 +617,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme { primary: customTheme.background?.primary ?? colors.Background, message: colors.MessageBackground!, input: colors.InputBackground!, + selection: colors.SelectionBackground!, diff: { added: customTheme.background?.diff?.added ?? colors.DiffAdded, removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,