Skip to content

Commit 75f9ce8

Browse files
committed
Refactor to semantic color system with Tailwind-inspired structure
Replace 60+ specific color properties with semantic roles: - Core: primary, secondary, success, error, warning, info - Neutrals: foreground, background, muted, border, surface, surfaceHover - Context: aiLine, userLine, aiText, agentContent, etc. Changes: - Update ChatTheme interface with semantic color structure - Refactor dark and light theme definitions to use semantic colors - Update all 50+ component files to use new property names - statusAccent → primary - statusSecondary → secondary - messageAiText → aiText - chromeText → foreground - agentResponseCount → muted - logoColor → logo - linkColor → link - (and 20+ more mappings) - Remove color constants from login/constants.ts - Update LoginModal to use theme.success/error instead of constants - Fix background property mapping in theme-config.ts Benefits: - Much easier to create custom themes (15 semantic colors vs 60+ specific) - Cleaner component code (theme.primary vs theme.statusAccent) - Matches modern design system patterns (Tailwind, Chakra, MUI) - Single source of truth for color roles
1 parent 021d570 commit 75f9ce8

20 files changed

+359
-233
lines changed

cli/src/chat.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export const App = ({
208208
? `Welcome back, ${userCredentials.name.trim()}!`
209209
: null
210210

211-
const baseTextColor = theme.chromeText
211+
const baseTextColor = theme.foreground
212212

213213
const homeDir = os.homedir()
214214
const repoRoot = path.dirname(loadedAgentsData.agentsDir)
@@ -226,7 +226,7 @@ export const App = ({
226226
{
227227
type: 'text',
228228
content: '\n\n' + logoBlock,
229-
color: theme.logoColor,
229+
color: theme.logo,
230230
},
231231
]
232232

@@ -1026,7 +1026,7 @@ export const App = ({
10261026
key="virtualization-notice"
10271027
style={{ width: '100%', wrapMode: 'none' }}
10281028
>
1029-
<span fg={theme.statusSecondary}>
1029+
<span fg={theme.secondary}>
10301030
Showing latest {virtualTopLevelMessages.length} of{' '}
10311031
{topLevelMessages.length} messages. Scroll up to load more.
10321032
</span>
@@ -1105,8 +1105,8 @@ export const App = ({
11051105
return output
11061106
}
11071107

1108-
const messageAiTextColor = theme.messageAiText
1109-
const statusSecondaryColor = theme.statusSecondary
1108+
const messageAiTextColor = theme.aiText
1109+
const statusSecondaryColor = theme.secondary
11101110

11111111
return (
11121112
<box
@@ -1116,10 +1116,10 @@ export const App = ({
11161116
paddingRight: 1,
11171117
paddingTop: 1,
11181118
paddingBottom: 1,
1119-
backgroundColor: theme.panelBg,
1119+
backgroundColor: theme.surface,
11201120
border: true,
11211121
borderStyle: 'single',
1122-
borderColor: theme.validationBorderColor,
1122+
borderColor: theme.warning,
11231123
}}
11241124
>
11251125
{/* Header */}
@@ -1237,11 +1237,11 @@ export const App = ({
12371237
{hasStatus && statusIndicatorNode}
12381238
{hasStatus && (exitWarning || shouldShowQueuePreview) && ' '}
12391239
{exitWarning && (
1240-
<span fg={theme.statusSecondary}>{exitWarning}</span>
1240+
<span fg={theme.secondary}>{exitWarning}</span>
12411241
)}
12421242
{exitWarning && shouldShowQueuePreview && ' '}
12431243
{shouldShowQueuePreview && (
1244-
<span fg={theme.statusSecondary} bg={theme.inputFocusedBg}>
1244+
<span fg={theme.secondary} bg={theme.inputFocusedBg}>
12451245
{' '}
12461246
{formatQueuedPreview(
12471247
queuedMessages,

cli/src/components/__tests__/message-block.completion.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const basePalette = createMarkdownPalette(theme)
1313

1414
const palette: MarkdownPalette = {
1515
...basePalette,
16-
inlineCodeFg: theme.messageAiText,
17-
codeTextFg: theme.messageAiText,
16+
inlineCodeFg: theme.aiText,
17+
codeTextFg: theme.aiText,
1818
}
1919

2020
const baseProps = {
@@ -35,8 +35,8 @@ const baseProps = {
3535
startTime: null,
3636
},
3737
theme,
38-
textColor: theme.messageAiText,
39-
timestampColor: theme.timestampAi,
38+
textColor: theme.aiText,
39+
timestampColor: theme.aiTimestamp,
4040
markdownOptions: {
4141
codeBlockWidth: 72,
4242
palette,

cli/src/components/__tests__/message-block.streaming.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const basePalette = createMarkdownPalette(theme)
1313

1414
const palette: MarkdownPalette = {
1515
...basePalette,
16-
inlineCodeFg: theme.messageAiText,
17-
codeTextFg: theme.messageAiText,
16+
inlineCodeFg: theme.aiText,
17+
codeTextFg: theme.aiText,
1818
}
1919

2020
const baseProps = {
@@ -28,8 +28,8 @@ const baseProps = {
2828
completionTime: undefined,
2929
credits: undefined,
3030
theme,
31-
textColor: theme.messageAiText,
32-
timestampColor: theme.timestampAi,
31+
textColor: theme.aiText,
32+
timestampColor: theme.aiTimestamp,
3333
markdownOptions: {
3434
codeBlockWidth: 72,
3535
palette,

cli/src/components/agent-branch-item.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ export const AgentBranchItem = ({
6262
const isExpanded = !isCollapsed
6363
const toggleFrameColor = isExpanded
6464
? theme.agentToggleExpandedBg
65-
: theme.agentResponseCount
66-
const toggleIconColor = isStreaming ? theme.statusAccent : theme.chromeText
65+
: theme.muted
66+
const toggleIconColor = isStreaming ? theme.primary : theme.foreground
6767
const toggleIndicator = onToggle ? (isCollapsed ? '▸ ' : '▾ ') : ''
6868
const toggleLabel = `${branchChar}${toggleIndicator}`
6969
const collapseButtonFrame = theme.agentToggleExpandedBg
@@ -124,7 +124,7 @@ export const AgentBranchItem = ({
124124
if (isTextRenderable(value)) {
125125
return (
126126
<text
127-
fg={theme.agentText}
127+
fg={theme.agentContent}
128128
key="expanded-text"
129129
attributes={getAttributes()}
130130
>
@@ -207,7 +207,7 @@ export const AgentBranchItem = ({
207207
>
208208
<text fg={theme.agentToggleHeaderText}>Prompt</text>
209209
<text
210-
fg={theme.agentText}
210+
fg={theme.agentContent}
211211
style={{ wrapMode: 'word' }}
212212
attributes={getAttributes()}
213213
>
@@ -232,22 +232,22 @@ export const AgentBranchItem = ({
232232
{toggleLabel}
233233
</span>
234234
<span
235-
fg={theme.chromeText}
235+
fg={theme.foreground}
236236
attributes={isExpanded ? TextAttributes.BOLD : undefined}
237237
>
238238
{name}
239239
</span>
240240
{titleSuffix ? (
241241
<span
242-
fg={theme.chromeText}
242+
fg={theme.foreground}
243243
attributes={TextAttributes.BOLD}
244244
>
245245
{` ${titleSuffix}`}
246246
</span>
247247
) : null}
248248
{statusText ? (
249249
<span
250-
fg={statusColor ?? theme.agentResponseCount}
250+
fg={statusColor ?? theme.muted}
251251
attributes={TextAttributes.DIM}
252252
>
253253
{` ${statusText}`}
@@ -267,7 +267,7 @@ export const AgentBranchItem = ({
267267
}}
268268
>
269269
<text
270-
fg={isStreaming ? theme.agentText : theme.agentResponseCount}
270+
fg={isStreaming ? theme.agentContent : theme.muted}
271271
attributes={getAttributes(TextAttributes.ITALIC)}
272272
>
273273
{isStreaming ? streamingPreview : finishedPreview}
@@ -297,7 +297,7 @@ export const AgentBranchItem = ({
297297
Prompt
298298
</text>
299299
<text
300-
fg={theme.agentText}
300+
fg={theme.agentContent}
301301
style={{ wrapMode: 'word' }}
302302
attributes={getAttributes()}
303303
>

cli/src/components/agent-mode-toggle.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import type { ChatTheme } from '../types/theme-system'
77
const getModeConfig = (theme: ChatTheme) =>
88
({
99
FAST: {
10-
frameColor: theme.modeToggleFastBg,
11-
textColor: theme.modeToggleFastText,
10+
frameColor: theme.modeFastBg,
11+
textColor: theme.modeFastText,
1212
label: 'FAST',
1313
},
1414
MAX: {
15-
frameColor: theme.modeToggleMaxBg,
16-
textColor: theme.modeToggleMaxText,
15+
frameColor: theme.modeMaxBg,
16+
textColor: theme.modeMaxText,
1717
label: '💪 MAX',
1818
},
1919
}) as const

cli/src/components/login-modal.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import { useLogo } from '../hooks/use-logo'
1111
import { useSheenAnimation } from '../hooks/use-sheen-animation'
1212
import { useTheme, VariantProvider } from '../hooks/use-theme'
1313
import {
14-
LINK_COLOR_DEFAULT,
15-
LINK_COLOR_CLICKED,
16-
COPY_SUCCESS_COLOR,
17-
COPY_ERROR_COLOR,
18-
WARNING_COLOR,
1914
DEFAULT_TERMINAL_HEIGHT,
2015
MODAL_VERTICAL_MARGIN,
2116
MAX_MODAL_BASE_HEIGHT,
@@ -271,7 +266,7 @@ const LoginModalContent = ({
271266

272267
// Use custom hook for sheen animation
273268
const { applySheenToChar } = useSheenAnimation({
274-
logoColor: theme.logoColor,
269+
logoColor: theme.logo,
275270
terminalWidth: renderer?.width,
276271
sheenPosition,
277272
setSheenPosition,
@@ -281,7 +276,7 @@ const LoginModalContent = ({
281276
const { component: logoComponent } = useLogo({
282277
availableWidth: contentMaxWidth,
283278
applySheenToChar,
284-
textColor: theme.chromeText,
279+
textColor: theme.foreground,
285280
})
286281

287282
// Calculate modal dimensions
@@ -307,7 +302,7 @@ const LoginModalContent = ({
307302
top={modalTop}
308303
border
309304
borderStyle="double"
310-
borderColor={theme.statusAccent}
305+
borderColor={theme.primary}
311306
style={{
312307
width: modalWidth,
313308
height: modalHeight,
@@ -323,14 +318,14 @@ const LoginModalContent = ({
323318
style={{
324319
width: '100%',
325320
padding: 1,
326-
backgroundColor: '#ff0000',
321+
backgroundColor: theme.error,
327322
borderStyle: 'single',
328-
borderColor: WARNING_COLOR,
323+
borderColor: theme.error,
329324
flexShrink: 0,
330325
}}
331326
>
332327
<text style={{ wrapMode: 'word' }}>
333-
<span fg={theme.statusSecondary}>
328+
<span fg={theme.secondary}>
334329
{isNarrow
335330
? "⚠ Found API key but it's invalid. Please log in again."
336331
: '⚠ We found an API key but it appears to be invalid. Please log in again to continue.'}
@@ -374,7 +369,7 @@ const LoginModalContent = ({
374369
}}
375370
>
376371
<text style={{ wrapMode: 'none' }}>
377-
<span fg={theme.statusSecondary}>Loading...</span>
372+
<span fg={theme.secondary}>Loading...</span>
378373
</text>
379374
</box>
380375
)}
@@ -395,7 +390,7 @@ const LoginModalContent = ({
395390
</text>
396391
{!isVerySmall && (
397392
<text style={{ wrapMode: 'word' }}>
398-
<span fg={theme.statusSecondary}>
393+
<span fg={theme.secondary}>
399394
{isNarrow
400395
? 'Please try again'
401396
: 'Please restart the CLI and try again'}
@@ -417,7 +412,7 @@ const LoginModalContent = ({
417412
}}
418413
>
419414
<text style={{ wrapMode: 'word' }}>
420-
<span fg={theme.statusAccent}>
415+
<span fg={theme.primary}>
421416
{isNarrow
422417
? 'Press ENTER to login...'
423418
: 'Press ENTER to open your browser and finish logging in...'}
@@ -439,7 +434,7 @@ const LoginModalContent = ({
439434
}}
440435
>
441436
<text style={{ wrapMode: 'word' }}>
442-
<span fg={theme.statusSecondary}>
437+
<span fg={theme.secondary}>
443438
{isNarrow ? 'Click to copy:' : 'Click link to copy:'}
444439
</span>
445440
</text>
@@ -454,8 +449,8 @@ const LoginModalContent = ({
454449
text={loginUrl}
455450
maxWidth={maxUrlWidth}
456451
formatLines={formatLoginUrlLines}
457-
color={hasClickedLink ? LINK_COLOR_CLICKED : LINK_COLOR_DEFAULT}
458-
activeColor={LINK_COLOR_CLICKED}
452+
color={hasClickedLink ? theme.linkActive : theme.link}
453+
activeColor={theme.linkActive}
459454
underlineOnHover={true}
460455
isActive={justCopied}
461456
onActivate={handleActivateLoginUrl}
@@ -479,8 +474,8 @@ const LoginModalContent = ({
479474
<span
480475
fg={
481476
copyMessage.startsWith('✓')
482-
? COPY_SUCCESS_COLOR
483-
: COPY_ERROR_COLOR
477+
? theme.success
478+
: theme.error
484479
}
485480
>
486481
{copyMessage}

cli/src/components/message-block.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const MessageBlock = ({
7272
registerAgentRef,
7373
}: MessageBlockProps): ReactNode => {
7474
const theme = useTheme()
75-
const resolvedTextColor = textColor ?? theme.messageAiText
75+
const resolvedTextColor = textColor ?? theme.aiText
7676

7777
// Get elapsed time from timer for streaming AI messages
7878
const elapsedSeconds = timer.elapsedSeconds
@@ -140,8 +140,8 @@ export const MessageBlock = ({
140140
codeBlockWidth: Math.max(10, availableWidth - 12 - indentationOffset),
141141
palette: {
142142
...markdownPalette,
143-
inlineCodeFg: theme.agentText,
144-
codeTextFg: theme.agentText,
143+
inlineCodeFg: theme.agentContent,
144+
codeTextFg: theme.agentContent,
145145
},
146146
}
147147
}
@@ -235,7 +235,7 @@ export const MessageBlock = ({
235235
? null
236236
: (
237237
<text
238-
fg={theme.agentText}
238+
fg={theme.agentContent}
239239
style={{ wrapMode: 'word' }}
240240
attributes={
241241
theme.messageTextAttributes && theme.messageTextAttributes !== 0
@@ -334,7 +334,7 @@ export const MessageBlock = ({
334334
: agentBlock.status === 'complete'
335335
? 'completed'
336336
: agentBlock.status
337-
const statusColor = isActive ? theme.statusAccent : theme.agentResponseCount
337+
const statusColor = isActive ? theme.primary : theme.muted
338338
const statusIndicator = isActive ? '●' : '✓'
339339

340340
return (
@@ -399,7 +399,7 @@ export const MessageBlock = ({
399399
) => {
400400
const identifier = formatIdentifier(agent)
401401
return (
402-
<text key={`agent-${idx}`} style={{ wrapMode: 'word', fg: theme.agentText }}>
402+
<text key={`agent-${idx}`} style={{ wrapMode: 'word', fg: theme.agentContent }}>
403403
{` • ${identifier}`}
404404
</text>
405405
)
@@ -481,7 +481,7 @@ export const MessageBlock = ({
481481
typeof (nestedBlock as any).color === 'string'
482482
? ((nestedBlock as any).color as string)
483483
: undefined
484-
const nestedTextColor = explicitColor ?? theme.agentText
484+
const nestedTextColor = explicitColor ?? theme.agentContent
485485
nodes.push(
486486
<text
487487
key={renderKey}
@@ -513,7 +513,7 @@ export const MessageBlock = ({
513513
}}
514514
>
515515
{nestedBlock.render({
516-
textColor: theme.agentText,
516+
textColor: theme.agentContent,
517517
theme,
518518
})}
519519
</box>,
@@ -694,7 +694,7 @@ export const MessageBlock = ({
694694
attributes={TextAttributes.DIM}
695695
style={{
696696
wrapMode: 'none',
697-
fg: theme.statusSecondary,
697+
fg: theme.secondary,
698698
marginTop: 0,
699699
marginBottom: 0,
700700
alignSelf: 'flex-start',
@@ -709,7 +709,7 @@ export const MessageBlock = ({
709709
attributes={TextAttributes.DIM}
710710
style={{
711711
wrapMode: 'none',
712-
fg: theme.statusSecondary,
712+
fg: theme.secondary,
713713
marginTop: 0,
714714
marginBottom: 0,
715715
alignSelf: 'flex-start',

0 commit comments

Comments
 (0)