@golba98/codexa 1.0.2 → 1.0.4
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.
- package/README.md +396 -100
- package/bin/codexa.js +62 -144
- package/package.json +14 -8
- package/src/app.tsx +596 -306
- package/src/commands/handler.ts +6 -6
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/persistence.ts +10 -0
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +8 -16
- package/src/config/trustStore.ts +1 -1
- package/src/config/updateCheckCache.ts +19 -1
- package/src/core/README.md +52 -0
- package/src/core/agent/loop.ts +282 -0
- package/src/core/agent/protocol.ts +211 -0
- package/src/core/agent/tools.ts +414 -0
- package/src/core/{codexExecArgs.ts → codex/codexExecArgs.ts} +2 -2
- package/src/core/{codexLaunch.ts → codex/codexLaunch.ts} +3 -3
- package/src/core/{codexPrompt.ts → codex/codexPrompt.ts} +3 -3
- package/src/core/debug/modelStateDebug.ts +34 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +65 -43
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/providerLauncher/registry.ts +30 -14
- package/src/core/providerLauncher/types.ts +11 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +7 -1
- package/src/core/providerRuntime/antigravity.ts +305 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +268 -22
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/contextMetadata.ts +12 -24
- package/src/core/providerRuntime/local.ts +129 -51
- package/src/core/providerRuntime/models.ts +22 -11
- package/src/core/providerRuntime/registry.ts +58 -31
- package/src/core/providerRuntime/types.ts +19 -14
- package/src/core/providers/codexSubprocess.ts +2 -2
- package/src/core/providers/types.ts +1 -1
- package/src/core/{attachments.ts → shared/attachments.ts} +27 -4
- package/src/core/{cleanupFastFail.ts → shared/cleanupFastFail.ts} +1 -1
- package/src/core/{hollowResponseFormat.ts → shared/hollowResponseFormat.ts} +1 -1
- package/src/core/terminal/clearFrameBoundary.ts +814 -0
- package/src/core/terminal/frameLock.ts +109 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/terminalControl.ts +22 -0
- package/src/core/terminal/terminalTitle.ts +16 -102
- package/src/core/version/channel.ts +23 -0
- package/src/core/version/updateCheck.ts +193 -0
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +32 -39
- package/src/core/{planStorage.ts → workspace/planStorage.ts} +2 -2
- package/src/core/{workspaceGuard.ts → workspace/workspaceGuard.ts} +97 -13
- package/src/headless/execRunner.ts +2 -2
- package/src/index.tsx +43 -89
- package/src/session/appSession.ts +10 -7
- package/src/session/chatLifecycle.ts +1 -1
- package/src/session/liveRenderScheduler.ts +1 -1
- package/src/session/types.ts +3 -2
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +670 -719
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +158 -118
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +222 -42
- package/src/ui/ModelReasoningPicker.tsx +15 -15
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +9 -9
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +8 -4
- package/src/ui/SettingsPanel.tsx +9 -9
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +11 -11
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +1625 -1472
- package/src/ui/TopHeader.tsx +437 -293
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +33 -33
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +197 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +299 -25
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +103 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +112 -0
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +218 -136
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/gen-build-info.mjs +0 -33
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -655
- package/src/config/launchArgs.test.ts +0 -189
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -114
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codex.ts +0 -124
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/startupClear.test.ts +0 -55
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/updateCheck.test.ts +0 -194
- package/src/core/updateCheck.ts +0 -172
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -436
- package/src/index.test.tsx +0 -620
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1776
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -254
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -146
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- package/src/ui/timelineMeasureCache.test.ts +0 -986
- /package/src/core/{inputDebug.ts → debug/inputDebug.ts} +0 -0
- /package/src/core/{clipboard.ts → shared/clipboard.ts} +0 -0
- /package/src/core/{githubDiagnostics.ts → shared/githubDiagnostics.ts} +0 -0
- /package/src/core/{projectInstructions.ts → workspace/projectInstructions.ts} +0 -0
- /package/src/core/{workspaceActivity.ts → workspace/workspaceActivity.ts} +0 -0
- /package/src/core/{workspaceRoot.ts → workspace/workspaceRoot.ts} +0 -0
package/src/ui/Markdown.tsx
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
159
|
+
<Text color={theme.text}>
|
|
160
160
|
{base.slice(0, lastDot)}
|
|
161
|
-
<Text color={theme.
|
|
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.
|
|
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.
|
|
187
|
+
return theme.success;
|
|
188
188
|
case "remove":
|
|
189
|
-
return theme.
|
|
189
|
+
return theme.error;
|
|
190
190
|
case "hunk":
|
|
191
|
-
return theme.
|
|
191
|
+
return theme.accent;
|
|
192
192
|
case "file":
|
|
193
193
|
case "meta":
|
|
194
|
-
return theme.
|
|
194
|
+
return theme.info;
|
|
195
195
|
case "context":
|
|
196
196
|
default:
|
|
197
|
-
return theme.
|
|
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.
|
|
248
|
+
<Text color={theme.textDim}>{lineIndex + 1}</Text>
|
|
249
249
|
</Box>
|
|
250
|
-
<Text color={theme.
|
|
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.
|
|
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.
|
|
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.
|
|
277
|
+
<Text color={theme.accent}>{segment.ordered ? `${item.num}. ` : "• "}</Text>
|
|
278
278
|
<Box flexGrow={1} flexShrink={1}>
|
|
279
|
-
<InlineText parts={item.parts} color={theme.
|
|
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.
|
|
298
|
+
return <InlineText key={lineIndex} parts={parts} color={theme.text} />;
|
|
299
299
|
})}
|
|
300
300
|
</Box>
|
|
301
301
|
);
|
|
@@ -6,18 +6,36 @@ import {
|
|
|
6
6
|
normalizeReasoningForModelCapabilities,
|
|
7
7
|
} from "../core/models/codexModelCapabilities.js";
|
|
8
8
|
import { formatReasoningLabel } from "../config/settings.js";
|
|
9
|
-
import { traceInputDebug } from "../core/inputDebug.js";
|
|
9
|
+
import { traceInputDebug } from "../core/debug/inputDebug.js";
|
|
10
10
|
import { FOCUS_IDS } from "./focus.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
clampVisualText,
|
|
13
|
+
getShellWidth,
|
|
14
|
+
getAvailableRowsForPanel,
|
|
15
|
+
useAppLayoutBudget,
|
|
16
|
+
usePanelAvailableRows,
|
|
17
|
+
type Layout,
|
|
18
|
+
useActivePanelLayout,
|
|
19
|
+
type ActivePanelLayout,
|
|
20
|
+
type PanelLayout,
|
|
21
|
+
usePanelLayout,
|
|
22
|
+
} from "./layout.js";
|
|
23
|
+
import { calculateListWindow } from "./layoutListWindow.js";
|
|
12
24
|
import { useTheme } from "./theme.js";
|
|
13
25
|
import type { GeminiModelSelection } from "../core/providerRuntime/types.js";
|
|
14
26
|
|
|
15
27
|
// ─── Types & helpers ─────────────────────────────────────────────────────────
|
|
16
28
|
|
|
17
29
|
type ModelPickerCloseReason = "escape" | "empty-selection";
|
|
30
|
+
type ModelRenderMode = "full" | "compact" | "windowed";
|
|
18
31
|
|
|
19
32
|
interface ModelPickerScreenProps {
|
|
20
|
-
layout: Layout
|
|
33
|
+
layout: Layout & {
|
|
34
|
+
contentWidth?: number;
|
|
35
|
+
};
|
|
36
|
+
availableRows?: number;
|
|
37
|
+
activePanelLayout?: ActivePanelLayout;
|
|
38
|
+
panelLayout?: PanelLayout;
|
|
21
39
|
models: readonly CodexModelCapability[];
|
|
22
40
|
currentModel: string;
|
|
23
41
|
currentReasoning: string;
|
|
@@ -50,10 +68,25 @@ function getReasoningLevels(model: CodexModelCapability | undefined): readonly R
|
|
|
50
68
|
|
|
51
69
|
function getModelSourceMarker(models: readonly CodexModelCapability[], activeProviderLabel: string): string | null {
|
|
52
70
|
if (activeProviderLabel !== "Claude" || models.length === 0) return null;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
const raw = models[0]?.raw as { source?: string; discoveryKind?: string } | null | undefined;
|
|
72
|
+
const source = raw?.source;
|
|
73
|
+
const sourceLabel = source === "claude-code-package"
|
|
74
|
+
? "installed package metadata"
|
|
75
|
+
: source === "claude-code-command"
|
|
76
|
+
? "Claude Code command"
|
|
77
|
+
: source === "claude-code-cache"
|
|
78
|
+
? "Claude Code cache"
|
|
79
|
+
: source === "claude-code-config" || source === "settings" || source === "config"
|
|
80
|
+
? "Claude settings"
|
|
81
|
+
: "Claude Code";
|
|
82
|
+
if (source === "claude-code-package" || source === "claude-code-command" || source === "claude-code-cache" || source === "claude-code-config" || source === "claude-code" || source === "discovered") {
|
|
83
|
+
return raw?.discoveryKind === "aliases"
|
|
84
|
+
? `Claude Code aliases resolved from ${sourceLabel}`
|
|
85
|
+
: `Claude Code models discovered from ${sourceLabel}`;
|
|
86
|
+
}
|
|
87
|
+
if (source === "settings" || source === "config") return "Claude Code models discovered from Claude settings";
|
|
88
|
+
if (source === "fallback") return "Claude Code discovery failed; using fallback aliases";
|
|
89
|
+
return null;
|
|
57
90
|
}
|
|
58
91
|
|
|
59
92
|
function normalizeDraftReasoning(
|
|
@@ -109,6 +142,9 @@ function describeInputKey(
|
|
|
109
142
|
|
|
110
143
|
export function ModelPickerScreen({
|
|
111
144
|
layout,
|
|
145
|
+
availableRows: propAvailableRows,
|
|
146
|
+
activePanelLayout,
|
|
147
|
+
panelLayout,
|
|
112
148
|
models: baseModels,
|
|
113
149
|
currentModel,
|
|
114
150
|
currentReasoning,
|
|
@@ -279,13 +315,40 @@ export function ModelPickerScreen({
|
|
|
279
315
|
{ isActive: isFocused },
|
|
280
316
|
);
|
|
281
317
|
|
|
318
|
+
const contextLayout = useActivePanelLayout();
|
|
319
|
+
const activeLayout = (activePanelLayout ?? contextLayout) as ActivePanelLayout | undefined;
|
|
320
|
+
|
|
282
321
|
const shellWidth = getShellWidth(layout.cols);
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
322
|
+
const hookPanelLayout = usePanelLayout();
|
|
323
|
+
const hookAvailableRows = usePanelAvailableRows();
|
|
324
|
+
const resolvedPanelLayout = useMemo<PanelLayout>(() => {
|
|
325
|
+
if (panelLayout) return panelLayout;
|
|
326
|
+
if (hookPanelLayout) return hookPanelLayout;
|
|
327
|
+
|
|
328
|
+
const mode = layout.mode;
|
|
329
|
+
const resolvedRows = activeLayout
|
|
330
|
+
? activeLayout.availableRows
|
|
331
|
+
: getAvailableRowsForPanel(layout, propAvailableRows ?? hookAvailableRows);
|
|
332
|
+
const resolvedCols = activeLayout
|
|
333
|
+
? activeLayout.availableCols
|
|
334
|
+
: Math.max(20, shellWidth - 4);
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
mode: (mode === "compact" || mode === "micro" as any) ? "compact" : mode === "expanded" || mode === "max" as any || mode === "wide" as any ? "expanded" : "regular",
|
|
338
|
+
availableRows: resolvedRows,
|
|
339
|
+
availableCols: resolvedCols,
|
|
340
|
+
};
|
|
341
|
+
}, [panelLayout, hookPanelLayout, layout, activeLayout, propAvailableRows, shellWidth, hookAvailableRows]);
|
|
342
|
+
|
|
343
|
+
const panelWidth = activeLayout
|
|
344
|
+
? activeLayout.width
|
|
345
|
+
: Math.max(38, Math.min((layout as any).contentWidth ?? shellWidth, shellWidth - 2));
|
|
346
|
+
|
|
347
|
+
const availableRows = resolvedPanelLayout.availableRows;
|
|
348
|
+
const innerWidth = resolvedPanelLayout.availableCols;
|
|
349
|
+
const help = resolvedPanelLayout.mode === "compact"
|
|
286
350
|
? "↑↓ · ←→ · Enter · Esc"
|
|
287
351
|
: "↑↓ model · ←→ reasoning · Enter select · Esc cancel";
|
|
288
|
-
const title = clampVisualText(`Select model ${help}`, innerWidth);
|
|
289
352
|
const aOrAn = /^[aeiou]/i.test(activeProviderLabel) ? "an" : "a";
|
|
290
353
|
const routeText = routeTextOverride ?? `Choose ${aOrAn} ${activeProviderLabel} model to use inside Codexa.`;
|
|
291
354
|
const reasoningText = reasoningUnavailable
|
|
@@ -293,56 +356,173 @@ export function ModelPickerScreen({
|
|
|
293
356
|
: `Reasoning: ${formatReasoningLabel(draftReasoning)}`;
|
|
294
357
|
const sourceMarker = getModelSourceMarker(models, activeProviderLabel);
|
|
295
358
|
|
|
359
|
+
const appLayoutBudget = useAppLayoutBudget();
|
|
360
|
+
|
|
361
|
+
const activeModelIndex = models.findIndex((m) => m.model === currentModel || m.id === currentModel);
|
|
362
|
+
const hasSourceMarker = !!sourceMarker;
|
|
363
|
+
|
|
364
|
+
// ─── Layout & Windowing ───────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
const windowResult = useMemo(() => {
|
|
367
|
+
const allowFull = appLayoutBudget?.showPanelColumnHeaders ?? true;
|
|
368
|
+
|
|
369
|
+
// Try fitting with full metadata
|
|
370
|
+
const fullChrome = 5 + (hasSourceMarker ? 1 : 0);
|
|
371
|
+
if (allowFull && models.length + fullChrome <= availableRows) {
|
|
372
|
+
return {
|
|
373
|
+
...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: fullChrome, showIndicators: false }),
|
|
374
|
+
mode: "full" as const,
|
|
375
|
+
showRouteText: true,
|
|
376
|
+
showReasoningText: true,
|
|
377
|
+
showSourceMarker: hasSourceMarker,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Try fitting without source marker
|
|
382
|
+
if (allowFull && models.length + 5 <= availableRows) {
|
|
383
|
+
return {
|
|
384
|
+
...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: 5, showIndicators: false }),
|
|
385
|
+
mode: "full" as const,
|
|
386
|
+
showRouteText: true,
|
|
387
|
+
showReasoningText: true,
|
|
388
|
+
showSourceMarker: false,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Try compact with reasoning
|
|
393
|
+
if (models.length + 4 <= availableRows) {
|
|
394
|
+
return {
|
|
395
|
+
...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: 4, showIndicators: false }),
|
|
396
|
+
mode: "compact" as const,
|
|
397
|
+
showRouteText: false,
|
|
398
|
+
showReasoningText: true,
|
|
399
|
+
showSourceMarker: false,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Try minimal compact
|
|
404
|
+
if (models.length + 3 <= availableRows) {
|
|
405
|
+
return {
|
|
406
|
+
...calculateListWindow({ itemCount: models.length, selectedIndex: draftSelectedModel, availableRows, chromeRows: 3, showIndicators: false }),
|
|
407
|
+
mode: "compact" as const,
|
|
408
|
+
showRouteText: false,
|
|
409
|
+
showReasoningText: false,
|
|
410
|
+
showSourceMarker: false,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Windowed mode
|
|
415
|
+
const window = calculateListWindow({
|
|
416
|
+
itemCount: models.length,
|
|
417
|
+
selectedIndex: draftSelectedModel,
|
|
418
|
+
availableRows,
|
|
419
|
+
chromeRows: 3, // Title (1) + Border (2)
|
|
420
|
+
showIndicators: true,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
...window,
|
|
425
|
+
mode: "windowed" as const,
|
|
426
|
+
showRouteText: false,
|
|
427
|
+
showReasoningText: false,
|
|
428
|
+
showSourceMarker: false,
|
|
429
|
+
};
|
|
430
|
+
}, [models.length, draftSelectedModel, availableRows, hasSourceMarker, appLayoutBudget?.showPanelColumnHeaders]);
|
|
431
|
+
|
|
432
|
+
const visibleModels = useMemo(() => {
|
|
433
|
+
return models.slice(windowResult.start, windowResult.end);
|
|
434
|
+
}, [models, windowResult.start, windowResult.end]);
|
|
435
|
+
|
|
436
|
+
const activeModel = models[activeModelIndex];
|
|
437
|
+
const showCurrentLine = windowResult.mode === "windowed" && activeModelIndex >= 0 && (activeModelIndex < windowResult.start || activeModelIndex >= windowResult.end);
|
|
438
|
+
const title = clampVisualText(
|
|
439
|
+
windowResult.mode === "windowed"
|
|
440
|
+
? `Select model · Showing ${windowResult.start + 1}-${windowResult.end} of ${models.length}`
|
|
441
|
+
: `Select model ${help}`,
|
|
442
|
+
innerWidth,
|
|
443
|
+
);
|
|
444
|
+
|
|
296
445
|
return (
|
|
297
446
|
<Box flexDirection="column" width={panelWidth}>
|
|
298
447
|
<Box
|
|
299
448
|
borderStyle="round"
|
|
300
|
-
borderColor={theme.
|
|
449
|
+
borderColor={theme.prompt}
|
|
301
450
|
paddingX={1}
|
|
302
451
|
paddingY={0}
|
|
303
452
|
width={panelWidth}
|
|
304
453
|
flexDirection="column"
|
|
305
454
|
>
|
|
306
455
|
<Box width="100%" overflow="hidden">
|
|
307
|
-
<Text color={theme.
|
|
456
|
+
<Text color={theme.accent} bold>{title}</Text>
|
|
308
457
|
</Box>
|
|
309
|
-
|
|
310
|
-
<
|
|
311
|
-
{
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
458
|
+
{windowResult.showRouteText && (
|
|
459
|
+
<Box width="100%" overflow="hidden">
|
|
460
|
+
<Text color={theme.textMuted}>
|
|
461
|
+
{clampVisualText(routeText, innerWidth)}
|
|
462
|
+
</Text>
|
|
463
|
+
</Box>
|
|
464
|
+
)}
|
|
465
|
+
{windowResult.showReasoningText && (
|
|
466
|
+
<Box width="100%" overflow="hidden">
|
|
467
|
+
<Text color={reasoningUnavailable ? theme.textDim : theme.textMuted}>
|
|
468
|
+
{clampVisualText(reasoningText, innerWidth)}
|
|
469
|
+
</Text>
|
|
470
|
+
</Box>
|
|
471
|
+
)}
|
|
472
|
+
{windowResult.showSourceMarker && sourceMarker && (
|
|
320
473
|
<Box width="100%" overflow="hidden">
|
|
321
|
-
<Text color={theme.
|
|
474
|
+
<Text color={theme.textDim}>
|
|
322
475
|
{clampVisualText(sourceMarker, innerWidth)}
|
|
323
476
|
</Text>
|
|
324
477
|
</Box>
|
|
325
478
|
)}
|
|
326
479
|
|
|
327
|
-
|
|
480
|
+
{showCurrentLine && activeModel && (
|
|
481
|
+
<Box height={1} overflow="hidden">
|
|
482
|
+
<Text color={theme.textMuted} wrap="truncate">Current: <Text color={theme.text} bold>{getModelName(activeModel)}</Text></Text>
|
|
483
|
+
</Box>
|
|
484
|
+
)}
|
|
485
|
+
|
|
486
|
+
{windowResult.showAbove && (
|
|
487
|
+
<Box height={1} overflow="hidden">
|
|
488
|
+
<Text color={theme.accent}>↑ {windowResult.start} more</Text>
|
|
489
|
+
</Box>
|
|
490
|
+
)}
|
|
491
|
+
|
|
492
|
+
<Box
|
|
493
|
+
flexDirection="column"
|
|
494
|
+
marginTop={0}
|
|
495
|
+
width="100%"
|
|
496
|
+
height={models.length > 0 ? visibleModels.length : undefined}
|
|
497
|
+
overflow={models.length > 0 ? "hidden" : undefined}
|
|
498
|
+
>
|
|
328
499
|
{models.length === 0 ? (
|
|
329
|
-
<Text color={theme.
|
|
500
|
+
<Text color={theme.textMuted}>
|
|
330
501
|
{isLoading ? "Discovering models from the Codex runtime..." : (emptyMessage ?? "No models available.")}
|
|
331
502
|
</Text>
|
|
332
503
|
) : (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
504
|
+
visibleModels.map((model, index) => {
|
|
505
|
+
const actualIndex = windowResult.start + index;
|
|
506
|
+
return (
|
|
507
|
+
<ModelPickerRow
|
|
508
|
+
key={model.id}
|
|
509
|
+
model={model}
|
|
510
|
+
width={innerWidth}
|
|
511
|
+
currentModel={currentModel}
|
|
512
|
+
currentGeminiSelection={currentGeminiSelection}
|
|
513
|
+
isHighlighted={actualIndex === draftSelectedModel}
|
|
514
|
+
selectedReasoning={actualIndex === draftSelectedModel ? draftReasoning : normalizeDraftReasoning(model, currentReasoning)}
|
|
515
|
+
/>
|
|
516
|
+
);
|
|
517
|
+
})
|
|
344
518
|
)}
|
|
345
519
|
</Box>
|
|
520
|
+
|
|
521
|
+
{windowResult.showBelow && (
|
|
522
|
+
<Box height={1} overflow="hidden">
|
|
523
|
+
<Text color={theme.accent}>↓ {windowResult.hiddenBelow} more</Text>
|
|
524
|
+
</Box>
|
|
525
|
+
)}
|
|
346
526
|
</Box>
|
|
347
527
|
</Box>
|
|
348
528
|
);
|
|
@@ -389,15 +569,15 @@ function ModelPickerRow({
|
|
|
389
569
|
return (
|
|
390
570
|
<Box width="100%" overflow="hidden">
|
|
391
571
|
<Box width={markerWidth} flexShrink={0}>
|
|
392
|
-
<Text color={isHighlighted ? theme.
|
|
572
|
+
<Text color={isHighlighted ? theme.accent : theme.textDim}>{isHighlighted ? ">" : " "}</Text>
|
|
393
573
|
</Box>
|
|
394
574
|
<Box width={nameWidth} flexShrink={0} overflow="hidden">
|
|
395
|
-
<Text color={isHighlighted ? theme.
|
|
575
|
+
<Text color={isHighlighted ? theme.text : theme.textMuted} bold={isHighlighted} wrap="truncate">
|
|
396
576
|
{name}
|
|
397
577
|
</Text>
|
|
398
578
|
</Box>
|
|
399
579
|
<Box width={checkWidth} flexShrink={0}>
|
|
400
|
-
<Text color={theme.
|
|
580
|
+
<Text color={theme.textDim}>{isCurrent ? "✓" : " "}</Text>
|
|
401
581
|
</Box>
|
|
402
582
|
{pillWidth > 0 && (
|
|
403
583
|
<>
|
|
@@ -405,7 +585,7 @@ function ModelPickerRow({
|
|
|
405
585
|
<Text> </Text>
|
|
406
586
|
</Box>
|
|
407
587
|
<Box width={pillWidth} flexShrink={0} overflow="hidden">
|
|
408
|
-
<Text color={theme.
|
|
588
|
+
<Text color={theme.accent} bold wrap="truncate">
|
|
409
589
|
{pillText}
|
|
410
590
|
</Text>
|
|
411
591
|
</Box>
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
normalizeReasoningForModelCapabilities,
|
|
7
7
|
} from "../core/models/codexModelCapabilities.js";
|
|
8
8
|
import { formatReasoningLabel } from "../config/settings.js";
|
|
9
|
-
import { traceInputDebug } from "../core/inputDebug.js";
|
|
9
|
+
import { traceInputDebug } from "../core/debug/inputDebug.js";
|
|
10
10
|
import { FOCUS_IDS } from "./focus.js";
|
|
11
11
|
import { useTheme } from "./theme.js";
|
|
12
12
|
|
|
@@ -290,18 +290,18 @@ function LoadingPickerView({
|
|
|
290
290
|
<Box flexDirection="column" width="100%">
|
|
291
291
|
<Box
|
|
292
292
|
borderStyle="round"
|
|
293
|
-
borderColor={theme.
|
|
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.
|
|
301
|
-
<Text color={theme.
|
|
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.
|
|
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.
|
|
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.
|
|
364
|
-
<Text color={theme.
|
|
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.
|
|
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.
|
|
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.
|
|
426
|
+
? theme.textDim
|
|
427
427
|
: isActive
|
|
428
|
-
? isHighlighted ? theme.
|
|
429
|
-
: theme.
|
|
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.
|
|
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.
|
|
450
|
+
<Text color={theme.textDim}>{commitMark}</Text>
|
|
451
451
|
</Box>
|
|
452
452
|
</Box>
|
|
453
453
|
<Box flexDirection="row" gap={1} flexShrink={0}>
|
package/src/ui/Panel.tsx
CHANGED
|
@@ -13,8 +13,8 @@ interface PanelProps {
|
|
|
13
13
|
|
|
14
14
|
export function Panel({ cols, title, rightTitle, borderColor, titleColor, children }: PanelProps) {
|
|
15
15
|
const theme = useTheme();
|
|
16
|
-
const cBorder = borderColor || theme.
|
|
17
|
-
const cTitle = titleColor || theme.
|
|
16
|
+
const cBorder = borderColor || theme.borderFocused;
|
|
17
|
+
const cTitle = titleColor || theme.text;
|
|
18
18
|
|
|
19
19
|
const leftLabel = ` ${title} `;
|
|
20
20
|
const rightLabel = rightTitle ? ` ${rightTitle} ` : "";
|
|
@@ -32,7 +32,7 @@ export function Panel({ cols, title, rightTitle, borderColor, titleColor, childr
|
|
|
32
32
|
{"╭─"}
|
|
33
33
|
<Text color={cTitle}>{leftLabel}</Text>
|
|
34
34
|
{"─".repeat(dashCount)}
|
|
35
|
-
{rightTitle && <Text color={theme.
|
|
35
|
+
{rightTitle && <Text color={theme.textDim}>{rightLabel}</Text>}
|
|
36
36
|
{"╮"}
|
|
37
37
|
</Text>
|
|
38
38
|
<Box
|
|
@@ -85,10 +85,10 @@ export function PlanActionPicker({
|
|
|
85
85
|
const selected = index === selectedIndex;
|
|
86
86
|
return (
|
|
87
87
|
<Text key={row.value}>
|
|
88
|
-
<Text color={selected ? theme.
|
|
88
|
+
<Text color={selected ? theme.accent : theme.textDim}>
|
|
89
89
|
{selected ? "› " : vertical ? " " : ""}
|
|
90
90
|
</Text>
|
|
91
|
-
<Text color={selected ? theme.
|
|
91
|
+
<Text color={selected ? theme.text : theme.textMuted}>
|
|
92
92
|
{`[${row.key}] ${row.label}`}
|
|
93
93
|
</Text>
|
|
94
94
|
</Text>
|
|
@@ -98,7 +98,7 @@ export function PlanActionPicker({
|
|
|
98
98
|
if (vertical) {
|
|
99
99
|
return (
|
|
100
100
|
<Box flexDirection="column">
|
|
101
|
-
<Text color={isFocused ? theme.
|
|
101
|
+
<Text color={isFocused ? theme.text : theme.textMuted} bold={isFocused}>Plan ready</Text>
|
|
102
102
|
{ACTION_ROWS.map(renderAction)}
|
|
103
103
|
</Box>
|
|
104
104
|
);
|
|
@@ -106,12 +106,12 @@ export function PlanActionPicker({
|
|
|
106
106
|
|
|
107
107
|
return (
|
|
108
108
|
<Text>
|
|
109
|
-
<Text color={isFocused ? theme.
|
|
110
|
-
<Text color={theme.
|
|
109
|
+
<Text color={isFocused ? theme.text : theme.textMuted} bold={isFocused}>Plan ready</Text>
|
|
110
|
+
<Text color={theme.textDim}>{" "}</Text>
|
|
111
111
|
{ACTION_ROWS.map((row, index) => (
|
|
112
112
|
<React.Fragment key={row.value}>
|
|
113
113
|
{renderAction(row, index)}
|
|
114
|
-
{index < ACTION_ROWS.length - 1 && <Text color={theme.
|
|
114
|
+
{index < ACTION_ROWS.length - 1 && <Text color={theme.textDim}>{" "}</Text>}
|
|
115
115
|
</React.Fragment>
|
|
116
116
|
))}
|
|
117
117
|
</Text>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { normalizePlanReviewMarkdown } from "../core/planStorage.js";
|
|
3
|
+
import { normalizePlanReviewMarkdown } from "../core/workspace/planStorage.js";
|
|
4
4
|
import { getUsableShellWidth } from "./layout.js";
|
|
5
5
|
import { parseMarkdown } from "./Markdown.js";
|
|
6
6
|
import { getTextWidth } from "./textLayout.js";
|
|
@@ -192,21 +192,21 @@ export function PlanReviewPanel({
|
|
|
192
192
|
return (
|
|
193
193
|
<Box width="100%" flexDirection="column" paddingX={2}>
|
|
194
194
|
<Text wrap="truncate">
|
|
195
|
-
<Text color={theme.
|
|
196
|
-
<Text color={theme.
|
|
197
|
-
<Text color={theme.
|
|
198
|
-
<Text color={theme.
|
|
195
|
+
<Text color={theme.border}>{"╭─ "}</Text>
|
|
196
|
+
<Text color={theme.text} bold>{topBorder.title}</Text>
|
|
197
|
+
<Text color={theme.border}>{topBorder.fill}</Text>
|
|
198
|
+
<Text color={theme.border}>{"╮"}</Text>
|
|
199
199
|
</Text>
|
|
200
200
|
{displayRows.map((row, index) => (
|
|
201
201
|
<Text key={index} wrap="truncate">
|
|
202
|
-
<Text color={theme.
|
|
203
|
-
<Text color={row.tone === "muted" ? theme.
|
|
202
|
+
<Text color={theme.border}>{"│ "}</Text>
|
|
203
|
+
<Text color={row.tone === "muted" ? theme.textMuted : theme.text} bold={row.bold}>
|
|
204
204
|
{padVisual(row.text, contentWidth)}
|
|
205
205
|
</Text>
|
|
206
|
-
<Text color={theme.
|
|
206
|
+
<Text color={theme.border}>{" │"}</Text>
|
|
207
207
|
</Text>
|
|
208
208
|
))}
|
|
209
|
-
<Text wrap="truncate" color={theme.
|
|
209
|
+
<Text wrap="truncate" color={theme.border}>{`╰${"─".repeat(Math.max(1, panelCols - 2))}╯`}</Text>
|
|
210
210
|
</Box>
|
|
211
211
|
);
|
|
212
212
|
}
|