@golba98/codexa 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +391 -94
  2. package/package.json +12 -7
  3. package/src/app.tsx +149 -117
  4. package/src/commands/handler.ts +4 -4
  5. package/src/config/buildInfo.ts +2 -2
  6. package/src/config/persistence.ts +10 -0
  7. package/src/config/settings.ts +7 -15
  8. package/src/config/updateCheckCache.ts +19 -1
  9. package/src/core/channel.ts +23 -0
  10. package/src/core/executables/codexExecutable.ts +1 -0
  11. package/src/core/executables/executableResolver.ts +61 -43
  12. package/src/core/launchContext.ts +33 -33
  13. package/src/core/process/processValidation.ts +9 -5
  14. package/src/core/providerLauncher/launcher.ts +59 -42
  15. package/src/core/updateCheck.ts +111 -95
  16. package/src/session/types.ts +2 -1
  17. package/src/ui/ActionRequiredBlock.tsx +5 -5
  18. package/src/ui/ActivityBars.tsx +3 -3
  19. package/src/ui/ActivityIndicator.tsx +6 -6
  20. package/src/ui/AgentBlock.tsx +6 -6
  21. package/src/ui/AnimatedStatusText.tsx +1 -1
  22. package/src/ui/AppShell.tsx +9 -24
  23. package/src/ui/AttachmentImportPanel.tsx +8 -8
  24. package/src/ui/AuthPanel.tsx +20 -20
  25. package/src/ui/BottomComposer.tsx +117 -78
  26. package/src/ui/DashCard.tsx +3 -3
  27. package/src/ui/Markdown.tsx +17 -17
  28. package/src/ui/ModelPickerScreen.tsx +10 -10
  29. package/src/ui/ModelReasoningPicker.tsx +14 -14
  30. package/src/ui/Panel.tsx +3 -3
  31. package/src/ui/PlanActionPicker.tsx +6 -6
  32. package/src/ui/PlanReviewPanel.tsx +8 -8
  33. package/src/ui/ProviderPicker.tsx +18 -18
  34. package/src/ui/RunFooter.tsx +3 -3
  35. package/src/ui/SelectionPanel.tsx +4 -4
  36. package/src/ui/SettingsPanel.tsx +8 -8
  37. package/src/ui/Spinner.tsx +1 -1
  38. package/src/ui/TextEntryPanel.tsx +10 -10
  39. package/src/ui/ThinkingBlock.tsx +8 -8
  40. package/src/ui/Timeline.tsx +21 -17
  41. package/src/ui/TopHeader.tsx +421 -293
  42. package/src/ui/TurnGroup.tsx +31 -31
  43. package/src/ui/UpdateAvailableCard.tsx +41 -0
  44. package/src/ui/UpdatePromptPanel.tsx +203 -0
  45. package/src/ui/focus.ts +3 -0
  46. package/src/ui/layout.ts +1 -1
  47. package/src/ui/logoVariants.ts +107 -0
  48. package/src/ui/modeDisplay.ts +12 -12
  49. package/src/ui/runtimeDisplay.ts +100 -0
  50. package/src/ui/theme.tsx +274 -395
  51. package/src/ui/timelineMeasure.ts +29 -19
  52. package/scripts/audit-codexa-capabilities.mjs +0 -466
  53. package/scripts/gen-build-info.mjs +0 -33
  54. package/scripts/smoke-terminal-bench.mjs +0 -35
  55. package/src/appRenderStability.test.ts +0 -131
  56. package/src/commands/handler.test.ts +0 -655
  57. package/src/config/launchArgs.test.ts +0 -189
  58. package/src/config/layeredConfig.test.ts +0 -143
  59. package/src/config/persistence.test.ts +0 -114
  60. package/src/config/runtimeConfig.test.ts +0 -218
  61. package/src/config/settings.test.ts +0 -155
  62. package/src/config/trustStore.test.ts +0 -29
  63. package/src/core/attachments.test.ts +0 -155
  64. package/src/core/auth/codexAuth.test.ts +0 -68
  65. package/src/core/cleanupFastFail.test.ts +0 -76
  66. package/src/core/codexExecArgs.test.ts +0 -195
  67. package/src/core/codexLaunch.test.ts +0 -205
  68. package/src/core/codexPrompt.test.ts +0 -252
  69. package/src/core/executables/codexExecutable.test.ts +0 -212
  70. package/src/core/executables/executableResolver.test.ts +0 -129
  71. package/src/core/executables/geminiExecutable.test.ts +0 -116
  72. package/src/core/executables/pathSanityScan.test.ts +0 -47
  73. package/src/core/githubDiagnostics.test.ts +0 -92
  74. package/src/core/hollowResponseFormat.test.ts +0 -58
  75. package/src/core/launchContext.test.ts +0 -157
  76. package/src/core/models/codexCapabilities.test.ts +0 -45
  77. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  78. package/src/core/models/modelSpecs.test.ts +0 -283
  79. package/src/core/perf/renderDebug.test.ts +0 -230
  80. package/src/core/planStorage.test.ts +0 -143
  81. package/src/core/process/CommandRunner.test.ts +0 -105
  82. package/src/core/projectInstructions.test.ts +0 -50
  83. package/src/core/providerLauncher/launcher.test.ts +0 -238
  84. package/src/core/providerLauncher/registry.test.ts +0 -324
  85. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  86. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  87. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  88. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  89. package/src/core/providerRuntime/gemini.test.ts +0 -437
  90. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  91. package/src/core/providerRuntime/local.test.ts +0 -787
  92. package/src/core/providerRuntime/registry.test.ts +0 -233
  93. package/src/core/providers/codexJsonStream.test.ts +0 -148
  94. package/src/core/providers/codexSubprocess.test.ts +0 -68
  95. package/src/core/providers/codexTranscript.test.ts +0 -284
  96. package/src/core/terminal/startupClear.test.ts +0 -55
  97. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  98. package/src/core/terminal/terminalControl.test.ts +0 -75
  99. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  100. package/src/core/terminal/terminalSelection.test.ts +0 -42
  101. package/src/core/terminal/terminalTitle.test.ts +0 -328
  102. package/src/core/updateCheck.test.ts +0 -194
  103. package/src/core/workspaceActivity.test.ts +0 -163
  104. package/src/core/workspaceGuard.test.ts +0 -151
  105. package/src/core/workspaceRoot.test.ts +0 -23
  106. package/src/exec.test.ts +0 -13
  107. package/src/headless/execArgs.test.ts +0 -147
  108. package/src/headless/execRunner.test.ts +0 -436
  109. package/src/index.test.tsx +0 -620
  110. package/src/session/appSession.test.ts +0 -897
  111. package/src/session/chatLifecycle.test.ts +0 -64
  112. package/src/session/liveRenderScheduler.test.ts +0 -201
  113. package/src/session/planFlow.test.ts +0 -103
  114. package/src/session/planTranscript.test.ts +0 -65
  115. package/src/session/promptRunSchedule.test.ts +0 -36
  116. package/src/ui/ActivityIndicator.test.tsx +0 -58
  117. package/src/ui/AgentBlock.test.ts +0 -6
  118. package/src/ui/AnimatedStatusText.test.ts +0 -16
  119. package/src/ui/AppShell.test.tsx +0 -1776
  120. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  121. package/src/ui/BottomComposer.test.ts +0 -674
  122. package/src/ui/CodexLogo.tsx +0 -55
  123. package/src/ui/Markdown.test.ts +0 -157
  124. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  125. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  126. package/src/ui/ModelPickerState.test.tsx +0 -151
  127. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  128. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  129. package/src/ui/PromptCardBorder.test.tsx +0 -161
  130. package/src/ui/ProviderPicker.test.tsx +0 -289
  131. package/src/ui/ProviderShortcut.test.tsx +0 -143
  132. package/src/ui/SettingsPanel.test.tsx +0 -233
  133. package/src/ui/Timeline.test.ts +0 -2067
  134. package/src/ui/TimelineNavigation.test.tsx +0 -201
  135. package/src/ui/TopHeader.test.tsx +0 -254
  136. package/src/ui/TurnGroup.test.tsx +0 -365
  137. package/src/ui/busyStatusAnimation.test.ts +0 -30
  138. package/src/ui/commandNormalize.test.ts +0 -142
  139. package/src/ui/diffRenderer.test.ts +0 -102
  140. package/src/ui/focusFlow.test.tsx +0 -1098
  141. package/src/ui/inputBuffer.test.ts +0 -151
  142. package/src/ui/layout.test.ts +0 -146
  143. package/src/ui/modeDisplay.test.ts +0 -42
  144. package/src/ui/runActivityView.test.ts +0 -89
  145. package/src/ui/runLifecycleView.test.tsx +0 -237
  146. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  147. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  148. package/src/ui/textLayout.test.ts +0 -18
  149. package/src/ui/themeFlow.test.ts +0 -53
  150. package/src/ui/timelineMeasureCache.test.ts +0 -986
