@golba98/codexa 1.0.1 → 1.0.2
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 +0 -0
- package/package.json +3 -2
- package/scripts/audit-codexa-capabilities.mjs +0 -0
- package/scripts/gen-build-info.mjs +33 -0
- package/scripts/smoke-terminal-bench.mjs +0 -0
- package/src/app.tsx +103 -2
- package/src/appRenderStability.test.ts +0 -0
- package/src/commands/handler.test.ts +12 -0
- package/src/commands/handler.ts +5 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.test.ts +31 -0
- package/src/config/launchArgs.ts +10 -0
- package/src/config/layeredConfig.test.ts +0 -0
- package/src/config/layeredConfig.ts +0 -0
- package/src/config/persistence.test.ts +6 -2
- package/src/config/persistence.ts +41 -0
- package/src/config/runtimeConfig.test.ts +0 -0
- package/src/config/runtimeConfig.ts +4 -0
- package/src/config/settings.test.ts +0 -0
- package/src/config/settings.ts +7 -6
- package/src/config/toml-serialize.ts +0 -0
- package/src/config/trustStore.test.ts +0 -0
- package/src/config/trustStore.ts +0 -0
- package/src/config/updateCheckCache.ts +45 -0
- package/src/core/attachments.test.ts +0 -0
- package/src/core/attachments.ts +0 -0
- package/src/core/auth/codexAuth.test.ts +0 -0
- package/src/core/auth/codexAuth.ts +0 -0
- package/src/core/cleanupFastFail.test.ts +0 -0
- package/src/core/cleanupFastFail.ts +0 -0
- package/src/core/clipboard.ts +0 -0
- package/src/core/codex.ts +0 -0
- package/src/core/codexExecArgs.test.ts +0 -0
- package/src/core/codexExecArgs.ts +0 -0
- package/src/core/codexLaunch.test.ts +0 -0
- package/src/core/codexLaunch.ts +0 -0
- package/src/core/codexPrompt.test.ts +0 -0
- package/src/core/codexPrompt.ts +0 -0
- package/src/core/executables/claudeExecutable.ts +0 -0
- package/src/core/executables/codexExecutable.test.ts +0 -0
- package/src/core/executables/codexExecutable.ts +0 -0
- package/src/core/executables/executableResolver.test.ts +0 -0
- package/src/core/executables/executableResolver.ts +0 -0
- package/src/core/executables/geminiExecutable.test.ts +0 -0
- package/src/core/executables/geminiExecutable.ts +0 -0
- package/src/core/executables/pathSanityScan.test.ts +0 -0
- package/src/core/githubDiagnostics.test.ts +0 -0
- package/src/core/githubDiagnostics.ts +0 -0
- package/src/core/hollowResponseFormat.test.ts +0 -0
- package/src/core/hollowResponseFormat.ts +0 -0
- package/src/core/inputDebug.ts +0 -0
- package/src/core/launchContext.test.ts +0 -0
- package/src/core/launchContext.ts +0 -0
- package/src/core/models/codexCapabilities.test.ts +0 -0
- package/src/core/models/codexCapabilities.ts +0 -0
- package/src/core/models/codexModelCapabilities.test.ts +0 -0
- package/src/core/models/codexModelCapabilities.ts +0 -0
- package/src/core/models/modelSpecs.test.ts +0 -0
- package/src/core/models/modelSpecs.ts +0 -0
- package/src/core/perf/profiler.ts +0 -0
- package/src/core/perf/renderDebug.test.ts +0 -0
- package/src/core/perf/renderDebug.ts +0 -0
- package/src/core/planStorage.test.ts +0 -0
- package/src/core/planStorage.ts +0 -0
- package/src/core/process/CommandRunner.test.ts +0 -0
- package/src/core/process/CommandRunner.ts +0 -0
- package/src/core/process/processValidation.ts +0 -0
- package/src/core/projectInstructions.test.ts +0 -0
- package/src/core/projectInstructions.ts +0 -0
- package/src/core/providerLauncher/launcher.test.ts +0 -0
- package/src/core/providerLauncher/launcher.ts +0 -0
- package/src/core/providerLauncher/registry.test.ts +0 -0
- package/src/core/providerLauncher/registry.ts +0 -0
- package/src/core/providerLauncher/types.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -0
- package/src/core/providerLauncher/workspaceConfig.ts +0 -0
- package/src/core/providerRuntime/anthropic.test.ts +0 -0
- package/src/core/providerRuntime/anthropic.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -0
- package/src/core/providerRuntime/capabilityProfile.ts +0 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -0
- package/src/core/providerRuntime/contextMetadata.ts +0 -0
- package/src/core/providerRuntime/gemini.test.ts +0 -0
- package/src/core/providerRuntime/gemini.ts +0 -0
- package/src/core/providerRuntime/lmstudio.test.ts +0 -0
- package/src/core/providerRuntime/lmstudio.ts +0 -0
- package/src/core/providerRuntime/local.test.ts +0 -0
- package/src/core/providerRuntime/local.ts +0 -0
- package/src/core/providerRuntime/models.ts +0 -0
- package/src/core/providerRuntime/reasoning.ts +0 -0
- package/src/core/providerRuntime/registry.test.ts +0 -0
- package/src/core/providerRuntime/registry.ts +0 -0
- package/src/core/providerRuntime/types.ts +0 -0
- package/src/core/providers/codexJsonStream.test.ts +0 -0
- package/src/core/providers/codexJsonStream.ts +0 -0
- package/src/core/providers/codexSubprocess.test.ts +0 -0
- package/src/core/providers/codexSubprocess.ts +0 -0
- package/src/core/providers/codexTranscript.test.ts +0 -0
- package/src/core/providers/codexTranscript.ts +0 -0
- package/src/core/providers/openaiNative.ts +0 -0
- package/src/core/providers/registry.ts +0 -0
- package/src/core/providers/types.ts +0 -0
- package/src/core/terminal/startupClear.test.ts +55 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.test.ts +0 -0
- package/src/core/terminal/terminalCapabilities.ts +0 -0
- package/src/core/terminal/terminalControl.test.ts +0 -0
- package/src/core/terminal/terminalControl.ts +0 -0
- package/src/core/terminal/terminalSanitize.test.ts +0 -0
- package/src/core/terminal/terminalSanitize.ts +0 -0
- package/src/core/terminal/terminalSelection.test.ts +0 -0
- package/src/core/terminal/terminalSelection.ts +0 -0
- package/src/core/terminal/terminalTitle.test.ts +0 -0
- package/src/core/terminal/terminalTitle.ts +0 -0
- package/src/core/updateCheck.test.ts +194 -0
- package/src/core/updateCheck.ts +172 -0
- package/src/core/workspaceActivity.test.ts +0 -0
- package/src/core/workspaceActivity.ts +0 -0
- package/src/core/workspaceGuard.test.ts +0 -0
- package/src/core/workspaceGuard.ts +0 -0
- package/src/core/workspaceRoot.test.ts +0 -0
- package/src/core/workspaceRoot.ts +0 -0
- package/src/exec.test.ts +0 -0
- package/src/exec.ts +0 -0
- package/src/headless/execArgs.test.ts +0 -0
- package/src/headless/execArgs.ts +1 -0
- package/src/headless/execRunner.test.ts +8 -6
- package/src/headless/execRunner.ts +0 -0
- package/src/index.test.tsx +5 -3
- package/src/index.tsx +13 -9
- package/src/session/appSession.test.ts +0 -0
- package/src/session/appSession.ts +0 -0
- package/src/session/chatLifecycle.test.ts +0 -0
- package/src/session/chatLifecycle.ts +0 -0
- package/src/session/liveRenderScheduler.test.ts +0 -0
- package/src/session/liveRenderScheduler.ts +0 -0
- package/src/session/planFlow.test.ts +0 -0
- package/src/session/planFlow.ts +0 -0
- package/src/session/planTranscript.test.ts +0 -0
- package/src/session/planTranscript.ts +0 -0
- package/src/session/promptRunSchedule.test.ts +0 -0
- package/src/session/promptRunSchedule.ts +0 -0
- package/src/session/types.ts +0 -0
- package/src/test/runtimeTestUtils.ts +0 -0
- package/src/types/react-dom.d.ts +0 -0
- package/src/ui/ActionRequiredBlock.tsx +0 -0
- package/src/ui/ActivityBars.tsx +0 -0
- package/src/ui/ActivityIndicator.test.tsx +0 -0
- package/src/ui/ActivityIndicator.tsx +0 -0
- package/src/ui/AgentBlock.test.ts +0 -0
- package/src/ui/AgentBlock.tsx +0 -0
- package/src/ui/AnimatedStatusText.test.ts +0 -0
- package/src/ui/AnimatedStatusText.tsx +0 -0
- package/src/ui/AppShell.test.tsx +60 -23
- package/src/ui/AppShell.tsx +28 -5
- package/src/ui/AttachmentImportPanel.test.tsx +0 -0
- package/src/ui/AttachmentImportPanel.tsx +0 -0
- package/src/ui/AuthPanel.tsx +0 -0
- package/src/ui/BackendPicker.tsx +0 -0
- package/src/ui/BottomComposer.test.ts +0 -0
- package/src/ui/BottomComposer.tsx +0 -0
- package/src/ui/CodexLogo.tsx +0 -0
- package/src/ui/DashCard.tsx +0 -0
- package/src/ui/Markdown.test.ts +0 -0
- package/src/ui/Markdown.tsx +0 -0
- package/src/ui/ModePicker.tsx +0 -0
- package/src/ui/ModelPicker.tsx +0 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -0
- package/src/ui/ModelPickerScreen.test.tsx +0 -0
- package/src/ui/ModelPickerScreen.tsx +0 -0
- package/src/ui/ModelPickerState.test.tsx +0 -0
- package/src/ui/ModelReasoningPicker.test.tsx +0 -0
- package/src/ui/ModelReasoningPicker.tsx +0 -0
- package/src/ui/Panel.tsx +0 -0
- package/src/ui/PermissionsPanel.tsx +0 -0
- package/src/ui/PlanActionPicker.tsx +0 -0
- package/src/ui/PlanReviewPanel.test.tsx +0 -0
- package/src/ui/PlanReviewPanel.tsx +0 -0
- package/src/ui/PromptCardBorder.test.tsx +0 -0
- package/src/ui/ProviderPicker.test.tsx +0 -0
- package/src/ui/ProviderPicker.tsx +0 -0
- package/src/ui/ProviderShortcut.test.tsx +0 -0
- package/src/ui/ReasoningPicker.tsx +0 -0
- package/src/ui/RunFooter.tsx +0 -0
- package/src/ui/SelectionPanel.tsx +0 -0
- package/src/ui/SettingsPanel.test.tsx +0 -0
- package/src/ui/SettingsPanel.tsx +0 -0
- package/src/ui/Spinner.tsx +0 -0
- package/src/ui/StaticIntroItem.tsx +0 -0
- package/src/ui/StaticTranscriptItem.tsx +0 -0
- package/src/ui/TextEntryPanel.tsx +0 -0
- package/src/ui/ThemePicker.tsx +0 -0
- package/src/ui/ThinkingBlock.tsx +0 -0
- package/src/ui/Timeline.test.ts +0 -0
- package/src/ui/Timeline.tsx +0 -0
- package/src/ui/TimelineNavigation.test.tsx +0 -0
- package/src/ui/TopHeader.test.tsx +53 -38
- package/src/ui/TopHeader.tsx +51 -15
- package/src/ui/TurnGroup.test.tsx +0 -0
- package/src/ui/TurnGroup.tsx +0 -0
- package/src/ui/busyStatusAnimation.test.ts +0 -0
- package/src/ui/busyStatusAnimation.ts +0 -0
- package/src/ui/commandNormalize.test.ts +0 -0
- package/src/ui/commandNormalize.ts +0 -0
- package/src/ui/diffRenderer.test.ts +0 -0
- package/src/ui/diffRenderer.ts +0 -0
- package/src/ui/focus.ts +0 -0
- package/src/ui/focusFlow.test.tsx +0 -0
- package/src/ui/inputBuffer.test.ts +0 -0
- package/src/ui/inputBuffer.ts +0 -0
- package/src/ui/layout.test.ts +3 -2
- package/src/ui/layout.ts +0 -0
- package/src/ui/modeDisplay.test.ts +0 -0
- package/src/ui/modeDisplay.ts +0 -0
- package/src/ui/outputPipeline.ts +0 -0
- package/src/ui/progressEntries.ts +0 -0
- package/src/ui/runActivityView.test.ts +0 -0
- package/src/ui/runActivityView.ts +0 -0
- package/src/ui/runLifecycleView.test.tsx +0 -0
- package/src/ui/slashCommands.ts +0 -0
- package/src/ui/statusRenderIsolation.test.tsx +0 -0
- package/src/ui/terminalAnswerFormat.test.ts +0 -0
- package/src/ui/terminalAnswerFormat.ts +0 -0
- package/src/ui/textLayout.test.ts +0 -0
- package/src/ui/textLayout.ts +0 -0
- package/src/ui/theme.tsx +0 -0
- package/src/ui/themeFlow.test.ts +0 -0
- package/src/ui/themeFlow.ts +0 -0
- package/src/ui/timelineMeasure.ts +0 -0
- package/src/ui/timelineMeasureCache.test.ts +0 -0
- package/src/ui/useThrottledValue.ts +0 -0
|
@@ -50,7 +50,11 @@ function sleep(ms = 50): Promise<void> {
|
|
|
50
50
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const HEADER_CONFIG_WITH_AUTH: HeaderConfig = {
|
|
53
|
+
const HEADER_CONFIG_WITH_AUTH: HeaderConfig = {
|
|
54
|
+
...HEADER_CONFIG_DEFAULTS,
|
|
55
|
+
showAuthStatus: true,
|
|
56
|
+
showContext: true,
|
|
57
|
+
};
|
|
54
58
|
|
|
55
59
|
async function renderHeader(cols: number, authState: CodexAuthState, headerConfig?: HeaderConfig): Promise<string> {
|
|
56
60
|
return renderHeaderWithWorkspace(cols, authState, "C:\\Development\\1-JavaScript\\13-Custom CLI", headerConfig);
|
|
@@ -105,9 +109,12 @@ test("full mode renders wordmark at wide terminal", async () => {
|
|
|
105
109
|
assert.match(output, /[█╔╗╚╝═║]/);
|
|
106
110
|
assert.match(output, new RegExp(`Codexa v${APP_VERSION.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
107
111
|
assert.match(output, /Authenticated/);
|
|
108
|
-
assert.match(output, /Workspace:\s*C:\\Development\\1-JavaScript\\13-Custom CLI/);
|
|
112
|
+
assert.match(output, /Workspace:\s*C:\\Development\\1-JavaScript\\13-Custom CLI/);
|
|
113
|
+
assert.match(output, /Provider:\s*Codexa Core/);
|
|
114
|
+
assert.match(output, /Context:\s*Unknown/);
|
|
115
|
+
assert.doesNotMatch(output, /Model:/);
|
|
116
|
+
assert.doesNotMatch(output, /Reasoning:/);
|
|
109
117
|
assert.doesNotMatch(output, /Runtime:/);
|
|
110
|
-
assert.doesNotMatch(output, /gpt-5\.4/i);
|
|
111
118
|
assert.doesNotMatch(output, /Net:\s*off/i);
|
|
112
119
|
assert.doesNotMatch(output, /Roots:\s*0/i);
|
|
113
120
|
assert.doesNotMatch(output, /FULL AUTO/i);
|
|
@@ -123,15 +130,17 @@ test("wide header centers metadata beside the logo with a clear column gap", asy
|
|
|
123
130
|
const workspaceRow = rows.findIndex((row) => row.includes("Workspace:"));
|
|
124
131
|
|
|
125
132
|
assert.ok(firstLogoRow >= 0, "logo should render");
|
|
126
|
-
assert.
|
|
127
|
-
|
|
128
|
-
assert.
|
|
129
|
-
|
|
130
|
-
});
|
|
133
|
+
assert.ok(brandRow >= firstLogoRow && brandRow <= firstLogoRow + 2, "metadata should be vertically centered within the logo block");
|
|
134
|
+
assert.equal(workspaceRow, brandRow + 2, "workspace should sit below auth in the metadata block");
|
|
135
|
+
assert.ok((rows[brandRow]?.indexOf(`Codexa v${APP_VERSION}`) ?? -1) >= 55, "metadata should have a visible left gap from the logo");
|
|
136
|
+
});
|
|
131
137
|
|
|
132
138
|
test("version and workspace metadata rows have a visible gap between them", async () => {
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
const output = await renderHeader(130, "authenticated", {
|
|
140
|
+
...HEADER_CONFIG_DEFAULTS,
|
|
141
|
+
showProvider: false,
|
|
142
|
+
showModel: false,
|
|
143
|
+
});
|
|
135
144
|
const rows = output.split("\n");
|
|
136
145
|
const brandRow = rows.findIndex((row) => row.includes(`Codexa v${APP_VERSION}`));
|
|
137
146
|
const workspaceRow = rows.findIndex((row) => row.includes("Workspace:"));
|
|
@@ -161,28 +170,33 @@ test("header wordmark lines never contain metadata text", () => {
|
|
|
161
170
|
assert.doesNotMatch(wordmarkText, /Auth:/);
|
|
162
171
|
});
|
|
163
172
|
|
|
164
|
-
test("compact mode renders version and auth", async () => {
|
|
165
|
-
const output = await renderHeader(105, "authenticated", HEADER_CONFIG_WITH_AUTH);
|
|
166
|
-
|
|
167
|
-
assert.match(output,
|
|
168
|
-
assert.match(output, /
|
|
169
|
-
assert.match(output, /
|
|
173
|
+
test("compact mode renders version and auth", async () => {
|
|
174
|
+
const output = await renderHeader(105, "authenticated", HEADER_CONFIG_WITH_AUTH);
|
|
175
|
+
|
|
176
|
+
assert.match(output, /[█╔╗╚╝═║]/);
|
|
177
|
+
assert.match(output, new RegExp(`v${APP_VERSION.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
178
|
+
assert.match(output, /Authenticated/);
|
|
179
|
+
assert.match(output, /Workspace:\s*C:\\Development\\1-JavaScript\\13-Custom CLI/);
|
|
180
|
+
assert.match(output, /Provider:\s*Codexa Core/);
|
|
181
|
+
assert.match(output, /Context:\s*Unknown/);
|
|
182
|
+
assert.doesNotMatch(output, /Model:/);
|
|
183
|
+
assert.doesNotMatch(output, /Reasoning:/);
|
|
170
184
|
assert.doesNotMatch(output, /Runtime:/);
|
|
171
|
-
assert.doesNotMatch(output, /gpt-5\.4/i);
|
|
172
185
|
assert.doesNotMatch(output, /Net:\s*off/i);
|
|
173
186
|
assert.doesNotMatch(output, /Roots:\s*0/i);
|
|
174
187
|
assert.doesNotMatch(output, /FULL AUTO/i);
|
|
175
|
-
assert.doesNotMatch(output, /[█╔╗╚╝═║]/);
|
|
176
188
|
});
|
|
177
189
|
|
|
178
|
-
test("micro mode renders version and auth", async () => {
|
|
179
|
-
const output = await renderHeader(50, "authenticated");
|
|
180
|
-
|
|
181
|
-
assert.match(output, /Codex/);
|
|
182
|
-
assert.match(output, new RegExp(`v${APP_VERSION.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
183
|
-
assert.doesNotMatch(output, /[█╔╗╚╝═║]/);
|
|
184
|
-
assert.
|
|
185
|
-
|
|
190
|
+
test("micro mode renders version and auth", async () => {
|
|
191
|
+
const output = await renderHeader(50, "authenticated", HEADER_CONFIG_WITH_AUTH);
|
|
192
|
+
|
|
193
|
+
assert.match(output, /Codex/);
|
|
194
|
+
assert.match(output, new RegExp(`v${APP_VERSION.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
195
|
+
assert.doesNotMatch(output, /[█╔╗╚╝═║]/);
|
|
196
|
+
assert.match(output, /Provider/);
|
|
197
|
+
assert.match(output, /Contex/);
|
|
198
|
+
assert.doesNotMatch(output, /Model:/);
|
|
199
|
+
});
|
|
186
200
|
|
|
187
201
|
test("full mode always shows wordmark regardless of activity", async () => {
|
|
188
202
|
const output = await renderHeader(180, "authenticated", HEADER_CONFIG_WITH_AUTH);
|
|
@@ -195,23 +209,24 @@ test("full mode always shows wordmark regardless of activity", async () => {
|
|
|
195
209
|
});
|
|
196
210
|
|
|
197
211
|
test("full mode renders Checking for checking auth state", async () => {
|
|
198
|
-
const output = await renderHeader(130, "checking", HEADER_CONFIG_WITH_AUTH);
|
|
199
|
-
assert.match(output, /Checking/);
|
|
200
|
-
assert.doesNotMatch(output, /Unknown/);
|
|
201
|
-
});
|
|
212
|
+
const output = await renderHeader(130, "checking", HEADER_CONFIG_WITH_AUTH);
|
|
213
|
+
assert.match(output, /Checking/);
|
|
214
|
+
assert.doesNotMatch(output, /Auth:\s*Unknown/);
|
|
215
|
+
});
|
|
202
216
|
|
|
203
|
-
test("compact mode preserves workspace truncation
|
|
217
|
+
test("compact mode preserves workspace truncation with runtime text", async () => {
|
|
204
218
|
const output = await renderHeaderWithWorkspace(
|
|
205
219
|
60,
|
|
206
220
|
"authenticated",
|
|
207
221
|
"C:\\Development\\1-JavaScript\\13-Custom CLI\\packages\\really-long-subfolder\\nested\\workspace",
|
|
208
222
|
);
|
|
209
223
|
|
|
210
|
-
assert.match(output, /\.\.\. /);
|
|
211
|
-
assert.match(output, /nested\\workspace/);
|
|
212
|
-
assert.doesNotMatch(output, /packages\\really-long-subfolder/);
|
|
213
|
-
assert.
|
|
214
|
-
|
|
224
|
+
assert.match(output, /\.\.\. /);
|
|
225
|
+
assert.match(output, /nested\\workspace/);
|
|
226
|
+
assert.doesNotMatch(output, /packages\\really-long-subfolder/);
|
|
227
|
+
assert.match(output, /Provider:\s*Codexa Core/);
|
|
228
|
+
assert.doesNotMatch(output, /Model:/);
|
|
229
|
+
});
|
|
215
230
|
|
|
216
231
|
test("renders configured workspace display labels", async () => {
|
|
217
232
|
const workspaceRoot = "C:\\Development\\1-JavaScript\\13-Custom-CLI-Normal";
|
|
@@ -221,7 +236,7 @@ test("renders configured workspace display labels", async () => {
|
|
|
221
236
|
"authenticated",
|
|
222
237
|
formatWorkspaceDisplayPath(workspaceRoot, "dir"),
|
|
223
238
|
);
|
|
224
|
-
assert.match(dirOutput, /Workspace:\s*13-Custom-CLI-Normal/);
|
|
239
|
+
assert.match(dirOutput, /Workspace:\s*(?:.*\\)?13-Custom-CLI-Normal/);
|
|
225
240
|
|
|
226
241
|
const nameOutput = await renderHeaderWithWorkspace(
|
|
227
242
|
130,
|
|
@@ -235,5 +250,5 @@ test("renders configured workspace display labels", async () => {
|
|
|
235
250
|
"authenticated",
|
|
236
251
|
formatWorkspaceDisplayPath(workspaceRoot, "simple"),
|
|
237
252
|
);
|
|
238
|
-
assert.match(simpleOutput, /Workspace:\s*13-Custom-CLI-Normal/);
|
|
253
|
+
assert.match(simpleOutput, /Workspace:\s*(?:.*\\)?13-Custom-CLI-Normal/);
|
|
239
254
|
});
|
package/src/ui/TopHeader.tsx
CHANGED
|
@@ -20,6 +20,8 @@ export const HEADER_WORDMARK_LINES = [
|
|
|
20
20
|
|
|
21
21
|
const MIN_METADATA_COLUMN_WIDTH = 60;
|
|
22
22
|
const STACKED_METADATA_GAP_ROWS = 1;
|
|
23
|
+
const MIN_LOGO_TERMINAL_WIDTH = getWordmarkWidth() + 2;
|
|
24
|
+
const MIN_LOGO_TERMINAL_ROWS = 24;
|
|
23
25
|
|
|
24
26
|
export type HeaderHeroMode = "wide" | "stacked" | "compact";
|
|
25
27
|
|
|
@@ -34,6 +36,7 @@ export interface HeaderHeroLayout {
|
|
|
34
36
|
totalRows: number;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
type HeaderMetadataLine = { key: string; text: string; color: string; bold: boolean };
|
|
37
40
|
|
|
38
41
|
interface TopHeaderProps {
|
|
39
42
|
authState: CodexAuthState;
|
|
@@ -47,29 +50,43 @@ function getWordmarkWidth(): number {
|
|
|
47
50
|
return HEADER_WORDMARK_LINES.reduce((maxWidth, line) => Math.max(maxWidth, getTextWidth(line)), 0);
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
function getMetadataRowCount(headerConfig: HeaderConfig): number {
|
|
54
|
+
return [
|
|
55
|
+
headerConfig.showBrand,
|
|
56
|
+
headerConfig.showAuthStatus,
|
|
57
|
+
headerConfig.showWorkspace,
|
|
58
|
+
headerConfig.showProvider,
|
|
59
|
+
headerConfig.showContext,
|
|
60
|
+
].filter(Boolean).length;
|
|
61
|
+
}
|
|
62
|
+
|
|
50
63
|
function getHeaderVerticalMargins(layout: Layout): { topMarginRows: number; bottomMarginRows: number } {
|
|
51
64
|
if (layout.mode !== "full") {
|
|
52
65
|
return {
|
|
53
66
|
topMarginRows: 0,
|
|
54
|
-
bottomMarginRows:
|
|
67
|
+
bottomMarginRows: 0,
|
|
55
68
|
};
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
if (layout.rows <= 24) {
|
|
59
|
-
return { topMarginRows: 0, bottomMarginRows:
|
|
72
|
+
return { topMarginRows: 0, bottomMarginRows: 0 };
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
return {
|
|
63
76
|
topMarginRows: 1,
|
|
64
|
-
bottomMarginRows:
|
|
77
|
+
bottomMarginRows: 0,
|
|
65
78
|
};
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
export function getHeaderHeroLayout(
|
|
81
|
+
export function getHeaderHeroLayout(
|
|
82
|
+
layout: Layout,
|
|
83
|
+
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
84
|
+
): HeaderHeroLayout {
|
|
69
85
|
const { topMarginRows, bottomMarginRows } = getHeaderVerticalMargins(layout);
|
|
70
|
-
const metadataRows =
|
|
86
|
+
const metadataRows = getMetadataRowCount(headerConfig);
|
|
87
|
+
const canRenderLogo = layout.cols >= MIN_LOGO_TERMINAL_WIDTH && layout.rows >= MIN_LOGO_TERMINAL_ROWS;
|
|
71
88
|
|
|
72
|
-
if (
|
|
89
|
+
if (!canRenderLogo) {
|
|
73
90
|
return {
|
|
74
91
|
mode: "compact",
|
|
75
92
|
topMarginRows,
|
|
@@ -85,7 +102,7 @@ export function getHeaderHeroLayout(layout: Layout): HeaderHeroLayout {
|
|
|
85
102
|
const logoRows = HEADER_WORDMARK_LINES.length;
|
|
86
103
|
const metadataGapColumns = layout.cols >= 150 ? 6 : layout.cols >= 130 ? 5 : 4;
|
|
87
104
|
const canUseWideHero = layout.cols >= getWordmarkWidth() + metadataGapColumns + MIN_METADATA_COLUMN_WIDTH + 2;
|
|
88
|
-
const metadataGapRows = canUseWideHero ? 0 : STACKED_METADATA_GAP_ROWS;
|
|
105
|
+
const metadataGapRows = canUseWideHero || metadataRows === 0 ? 0 : STACKED_METADATA_GAP_ROWS;
|
|
89
106
|
const contentRows = canUseWideHero
|
|
90
107
|
? logoRows
|
|
91
108
|
: logoRows + metadataGapRows + metadataRows;
|
|
@@ -102,8 +119,11 @@ export function getHeaderHeroLayout(layout: Layout): HeaderHeroLayout {
|
|
|
102
119
|
};
|
|
103
120
|
}
|
|
104
121
|
|
|
105
|
-
export function measureTopHeaderRows(
|
|
106
|
-
|
|
122
|
+
export function measureTopHeaderRows(
|
|
123
|
+
layout: Layout,
|
|
124
|
+
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
125
|
+
): number {
|
|
126
|
+
return getHeaderHeroLayout(layout, headerConfig).totalRows;
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
/** Truncate a path to fit within maxWidth, keeping the end and prefixing with "... " if needed */
|
|
@@ -117,6 +137,7 @@ export function TopHeader({
|
|
|
117
137
|
authState,
|
|
118
138
|
workspaceLabel,
|
|
119
139
|
layout,
|
|
140
|
+
runtimeSummary = null,
|
|
120
141
|
headerConfig = HEADER_CONFIG_DEFAULTS,
|
|
121
142
|
}: TopHeaderProps) {
|
|
122
143
|
renderDebug.useRenderDebug("Header", {
|
|
@@ -133,7 +154,7 @@ export function TopHeader({
|
|
|
133
154
|
mode: layout.mode,
|
|
134
155
|
});
|
|
135
156
|
|
|
136
|
-
const { cols
|
|
157
|
+
const { cols } = layout;
|
|
137
158
|
const theme = useTheme();
|
|
138
159
|
|
|
139
160
|
const authLabelRaw = getAuthStateLabel(authState);
|
|
@@ -142,16 +163,23 @@ export function TopHeader({
|
|
|
142
163
|
: authLabelRaw;
|
|
143
164
|
|
|
144
165
|
const wsDisplay = truncatePath(workspaceLabel, Math.max(18, cols - 40));
|
|
145
|
-
const heroLayout = getHeaderHeroLayout(layout);
|
|
166
|
+
const heroLayout = getHeaderHeroLayout(layout, headerConfig);
|
|
146
167
|
const metadataLines = [
|
|
147
168
|
headerConfig.showBrand ? { key: "brand", text: `Codexa v${APP_VERSION}`, color: theme.TEXT, bold: true } : null,
|
|
148
169
|
headerConfig.showAuthStatus ? { key: "auth", text: `Auth: ${authLabel}`, color: theme.TEXT, bold: false } : null,
|
|
149
170
|
headerConfig.showWorkspace ? { key: "workspace", text: `Workspace: ${wsDisplay}`, color: theme.MUTED, bold: false } : null,
|
|
150
|
-
|
|
171
|
+
headerConfig.showProvider && runtimeSummary?.providerLabel
|
|
172
|
+
? { key: "provider", text: `Provider: ${runtimeSummary.providerLabel}`, color: theme.TEXT, bold: false }
|
|
173
|
+
: null,
|
|
174
|
+
headerConfig.showContext
|
|
175
|
+
? { key: "context", text: `Context: ${runtimeSummary?.contextLabel ?? "Unknown"}`, color: theme.MUTED, bold: false }
|
|
176
|
+
: null,
|
|
177
|
+
].filter((line): line is HeaderMetadataLine => Boolean(line));
|
|
151
178
|
|
|
152
179
|
// Add a 1-row gap between the version line and workspace line in wide mode
|
|
153
180
|
// so the two pieces of metadata have breathing room beside the logo.
|
|
154
181
|
const hasMetadataGap = heroLayout.mode === "wide"
|
|
182
|
+
&& metadataLines.length <= 3
|
|
155
183
|
&& metadataLines.some((l) => l.key === "brand")
|
|
156
184
|
&& metadataLines.some((l) => l.key === "workspace");
|
|
157
185
|
const metadataVisualRows = metadataLines.length + (hasMetadataGap ? 1 : 0);
|
|
@@ -178,7 +206,7 @@ export function TopHeader({
|
|
|
178
206
|
</Box>
|
|
179
207
|
);
|
|
180
208
|
|
|
181
|
-
if (mode
|
|
209
|
+
if (heroLayout.mode !== "compact") {
|
|
182
210
|
const metadataTopOffset = Math.max(0, Math.floor((HEADER_WORDMARK_LINES.length - metadataVisualRows) / 2));
|
|
183
211
|
|
|
184
212
|
return (
|
|
@@ -188,7 +216,7 @@ export function TopHeader({
|
|
|
188
216
|
)}
|
|
189
217
|
|
|
190
218
|
{heroLayout.mode === "wide" ? (
|
|
191
|
-
<Box flexDirection="row" width="100%">
|
|
219
|
+
<Box flexDirection="row" width="100%" alignItems="flex-start">
|
|
192
220
|
{logoColumn}
|
|
193
221
|
<Box width={heroLayout.metadataGapColumns} flexShrink={0} />
|
|
194
222
|
<Box flexDirection="column" flexGrow={1} paddingTop={metadataTopOffset}>
|
|
@@ -225,7 +253,15 @@ export function TopHeader({
|
|
|
225
253
|
}
|
|
226
254
|
if (headerConfig.showWorkspace) {
|
|
227
255
|
if (compactParts.length > 0) compactParts.push(<Text key="sep-ws" color={theme.DIM}>{" · "}</Text>);
|
|
228
|
-
compactParts.push(<Text key="ws" color={theme.MUTED} wrap="truncate">{wsDisplay}</Text>);
|
|
256
|
+
compactParts.push(<Text key="ws" color={theme.MUTED} wrap="truncate">{`Workspace: ${wsDisplay}`}</Text>);
|
|
257
|
+
}
|
|
258
|
+
if (headerConfig.showProvider && runtimeSummary?.providerLabel) {
|
|
259
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-provider" color={theme.DIM}>{" · "}</Text>);
|
|
260
|
+
compactParts.push(<Text key="provider" color={theme.TEXT} wrap="truncate">{`Provider: ${runtimeSummary.providerLabel}`}</Text>);
|
|
261
|
+
}
|
|
262
|
+
if (headerConfig.showContext) {
|
|
263
|
+
if (compactParts.length > 0) compactParts.push(<Text key="sep-context" color={theme.DIM}>{" · "}</Text>);
|
|
264
|
+
compactParts.push(<Text key="context" color={theme.MUTED} wrap="truncate">{`Context: ${runtimeSummary?.contextLabel ?? "Unknown"}`}</Text>);
|
|
229
265
|
}
|
|
230
266
|
|
|
231
267
|
return (
|
|
File without changes
|
package/src/ui/TurnGroup.tsx
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/diffRenderer.ts
CHANGED
|
File without changes
|
package/src/ui/focus.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/inputBuffer.ts
CHANGED
|
File without changes
|
package/src/ui/layout.test.ts
CHANGED
|
@@ -85,8 +85,9 @@ test("chooses startup header mode from measured row budget", () => {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test("measures the header rows for full and compact layouts", () => {
|
|
88
|
-
assert.equal(measureTopHeaderRows(createLayoutSnapshot(120, 30)),
|
|
89
|
-
assert.equal(measureTopHeaderRows(createLayoutSnapshot(80, 24)),
|
|
88
|
+
assert.equal(measureTopHeaderRows(createLayoutSnapshot(120, 30)), 7);
|
|
89
|
+
assert.equal(measureTopHeaderRows(createLayoutSnapshot(80, 24)), 10);
|
|
90
|
+
assert.equal(measureTopHeaderRows(createLayoutSnapshot(50, 24)), 1);
|
|
90
91
|
});
|
|
91
92
|
|
|
92
93
|
test("header hero switches from stacked to wide only when metadata has room", () => {
|
package/src/ui/layout.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/src/ui/modeDisplay.ts
CHANGED
|
File without changes
|
package/src/ui/outputPipeline.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/slashCommands.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/textLayout.ts
CHANGED
|
File without changes
|
package/src/ui/theme.tsx
CHANGED
|
File without changes
|
package/src/ui/themeFlow.test.ts
CHANGED
|
File without changes
|
package/src/ui/themeFlow.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|