@@ -49,10 +49,10 @@ export function AuthPanel({
49
49
  const authStateLabel = getAuthStateLabel(authStatus.state);
50
50
  const authStateColor =
51
51
  authStatus.state === "authenticated"
52
- ? theme.SUCCESS
52
+ ? theme.success
53
53
  : authStatus.state === "unauthenticated"
54
- ? theme.ERROR
55
- : theme.WARNING;
54
+ ? theme.error
55
+ : theme.warning;
56
56
  const checkedAtLabel = authStatus.checkedAt > 0
57
57
  ? new Date(authStatus.checkedAt).toLocaleTimeString()
58
58
  : "not checked yet";
@@ -60,52 +60,52 @@ export function AuthPanel({
60
60
  return (
61
61
  <Box
62
62
  borderStyle="round"
63
- borderColor={theme.BORDER_ACTIVE}
63
+ borderColor={theme.borderFocused}
64
64
  flexDirection="column"
65
65
  paddingX={2}
66
66
  paddingY={1}
67
67
  marginTop={1}
68
68
  width="100%"
69
69
  >
70
- <Text color={theme.ACCENT} bold>
70
+ <Text color={theme.accent} bold>
71
71
  Auth and subscription guidance
72
72
  </Text>
73
- <Text color={theme.MUTED}>Current backend: {provider.label}</Text>
74
- <Text color={theme.MUTED}>Current preference: {formatAuthPreferenceLabel(authPreference)}</Text>
75
- <Text color={theme.INFO}>Backend auth: {provider.authLabel}</Text>
73
+ <Text color={theme.textMuted}>Current backend: {provider.label}</Text>
74
+ <Text color={theme.textMuted}>Current preference: {formatAuthPreferenceLabel(authPreference)}</Text>
75
+ <Text color={theme.info}>Backend auth: {provider.authLabel}</Text>
76
76
  <Text color={authStateColor}>Runtime auth state: {authStateLabel}</Text>
77
- <Text color={theme.DIM}>Last checked: {checkedAtLabel}</Text>
78
- <Text color={theme.DIM}>Probe summary: {authStatus.rawSummary || "No probe output yet"}</Text>
79
- <Text color={theme.TEXT}>
77
+ <Text color={theme.textDim}>Last checked: {checkedAtLabel}</Text>
78
+ <Text color={theme.textDim}>Probe summary: {authStatus.rawSummary || "No probe output yet"}</Text>
79
+ <Text color={theme.text}>
80
80
  This UI securely bridges to the Codexa neural network. It does not collect or store your ChatGPT credentials.
81
81
  </Text>
82
- <Text color={theme.TEXT}>{provider.statusMessage}</Text>
82
+ <Text color={theme.text}>{provider.statusMessage}</Text>
83
83
  <Box flexDirection="column" marginTop={1}>
84
- <Text color={theme.INFO}>Commands:</Text>
85
- <Text color={theme.TEXT}> /login guided ChatGPT sign-in steps</Text>
86
- <Text color={theme.TEXT}> /logout guided sign-out steps</Text>
87
- <Text color={theme.TEXT}> /auth status refresh Codexa authentication</Text>
84
+ <Text color={theme.info}>Commands:</Text>
85
+ <Text color={theme.text}> /login guided ChatGPT sign-in steps</Text>
86
+ <Text color={theme.text}> /logout guided sign-out steps</Text>
87
+ <Text color={theme.text}> /auth status refresh Codexa authentication</Text>
88
88
  </Box>
89
89
  <Box flexDirection="column" marginTop={1}>
90
90
  {AUTH_PREFERENCES.map((item, index) => (
91
- <Text key={item.id} color={item.id === authPreference ? theme.SUCCESS : theme.TEXT}>
91
+ <Text key={item.id} color={item.id === authPreference ? theme.success : theme.text}>
92
92
  {index + 1}. {item.label} {item.id === authPreference ? "✓" : ""}
93
93
  </Text>
94
94
  ))}
95
95
  </Box>
96
96
  <Box marginTop={1}>
97
- <Text color={theme.DIM}>
97
+ <Text color={theme.textDim}>
98
98
  Press 1-{AUTH_PREFERENCES.length} to change preference, R to refresh status, Esc to close.
99
99
  </Text>
100
100
  </Box>
101
101
  {authStatusBusy && (
102
102
  <Box marginTop={1}>
103
- <Text color={theme.WARNING}>Checking auth status...</Text>
103
+ <Text color={theme.warning}>Checking auth status...</Text>
104
104
  </Box>
105
105
  )}
106
106
  {authStatus.recommendedAction && (
107
107
  <Box marginTop={1}>
108
- <Text color={theme.DIM}>Next step: {authStatus.recommendedAction}</Text>
108
+ <Text color={theme.textDim}>Next step: {authStatus.recommendedAction}</Text>
109
109
  </Box>
110
110
  )}
111
111
  </Box>
@@ -98,10 +98,12 @@ export function getTokenBarDisplay(tokensUsed: number, modelSpec: ModelSpec) {
98
98
  interface BottomComposerProps {
99
99
  layout: Layout;
100
100
  uiState: UIState;
101
- themeName?: string;
102
- mode?: string;
103
- model?: string;
104
- reasoningLevel?: string;
101
+ themeName?: string;
102
+ mode?: string;
103
+ model?: string;
104
+ footerModelDisplay?: string;
105
+ reasoningLevel?: string;
106
+ contextDisplay?: string;
105
107
  planMode?: boolean;
106
108
  showBusyLoader?: boolean;
107
109
  tokensUsed?: number;
@@ -236,7 +238,7 @@ export function measureBottomComposerRows({
236
238
 
237
239
  // ALWAYS reserve 1 row for the status line to prevent height shifting between idle and busy states.
238
240
  const statusLineReservedRows = 1;
239
- const showMetadata = layout.mode !== "micro" && layout.rows > 24;
241
+ const showMetadata = layout.mode !== "micro";
240
242
  const bottomPadding = layout.mode === "micro" || layout.rows <= 24 ? 0 : 1;
241
243
  const footerGapRows = getComposerToFooterGapRows(layout);
242
244
 
@@ -338,13 +340,53 @@ function getPlaceholder(persona: ComposerPersona): string {
338
340
 
339
341
  // ─── Component ────────────────────────────────────────────────────────────────
340
342
 
343
+ function renderFooterRuntime(displayStr: string, theme: any) {
344
+ // e.g. "Claude Code CLI / Sonnet 4.6 (Low)"
345
+ const slashIndex = displayStr.indexOf("/");
346
+ if (slashIndex === -1) {
347
+ return <Text color={theme.model} wrap="truncate">{displayStr}</Text>;
348
+ }
349
+ const providerPart = displayStr.substring(0, slashIndex).trim();
350
+ let remaining = displayStr.substring(slashIndex + 1).trim();
351
+
352
+ const parenIndex = remaining.indexOf("(");
353
+ if (parenIndex === -1) {
354
+ return (
355
+ <Box flexDirection="row" overflow="hidden">
356
+ <Text color={theme.provider}>{providerPart}</Text>
357
+ <Text color={theme.textMuted}>{" / "}</Text>
358
+ <Text color={theme.model}>{remaining}</Text>
359
+ </Box>
360
+ );
361
+ }
362
+
363
+ const modelPart = remaining.substring(0, parenIndex).trim();
364
+ let reasoningPart = remaining.substring(parenIndex + 1).trim();
365
+ if (reasoningPart.endsWith(")")) {
366
+ reasoningPart = reasoningPart.substring(0, reasoningPart.length - 1).trim();
367
+ }
368
+
369
+ return (
370
+ <Box flexDirection="row" overflow="hidden">
371
+ <Text color={theme.provider}>{providerPart}</Text>
372
+ <Text color={theme.textMuted}>{" / "}</Text>
373
+ <Text color={theme.model}>{modelPart}</Text>
374
+ <Text color={theme.textMuted}>{" ("}</Text>
375
+ <Text color={theme.accentMuted}>{reasoningPart}</Text>
376
+ <Text color={theme.textMuted}>{")"}</Text>
377
+ </Box>
378
+ );
379
+ }
380
+
341
381
  export function BottomComposer({
342
382
  layout,
343
383
  uiState,
344
- themeName = "purple",
345
- mode = "",
346
- model = "",
347
- reasoningLevel = "",
384
+ themeName = "purple",
385
+ mode = "",
386
+ model = "",
387
+ footerModelDisplay,
388
+ reasoningLevel = "",
389
+ contextDisplay,
348
390
  planMode = false,
349
391
  showBusyLoader = true,
350
392
  tokensUsed = 0,
@@ -807,16 +849,12 @@ export function BottomComposer({
807
849
  }
808
850
  }, { isActive: isFocused });
809
851
 
810
- const modeDisplay = getModeDisplaySpec(mode, theme);
811
- const tokenDisplay = getTokenBarDisplay(tokensUsed, modelSpec);
812
- const tokenColor = tokenDisplay.percentage === null ? theme.DIM
813
- : tokenDisplay.percentage >= 90 ? theme.ERROR
814
- : tokenDisplay.percentage >= 70 ? theme.WARNING
815
- : theme.SUCCESS;
816
- const reasoningSuffix = reasoningLevel ? ` (${reasoningLevel})` : "";
852
+ const tokenDisplay = getTokenBarDisplay(tokensUsed, modelSpec);
853
+ const reasoningSuffix = reasoningLevel ? ` (${reasoningLevel})` : "";
854
+ const footerRuntimeDisplay = footerModelDisplay ?? `${model}${reasoningSuffix}`;
817
855
  const isAnswerMode = persona === "answer";
818
856
  const showBusyFooter = shouldRenderBusyFooter(layout, uiState);
819
- const promptPrefixColor = inputLocked ? theme.DIM : theme.TEXT;
857
+ const promptPrefixColor = inputLocked ? theme.textDim : theme.text;
820
858
  const lockedInputText = promptViewport.visibleRows[0]?.text ?? " ";
821
859
 
822
860
  // The prompt line is shared between bordered and non-bordered layouts.
@@ -826,12 +864,12 @@ export function BottomComposer({
826
864
  <Box flexDirection="column" flexGrow={1}>
827
865
  {value.length === 0 && !inputLocked ? (
828
866
  <Box width="100%" overflow="hidden">
829
- <Text backgroundColor={cursorVisible ? theme.TEXT : undefined} color={cursorVisible ? theme.PANEL : undefined}>{" "}</Text>
830
- <Text color={theme.DIM}>{placeholderText}</Text>
867
+ <Text backgroundColor={cursorVisible ? theme.text : undefined} color={cursorVisible ? theme.surface : undefined}>{" "}</Text>
868
+ <Text color={theme.textDim}>{placeholderText}</Text>
831
869
  </Box>
832
870
  ) : inputLocked ? (
833
871
  <Box key="busy-locked-input" width="100%" overflow="hidden">
834
- <Text color={theme.DIM}>{lockedInputText || " "}</Text>
872
+ <Text color={theme.textDim}>{lockedInputText || " "}</Text>
835
873
  </Box>
836
874
  ) : (
837
875
  promptViewport.visibleRows.map((row, index) => {
@@ -845,14 +883,14 @@ export function BottomComposer({
845
883
  <Box key={`${row.start}-${row.end}-${index}`} width="100%" overflow="hidden">
846
884
  {isCursorRow && segments ? (
847
885
  <>
848
- <Text color={theme.TEXT}>{segments.before}</Text>
849
- <Text backgroundColor={cursorVisible ? theme.TEXT : undefined} color={cursorVisible ? theme.PANEL : undefined}>
886
+ <Text color={theme.text}>{segments.before}</Text>
887
+ <Text backgroundColor={cursorVisible ? theme.text : undefined} color={cursorVisible ? theme.surface : undefined}>
850
888
  {segments.current || " "}
851
889
  </Text>
852
- <Text color={theme.TEXT}>{segments.after}</Text>
890
+ <Text color={theme.text}>{segments.after}</Text>
853
891
  </>
854
892
  ) : (
855
- <Text color={theme.TEXT}>{row.text || " "}</Text>
893
+ <Text color={theme.text}>{row.text || " "}</Text>
856
894
  )}
857
895
  </Box>
858
896
  );
@@ -876,27 +914,57 @@ export function BottomComposer({
876
914
  paddingX={1}
877
915
  paddingY={0}
878
916
  borderStyle="round"
879
- borderColor={theme.WARNING}
917
+ borderColor={theme.warning}
880
918
  >
881
919
  {promptLine}
882
920
  </Box>
883
- ) : (
884
- // Normal mode: clean prompt in rounded border
885
- <Box
886
- flexDirection="column"
921
+ ) : (
922
+ // Normal mode: clean prompt in rounded border
923
+ <Box
924
+ flexDirection="column"
887
925
  width="100%"
888
926
  paddingX={1}
889
927
  paddingY={0}
890
928
  borderStyle="round"
891
- borderColor={theme.BORDER_SUBTLE}
929
+ borderColor={theme.border}
892
930
  >
893
- {promptLine}
894
- </Box>
895
- )}
896
-
897
- {commandSuggestionState.reserveSuggestionRow && (
898
- <Box paddingLeft={1} marginTop={0} width="100%" overflow="hidden">
899
- <Text color={theme.DIM} wrap="truncate">{suggestionText || " "}</Text>
931
+ {promptLine}
932
+ </Box>
933
+ )}
934
+
935
+ {layoutMode !== "micro" && (
936
+ <Box paddingLeft={1} paddingRight={1} marginTop={0} width="100%" justifyContent="space-between">
937
+ <Box flexGrow={1} flexShrink={1} overflow="hidden">
938
+ {renderFooterRuntime(footerRuntimeDisplay, theme)}
939
+ </Box>
940
+ <Box flexShrink={0}>
941
+ {contextDisplay ? (
942
+ <Box flexDirection="row">
943
+ <Text color={theme.textMuted}>Context: </Text>
944
+ <Text color={theme.context}>{contextDisplay}</Text>
945
+ </Box>
946
+ ) : tokenDisplay.hasKnownLimit ? (
947
+ <Box flexDirection="row">
948
+ <Text color={theme.textMuted}>Context: </Text>
949
+ <Text color={theme.context}>{tokenDisplay.usedText}</Text>
950
+ <Text color={theme.textDim}>
951
+ {" / "}{tokenDisplay.limitText}
952
+ {tokenDisplay.percentage !== null ? ` · ${tokenDisplay.isEstimatedLimit ? "~" : ""}${tokenDisplay.percentage}%` : ""}
953
+ </Text>
954
+ </Box>
955
+ ) : (
956
+ <Box flexDirection="row">
957
+ <Text color={theme.textMuted}>Context: </Text>
958
+ <Text color={theme.textDim}>Unknown</Text>
959
+ </Box>
960
+ )}
961
+ </Box>
962
+ </Box>
963
+ )}
964
+
965
+ {commandSuggestionState.reserveSuggestionRow && (
966
+ <Box paddingLeft={1} marginTop={0} width="100%" overflow="hidden">
967
+ <Text color={theme.textDim} wrap="truncate">{suggestionText || " "}</Text>
900
968
  </Box>
901
969
  )}
902
970
 
@@ -911,7 +979,7 @@ export function BottomComposer({
911
979
  <Box flexShrink={1} flexGrow={1} overflow="hidden" flexDirection="row">
912
980
  {!!getExternalCliLabel(activeProviderId ?? "") && uiState.kind === "THINKING" && (
913
981
  <>
914
- <Spinner color={theme.ACCENT} />
982
+ <Spinner color={theme.accent} />
915
983
  <Text>{" "}</Text>
916
984
  </>
917
985
  )}
@@ -923,52 +991,21 @@ export function BottomComposer({
923
991
  </Box>
924
992
  {inputLocked && (
925
993
  <Box flexShrink={0}>
926
- <Text color={theme.DIM}>Esc cancel Ctrl+C quit</Text>
994
+ <Text color={theme.textDim}>Esc cancel Ctrl+C quit</Text>
927
995
  </Box>
928
996
  )}
929
997
  {!inputLocked && selectionProfile && (
930
998
  <Box flexShrink={0}>
931
- <Text color={theme.DIM}>{selectionProfile.shortHint}</Text>
999
+ <Text color={theme.textDim}>{selectionProfile.shortHint}</Text>
932
1000
  </Box>
933
1001
  )}
934
1002
  </>
935
1003
  )}
936
1004
  </Box>
937
1005
 
938
- {layoutMode !== "micro" && !crampedViewport && (
939
- <Box paddingLeft={1} paddingRight={1} marginTop={0} width="100%" justifyContent="space-between">
940
- <Box flexGrow={1} flexShrink={1} overflow="hidden">
941
- <Text
942
- color={modeDisplay.ringColor}
943
- backgroundColor={modeDisplay.ringFill}
944
- bold={modeDisplay.ringBold}
945
- >
946
- {modeDisplay.ringGlyph}
947
- </Text>
948
- <Text color={theme.DIM}>{" "}</Text>
949
- <Text color={modeDisplay.labelColor} bold={modeDisplay.labelBold}>{modeDisplay.label}</Text>
950
- <Text color={theme.DIM}>{" "}{model}{reasoningSuffix}{" Ctrl+O Ctrl+Alt+P"}</Text>
951
- {planMode && <Text color={theme.ACCENT}>{" Plan"}</Text>}
952
- </Box>
953
- <Box flexShrink={0}>
954
- {tokenDisplay.hasKnownLimit ? (
955
- <>
956
- <Text color={theme.DIM}>Context: </Text>
957
- <Text color={theme.TEXT}>{tokenDisplay.usedText}</Text>
958
- <Text color={theme.DIM}>
959
- {" / "}{tokenDisplay.limitText}
960
- {tokenDisplay.percentage !== null ? ` · ${tokenDisplay.isEstimatedLimit ? "~" : ""}${tokenDisplay.percentage}%` : ""}
961
- </Text>
962
- </>
963
- ) : (
964
- <Text color={theme.DIM}>Context: Unknown</Text>
965
- )}
966
- </Box>
967
- </Box>
968
- )}
969
- </Box>
970
- );
971
- }
1006
+ </Box>
1007
+ );
1008
+ }
972
1009
 
973
1010
  // Helper to extract the relevant uiState kind for comparison
974
1011
  function getUiStateKey(uiState: UIState): string {
@@ -1000,10 +1037,12 @@ export const MemoizedBottomComposer = memo(BottomComposer, (prev, next) => {
1000
1037
  if (prev.value !== next.value) return false;
1001
1038
  if (prev.cursor !== next.cursor) return false;
1002
1039
 
1003
- // Re-render if display props change
1004
- if (prev.mode !== next.mode) return false;
1005
- if (prev.model !== next.model) return false;
1006
- if (prev.reasoningLevel !== next.reasoningLevel) return false;
1040
+ // Re-render if display props change
1041
+ if (prev.mode !== next.mode) return false;
1042
+ if (prev.model !== next.model) return false;
1043
+ if (prev.footerModelDisplay !== next.footerModelDisplay) return false;
1044
+ if (prev.reasoningLevel !== next.reasoningLevel) return false;
1045
+ if (prev.contextDisplay !== next.contextDisplay) return false;
1007
1046
  if (prev.planMode !== next.planMode) return false;
1008
1047
  if (prev.showBusyLoader !== next.showBusyLoader) return false;
1009
1048
  if (prev.tokensUsed !== next.tokensUsed) return false;
@@ -24,9 +24,9 @@ export function DashCard({
24
24
  children,
25
25
  }: DashCardProps) {
26
26
  const theme = useTheme();
27
- const border = borderColor ?? theme.BORDER_SUBTLE;
28
- const tColor = titleColor ?? theme.MUTED;
29
- const bColor = badgeColor ?? theme.DIM;
27
+ const border = borderColor ?? theme.border;
28
+ const tColor = titleColor ?? theme.textMuted;
29
+ const bColor = badgeColor ?? theme.textDim;
30
30
 
31
31
  // Account for AppShell gutter (-1) and Timeline paddingX={1} (-2)
32
32
  const w = cols - 3;
@@ -131,7 +131,7 @@ function InlineText({ parts, color }: { parts: InlinePart[]; color: string }) {
131
131
  return (
132
132
  <Text color={color} wrap="wrap">
133
133
  {parts.map((part, index) => {
134
- if (part.kind === "code") return <Text key={index} color={theme.INFO}>{part.text}</Text>;
134
+ if (part.kind === "code") return <Text key={index} color={theme.info}>{part.text}</Text>;
135
135
  if (part.kind === "bold") return <Text key={index} bold>{part.text}</Text>;
136
136
  return <Text key={index}>{part.text}</Text>;
137
137
  })}
@@ -148,7 +148,7 @@ function renderTreeLabel(label: string, theme: ReturnType<typeof useTheme>) {
148
148
 
149
149
  if (isDirectory) {
150
150
  return (
151
- <Text color={theme.TEXT} bold>
151
+ <Text color={theme.text} bold>
152
152
  {base}
153
153
  {slash}
154
154
  </Text>
@@ -156,9 +156,9 @@ function renderTreeLabel(label: string, theme: ReturnType<typeof useTheme>) {
156
156
  }
157
157
 
158
158
  return (
159
- <Text color={theme.TEXT}>
159
+ <Text color={theme.text}>
160
160
  {base.slice(0, lastDot)}
161
- <Text color={theme.DIM}>{base.slice(lastDot)}</Text>
161
+ <Text color={theme.textDim}>{base.slice(lastDot)}</Text>
162
162
  </Text>
163
163
  );
164
164
  }
@@ -171,7 +171,7 @@ function TreeLine({ line }: { line: string }) {
171
171
 
172
172
  return (
173
173
  <Box width="100%">
174
- <Text color={theme.DIM}>{prefix}</Text>
174
+ <Text color={theme.textDim}>{prefix}</Text>
175
175
  {renderTreeLabel(label, theme)}
176
176
  </Box>
177
177
  );
@@ -184,17 +184,17 @@ function isTreeLine(line: string): boolean {
184
184
  function getDiffColor(kind: DiffRenderLineType, theme: ReturnType<typeof useTheme>): string {
185
185
  switch (kind) {
186
186
  case "add":
187
- return theme.SUCCESS;
187
+ return theme.success;
188
188
  case "remove":
189
- return theme.ERROR;
189
+ return theme.error;
190
190
  case "hunk":
191
- return theme.ACCENT;
191
+ return theme.accent;
192
192
  case "file":
193
193
  case "meta":
194
- return theme.INFO;
194
+ return theme.info;
195
195
  case "context":
196
196
  default:
197
- return theme.MUTED;
197
+ return theme.textMuted;
198
198
  }
199
199
  }
200
200
 
@@ -245,9 +245,9 @@ export function RenderMessage({ segments, width, brightHeadings = false }: { seg
245
245
  ) : (
246
246
  <Box key={lineIndex}>
247
247
  <Box width={3} flexShrink={0} marginRight={1} justifyContent="flex-end">
248
- <Text color={theme.DIM}>{lineIndex + 1}</Text>
248
+ <Text color={theme.textDim}>{lineIndex + 1}</Text>
249
249
  </Box>
250
- <Text color={theme.MUTED} wrap="wrap">{line || " "}</Text>
250
+ <Text color={theme.textMuted} wrap="wrap">{line || " "}</Text>
251
251
  </Box>
252
252
  )
253
253
  ))}
@@ -257,10 +257,10 @@ export function RenderMessage({ segments, width, brightHeadings = false }: { seg
257
257
  }
258
258
 
259
259
  if (segment.type === "header") {
260
- const color = segment.level === 1 ? theme.ACCENT : segment.level === 2 ? theme.TEXT : (brightHeadings ? theme.TEXT : theme.MUTED);
260
+ const color = segment.level === 1 ? theme.accent : segment.level === 2 ? theme.text : (brightHeadings ? theme.text : theme.textMuted);
261
261
  return (
262
262
  <Box key={index} flexDirection="column" marginTop={marginTop}>
263
- {segment.level <= 2 && <Text color={theme.BORDER_SUBTLE}>{"───"}</Text>}
263
+ {segment.level <= 2 && <Text color={theme.border}>{"───"}</Text>}
264
264
  <Box>
265
265
  <Text color={color}>{segment.level <= 2 ? "✧ " : "• "}</Text>
266
266
  <InlineText parts={segment.parts} color={color} />
@@ -274,9 +274,9 @@ export function RenderMessage({ segments, width, brightHeadings = false }: { seg
274
274
  <Box key={index} flexDirection="column" marginTop={marginTop}>
275
275
  {segment.items.map((item, itemIndex) => (
276
276
  <Box key={itemIndex}>
277
- <Text color={theme.ACCENT}>{segment.ordered ? `${item.num}. ` : "• "}</Text>
277
+ <Text color={theme.accent}>{segment.ordered ? `${item.num}. ` : "• "}</Text>
278
278
  <Box flexGrow={1} flexShrink={1}>
279
- <InlineText parts={item.parts} color={theme.TEXT} />
279
+ <InlineText parts={item.parts} color={theme.text} />
280
280
  </Box>
281
281
  </Box>
282
282
  ))}
@@ -295,7 +295,7 @@ export function RenderMessage({ segments, width, brightHeadings = false }: { seg
295
295
  if (parts.length === 1 && parts[0]!.kind === "text" && !parts[0]!.text.trim()) {
296
296
  return null;
297
297
  }
298
- return <InlineText key={lineIndex} parts={parts} color={theme.TEXT} />;
298
+ return <InlineText key={lineIndex} parts={parts} color={theme.text} />;
299
299
  })}
300
300
  </Box>
301
301
  );
@@ -297,28 +297,28 @@ export function ModelPickerScreen({
297
297
  <Box flexDirection="column" width={panelWidth}>
298
298
  <Box
299
299
  borderStyle="round"
300
- borderColor={theme.PROMPT}
300
+ borderColor={theme.prompt}
301
301
  paddingX={1}
302
302
  paddingY={0}
303
303
  width={panelWidth}
304
304
  flexDirection="column"
305
305
  >
306
306
  <Box width="100%" overflow="hidden">
307
- <Text color={theme.ACCENT} bold>{title}</Text>
307
+ <Text color={theme.accent} bold>{title}</Text>
308
308
  </Box>
309
309
  <Box width="100%" overflow="hidden">
310
- <Text color={theme.MUTED}>
310
+ <Text color={theme.textMuted}>
311
311
  {clampVisualText(routeText, innerWidth)}
312
312
  </Text>
313
313
  </Box>
314
314
  <Box width="100%" overflow="hidden">
315
- <Text color={reasoningUnavailable ? theme.DIM : theme.MUTED}>
315
+ <Text color={reasoningUnavailable ? theme.textDim : theme.textMuted}>
316
316
  {clampVisualText(reasoningText, innerWidth)}
317
317
  </Text>
318
318
  </Box>
319
319
  {sourceMarker && (
320
320
  <Box width="100%" overflow="hidden">
321
- <Text color={theme.DIM}>
321
+ <Text color={theme.textDim}>
322
322
  {clampVisualText(sourceMarker, innerWidth)}
323
323
  </Text>
324
324
  </Box>
@@ -326,7 +326,7 @@ export function ModelPickerScreen({
326
326
 
327
327
  <Box flexDirection="column" marginTop={0} width="100%">
328
328
  {models.length === 0 ? (
329
- <Text color={theme.MUTED}>
329
+ <Text color={theme.textMuted}>
330
330
  {isLoading ? "Discovering models from the Codex runtime..." : (emptyMessage ?? "No models available.")}
331
331
  </Text>
332
332
  ) : (
@@ -389,15 +389,15 @@ function ModelPickerRow({
389
389
  return (
390
390
  <Box width="100%" overflow="hidden">
391
391
  <Box width={markerWidth} flexShrink={0}>
392
- <Text color={isHighlighted ? theme.ACCENT : theme.DIM}>{isHighlighted ? ">" : " "}</Text>
392
+ <Text color={isHighlighted ? theme.accent : theme.textDim}>{isHighlighted ? ">" : " "}</Text>
393
393
  </Box>
394
394
  <Box width={nameWidth} flexShrink={0} overflow="hidden">
395
- <Text color={isHighlighted ? theme.TEXT : theme.MUTED} bold={isHighlighted}>
395
+ <Text color={isHighlighted ? theme.text : theme.textMuted} bold={isHighlighted}>
396
396
  {name}
397
397
  </Text>
398
398
  </Box>
399
399
  <Box width={checkWidth} flexShrink={0}>
400
- <Text color={theme.DIM}>{isCurrent ? "✓" : " "}</Text>
400
+ <Text color={theme.textDim}>{isCurrent ? "✓" : " "}</Text>
401
401
  </Box>
402
402
  {pillWidth > 0 && (
403
403
  <>
@@ -405,7 +405,7 @@ function ModelPickerRow({
405
405
  <Text> </Text>
406
406
  </Box>
407
407
  <Box width={pillWidth} flexShrink={0} overflow="hidden">
408
- <Text color={theme.ACCENT} bold>
408
+ <Text color={theme.accent} bold>
409
409
  {pillText}
410
410
  </Text>
411
411
  </Box>
@@ -290,18 +290,18 @@ function LoadingPickerView({
290
290
  <Box flexDirection="column" width="100%">
291
291
  <Box
292
292
  borderStyle="round"
293
- borderColor={theme.BORDER_SUBTLE}
293
+ borderColor={theme.border}
294
294
  paddingX={2}
295
295
  paddingY={0}
296
296
  width="100%"
297
297
  >
298
298
  <Box flexDirection="column" width="100%">
299
299
  <Box>
300
- <Text color={theme.ACCENT} bold>Select model </Text>
301
- <Text color={theme.MUTED}>Esc cancel</Text>
300
+ <Text color={theme.accent} bold>Select model </Text>
301
+ <Text color={theme.textMuted}>Esc cancel</Text>
302
302
  </Box>
303
303
  <Box marginTop={0}>
304
- <Text color={theme.DIM}>
304
+ <Text color={theme.textDim}>
305
305
  {isLoading
306
306
  ? "Discovering models from the Codex runtime…"
307
307
  : "No models available yet."}
@@ -353,18 +353,18 @@ function InteractivePickerView({
353
353
  return (
354
354
  <Box
355
355
  borderStyle="round"
356
- borderColor={theme.BORDER_ACTIVE}
356
+ borderColor={theme.borderFocused}
357
357
  paddingX={2}
358
358
  paddingY={0}
359
359
  width="100%"
360
360
  flexDirection="column"
361
361
  >
362
362
  <Box>
363
- <Text color={theme.ACCENT} bold>Select model </Text>
364
- <Text color={theme.MUTED}>{subtitle}</Text>
363
+ <Text color={theme.accent} bold>Select model </Text>
364
+ <Text color={theme.textMuted}>{subtitle}</Text>
365
365
  </Box>
366
366
  <Box marginTop={0}>
367
- <Text color={theme.DIM}>{reasoningHint}</Text>
367
+ <Text color={theme.textDim}>{reasoningHint}</Text>
368
368
  </Box>
369
369
 
370
370
  <Box
@@ -415,7 +415,7 @@ function ModelRow({
415
415
  theme,
416
416
  }: ModelRowProps) {
417
417
  const cursorGlyph = isHighlighted ? "▸ " : " ";
418
- const nameColor = isHighlighted ? theme.TEXT : theme.MUTED;
418
+ const nameColor = isHighlighted ? theme.text : theme.textMuted;
419
419
  const commitMark = isCommitted ? " ✓" : "";
420
420
  const selectedIndex = availableLevels.findIndex((level) => level.id === selectedReasoning);
421
421
  const name = model.label === model.model ? model.model : `${model.label} (${model.model})`;
@@ -423,10 +423,10 @@ function ModelRow({
423
423
  const bars = availableLevels.map((level, i) => {
424
424
  const isActive = i === selectedIndex;
425
425
  const color = !interactive
426
- ? theme.DIM
426
+ ? theme.textDim
427
427
  : isActive
428
- ? isHighlighted ? theme.ACCENT : theme.TEXT
429
- : theme.DIM;
428
+ ? isHighlighted ? theme.accent : theme.text
429
+ : theme.textDim;
430
430
 
431
431
  return (
432
432
  <Text key={level.id} color={color} bold={isActive && isHighlighted && interactive}>
@@ -438,7 +438,7 @@ function ModelRow({
438
438
  return (
439
439
  <Box flexDirection="row" width="100%">
440
440
  <Box width={3} flexShrink={0}>
441
- <Text color={isHighlighted ? theme.ACCENT : theme.DIM}>{cursorGlyph}</Text>
441
+ <Text color={isHighlighted ? theme.accent : theme.textDim}>{cursorGlyph}</Text>
442
442
  </Box>
443
443
  <Box flexGrow={1} flexDirection="row" paddingRight={1}>
444
444
  <Box flexShrink={1}>
@@ -447,7 +447,7 @@ function ModelRow({
447
447
  </Text>
448
448
  </Box>
449
449
  <Box flexShrink={0} paddingLeft={1}>
450
- <Text color={theme.DIM}>{commitMark}</Text>
450
+ <Text color={theme.textDim}>{commitMark}</Text>
451
451
  </Box>
452
452
  </Box>
453
453
  <Box flexDirection="row" gap={1} flexShrink={0}>