@golba98/codexa 1.0.3 → 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 +17 -18
- package/bin/codexa.js +62 -144
- package/package.json +4 -3
- package/src/app.tsx +533 -275
- package/src/commands/handler.ts +2 -2
- package/src/config/buildInfo.ts +2 -2
- package/src/config/layeredConfig.ts +1 -1
- package/src/config/runtimeConfig.ts +1 -1
- package/src/config/settings.ts +1 -1
- package/src/config/trustStore.ts +1 -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/executableResolver.ts +5 -1
- package/src/core/perf/renderDebug.ts +10 -6
- 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/{channel.ts → version/channel.ts} +1 -1
- package/src/core/{updateCheck.ts → version/updateCheck.ts} +10 -5
- package/src/core/{launchContext.ts → workspace/launchContext.ts} +9 -16
- 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 +1 -1
- package/src/ui/AppShell.tsx +672 -706
- package/src/ui/AttachmentImportPanel.tsx +2 -2
- package/src/ui/BottomComposer.tsx +145 -144
- package/src/ui/ModelPickerScreen.tsx +216 -36
- package/src/ui/ModelReasoningPicker.tsx +1 -1
- package/src/ui/PlanReviewPanel.tsx +1 -1
- package/src/ui/ProviderPicker.tsx +735 -321
- package/src/ui/RuntimeStatusBar.tsx +108 -0
- package/src/ui/SelectionPanel.tsx +4 -0
- package/src/ui/SettingsPanel.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +1 -1
- package/src/ui/Timeline.tsx +1619 -1470
- package/src/ui/TopHeader.tsx +46 -30
- package/src/ui/TranscriptShell.tsx +322 -0
- package/src/ui/TurnGroup.tsx +2 -2
- package/src/ui/UpdateAvailableCard.tsx +3 -3
- package/src/ui/UpdatePromptPanel.tsx +16 -22
- package/src/ui/layout.ts +298 -24
- package/src/ui/layoutListWindow.ts +145 -0
- package/src/ui/logoVariants.ts +4 -8
- package/src/ui/runtimeDisplay.ts +15 -3
- package/src/ui/textLayout.ts +15 -4
- package/src/ui/timelineMeasure.ts +194 -122
- package/src/core/codex.ts +0 -124
- package/src/ui/StaticTranscriptItem.tsx +0 -56
- /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/layout.ts
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Responsive layout constants and hook.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five modes based on both terminal columns and rows:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* compact
|
|
8
|
-
*
|
|
6
|
+
* micro very small rows/cols → one-line shell
|
|
7
|
+
* compact normal short terminal → compact shell, no large logo
|
|
8
|
+
* normal standard terminal → compact header, roomy content
|
|
9
|
+
* wide large terminal → decorative header allowed
|
|
10
|
+
* max maximized terminal → full decorative layout
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
|
-
import { useEffect, useRef, useState } from "react";
|
|
13
|
+
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
12
14
|
import { useStdout } from "ink";
|
|
13
15
|
import stringWidth from "string-width";
|
|
14
16
|
import * as renderDebug from "../core/perf/renderDebug.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export const
|
|
17
|
+
import { setTerminalResizing, isTerminalResizing } from "../core/terminal/terminalControl.js";
|
|
18
|
+
|
|
19
|
+
export const BREAKPOINT_MAX = 180;
|
|
20
|
+
export const BREAKPOINT_WIDE = 140;
|
|
21
|
+
export const BREAKPOINT_NORMAL = 90;
|
|
22
|
+
export const BREAKPOINT_COMPACT = 60;
|
|
23
|
+
export const ROW_BREAKPOINT_MAX = 40;
|
|
24
|
+
export const ROW_BREAKPOINT_WIDE = 30;
|
|
25
|
+
export const ROW_BREAKPOINT_NORMAL = 20;
|
|
26
|
+
export const ROW_BREAKPOINT_COMPACT = 14;
|
|
27
|
+
export const MAX_CONTENT_WIDTH = 220;
|
|
28
|
+
export const MIN_TERMINAL_COLS = 20;
|
|
29
|
+
export const MIN_TERMINAL_ROWS = 10;
|
|
18
30
|
export const MIN_VIEWPORT_COLS = 20;
|
|
19
31
|
export const MIN_VIEWPORT_ROWS = 10;
|
|
20
|
-
export const RESTORE_SETTLE_MS = 100;
|
|
32
|
+
export const RESTORE_SETTLE_MS = process.env.NODE_ENV === "test" ? 0 : 100;
|
|
21
33
|
export const STARTUP_TINY_MIN_COLS = 40;
|
|
22
34
|
export const STARTUP_TINY_MIN_ROWS = 14;
|
|
23
35
|
export const STARTUP_FULL_MIN_COLS = 100; // matches LOGO_LARGE_MIN_COLS in logoVariants.ts
|
|
@@ -29,7 +41,7 @@ export const transcriptContentIndent = 4; // 2 for DashCard border + 2 for promp
|
|
|
29
41
|
const DEFAULT_COLUMNS = 120;
|
|
30
42
|
const DEFAULT_ROWS = 24;
|
|
31
43
|
|
|
32
|
-
export type LayoutMode = "
|
|
44
|
+
export type LayoutMode = "compact" | "regular" | "expanded";
|
|
33
45
|
export type StartupHeaderMode = "large" | "compact" | "tiny";
|
|
34
46
|
|
|
35
47
|
export interface Layout {
|
|
@@ -38,11 +50,85 @@ export interface Layout {
|
|
|
38
50
|
mode: LayoutMode;
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
export type PanelLayout = {
|
|
54
|
+
mode: "compact" | "regular" | "expanded";
|
|
55
|
+
availableRows: number;
|
|
56
|
+
availableCols: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type BottomChromeBudget = {
|
|
60
|
+
runtimeMetadataRows: number;
|
|
61
|
+
composerRows: number;
|
|
62
|
+
transientStatusRows: number;
|
|
63
|
+
bottomPaddingRows: number;
|
|
64
|
+
totalRows: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type AppLayoutBudget = {
|
|
68
|
+
mode: LayoutMode;
|
|
69
|
+
rows: number;
|
|
70
|
+
cols: number;
|
|
71
|
+
|
|
72
|
+
headerRows: number;
|
|
73
|
+
headerGapRows: number;
|
|
74
|
+
panelStagePaddingY: number;
|
|
75
|
+
|
|
76
|
+
activePanelRows: number;
|
|
77
|
+
activePanelCols: number;
|
|
78
|
+
|
|
79
|
+
bottomChromeBudget: BottomChromeBudget;
|
|
80
|
+
composerRows: number;
|
|
81
|
+
|
|
82
|
+
showNormalLogo: boolean;
|
|
83
|
+
showCompactHeader: boolean;
|
|
84
|
+
placeMetadataBesideLogo: boolean;
|
|
85
|
+
placeMetadataBelowLogo: boolean;
|
|
86
|
+
|
|
87
|
+
// Backward compatibility fields:
|
|
88
|
+
transcriptRows: number;
|
|
89
|
+
panelRows: number;
|
|
90
|
+
showLargeLogo: boolean;
|
|
91
|
+
showPanelSeparators: boolean;
|
|
92
|
+
showPanelColumnHeaders: boolean;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const PanelAvailableRowsContext = createContext<number | undefined>(undefined);
|
|
96
|
+
export const AppLayoutBudgetContext = createContext<AppLayoutBudget | undefined>(undefined);
|
|
97
|
+
export const PanelLayoutContext = createContext<PanelLayout | undefined>(undefined);
|
|
98
|
+
|
|
99
|
+
export interface ActivePanelLayout {
|
|
100
|
+
width: number;
|
|
101
|
+
height: number;
|
|
102
|
+
availableRows: number;
|
|
103
|
+
availableCols: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const ActivePanelLayoutContext = createContext<ActivePanelLayout | undefined>(undefined);
|
|
107
|
+
|
|
108
|
+
export function usePanelAvailableRows(): number | undefined {
|
|
109
|
+
return useContext(PanelAvailableRowsContext);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function useAppLayoutBudget(): AppLayoutBudget | undefined {
|
|
113
|
+
return useContext(AppLayoutBudgetContext);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function useActivePanelLayout(): ActivePanelLayout | undefined {
|
|
117
|
+
return useContext(ActivePanelLayoutContext);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function usePanelLayout(): PanelLayout | undefined {
|
|
121
|
+
return useContext(PanelLayoutContext);
|
|
122
|
+
}
|
|
123
|
+
|
|
41
124
|
export interface TerminalViewport extends Layout {
|
|
42
125
|
rawCols?: number;
|
|
43
126
|
rawRows?: number;
|
|
127
|
+
contentWidth: number;
|
|
128
|
+
isCramped: boolean;
|
|
44
129
|
unstable: boolean;
|
|
45
130
|
layoutEpoch: number;
|
|
131
|
+
isResizing: boolean;
|
|
46
132
|
}
|
|
47
133
|
|
|
48
134
|
// ─── Dimension helpers ────────────────────────────────────────────────────────
|
|
@@ -67,6 +153,13 @@ export function isRenderableViewport(cols: number | undefined, rows: number | un
|
|
|
67
153
|
&& Math.floor(rows) >= MIN_VIEWPORT_ROWS;
|
|
68
154
|
}
|
|
69
155
|
|
|
156
|
+
/** Returns true if the terminal is below the minimum supported size for a full UI. */
|
|
157
|
+
export function isCrampedTerminal(cols: number | undefined, rows: number | undefined): boolean {
|
|
158
|
+
const safeCols = normalizeDimension(cols, DEFAULT_COLUMNS);
|
|
159
|
+
const safeRows = normalizeDimension(rows, DEFAULT_ROWS);
|
|
160
|
+
return safeCols < MIN_TERMINAL_COLS || safeRows < MIN_TERMINAL_ROWS;
|
|
161
|
+
}
|
|
162
|
+
|
|
70
163
|
/**
|
|
71
164
|
* Leave a 1-column gutter so box-drawing borders never land exactly on the
|
|
72
165
|
* terminal edge, which can trigger a horizontal scrollbar in some Windows hosts.
|
|
@@ -75,6 +168,20 @@ export function getShellWidth(cols: number | undefined): number {
|
|
|
75
168
|
return Math.max(20, (cols ?? 120) - 1);
|
|
76
169
|
}
|
|
77
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Returns the width of the main content area, capped at MAX_CONTENT_WIDTH.
|
|
173
|
+
* This is used to center the UI in large terminals.
|
|
174
|
+
*/
|
|
175
|
+
export function getContentWidth(cols: number | undefined): number {
|
|
176
|
+
const shellWidth = getShellWidth(cols);
|
|
177
|
+
|
|
178
|
+
if (shellWidth < 100) return shellWidth;
|
|
179
|
+
if (shellWidth < 150) return shellWidth - 4;
|
|
180
|
+
if (shellWidth < 200) return shellWidth - 8;
|
|
181
|
+
|
|
182
|
+
return Math.min(shellWidth - 12, MAX_CONTENT_WIDTH);
|
|
183
|
+
}
|
|
184
|
+
|
|
78
185
|
export function getUsableShellWidth(cols: number | undefined, reservedColumns = 0): number {
|
|
79
186
|
return Math.max(1, getShellWidth(cols) - reservedColumns);
|
|
80
187
|
}
|
|
@@ -142,10 +249,118 @@ export function clampVisualText(text: string, maxWidth: number): string {
|
|
|
142
249
|
return output + ellipsis;
|
|
143
250
|
}
|
|
144
251
|
|
|
145
|
-
function computeMode(cols: number): LayoutMode {
|
|
146
|
-
if (cols
|
|
147
|
-
|
|
148
|
-
|
|
252
|
+
export function computeMode(cols: number, rows: number): LayoutMode {
|
|
253
|
+
if (rows <= 24 || cols <= 100) {
|
|
254
|
+
return "compact";
|
|
255
|
+
}
|
|
256
|
+
if (cols >= 140 && rows >= 30) {
|
|
257
|
+
return "expanded";
|
|
258
|
+
}
|
|
259
|
+
return "regular";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function isDecorativeLayoutMode(mode: LayoutMode): boolean {
|
|
263
|
+
return mode === "expanded";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function isCompactShellMode(mode: LayoutMode): boolean {
|
|
267
|
+
return mode === "compact" || mode === "regular";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface AppLayoutBudgetParams {
|
|
271
|
+
cols: number | undefined;
|
|
272
|
+
rows: number | undefined;
|
|
273
|
+
composerRows?: number;
|
|
274
|
+
panelHintRows?: number;
|
|
275
|
+
headerRows?: number;
|
|
276
|
+
headerGapRows?: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function computeAppLayoutBudget({
|
|
280
|
+
cols,
|
|
281
|
+
rows,
|
|
282
|
+
composerRows = 4,
|
|
283
|
+
panelHintRows = 0,
|
|
284
|
+
headerRows,
|
|
285
|
+
headerGapRows,
|
|
286
|
+
}: AppLayoutBudgetParams): AppLayoutBudget {
|
|
287
|
+
const safeCols = normalizeDimension(cols, DEFAULT_COLUMNS);
|
|
288
|
+
const safeRows = normalizeDimension(rows, DEFAULT_ROWS);
|
|
289
|
+
const mode = computeMode(safeCols, safeRows);
|
|
290
|
+
const shellHeight = getShellHeight(safeRows);
|
|
291
|
+
|
|
292
|
+
const showNormalLogo =
|
|
293
|
+
process.env["CODEXA_NO_ASCII_LOGO"] !== "1" && (
|
|
294
|
+
mode === "regular" ||
|
|
295
|
+
mode === "expanded" ||
|
|
296
|
+
(mode === "compact" && safeCols >= 72)
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const showCompactHeader = !showNormalLogo;
|
|
300
|
+
|
|
301
|
+
const placeMetadataBesideLogo =
|
|
302
|
+
showNormalLogo && safeCols >= 95;
|
|
303
|
+
|
|
304
|
+
const placeMetadataBelowLogo =
|
|
305
|
+
showNormalLogo && !placeMetadataBesideLogo;
|
|
306
|
+
|
|
307
|
+
const resolvedHeaderRows = headerRows ?? (showNormalLogo ? 6 : 1);
|
|
308
|
+
const resolvedHeaderGapRows = headerGapRows ?? (mode === "compact" ? 0 : 1);
|
|
309
|
+
const panelStagePaddingY = mode === "compact" ? 0 : 1;
|
|
310
|
+
const resolvedComposerRows = mode === "compact" ? 3 : Math.max(0, composerRows);
|
|
311
|
+
const runtimeMetadataRows = 1;
|
|
312
|
+
const transientStatusRows = 0;
|
|
313
|
+
const bottomPaddingRows = mode === "compact" ? 0 : 1;
|
|
314
|
+
const bottomChromeBudget: BottomChromeBudget = {
|
|
315
|
+
runtimeMetadataRows,
|
|
316
|
+
composerRows: resolvedComposerRows,
|
|
317
|
+
transientStatusRows,
|
|
318
|
+
bottomPaddingRows,
|
|
319
|
+
totalRows: runtimeMetadataRows + resolvedComposerRows + transientStatusRows + bottomPaddingRows,
|
|
320
|
+
};
|
|
321
|
+
const baseReservedRows =
|
|
322
|
+
resolvedHeaderRows +
|
|
323
|
+
resolvedHeaderGapRows +
|
|
324
|
+
panelStagePaddingY * 2 +
|
|
325
|
+
bottomChromeBudget.totalRows +
|
|
326
|
+
Math.max(0, panelHintRows);
|
|
327
|
+
|
|
328
|
+
const activePanelRows = Math.max(1, shellHeight - baseReservedRows);
|
|
329
|
+
const contentWidth = getContentWidth(safeCols);
|
|
330
|
+
|
|
331
|
+
const isCompact = mode === "compact";
|
|
332
|
+
const borderRows = 2;
|
|
333
|
+
const titleRows = 1;
|
|
334
|
+
const headerRowsInPanel = isCompact ? 0 : 1;
|
|
335
|
+
const panelChromeRows = borderRows + titleRows + headerRowsInPanel;
|
|
336
|
+
const innerAvailableRows = Math.max(1, activePanelRows - panelChromeRows);
|
|
337
|
+
|
|
338
|
+
const borderCols = 4;
|
|
339
|
+
const innerAvailableCols = Math.max(20, contentWidth - borderCols);
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
mode,
|
|
343
|
+
rows: safeRows,
|
|
344
|
+
cols: safeCols,
|
|
345
|
+
headerRows: resolvedHeaderRows,
|
|
346
|
+
headerGapRows: resolvedHeaderGapRows,
|
|
347
|
+
panelStagePaddingY,
|
|
348
|
+
activePanelRows: innerAvailableRows,
|
|
349
|
+
activePanelCols: innerAvailableCols,
|
|
350
|
+
bottomChromeBudget,
|
|
351
|
+
composerRows: resolvedComposerRows,
|
|
352
|
+
showNormalLogo,
|
|
353
|
+
showCompactHeader,
|
|
354
|
+
placeMetadataBesideLogo,
|
|
355
|
+
placeMetadataBelowLogo,
|
|
356
|
+
|
|
357
|
+
// Backward compatibility fields:
|
|
358
|
+
transcriptRows: activePanelRows,
|
|
359
|
+
panelRows: innerAvailableRows,
|
|
360
|
+
showLargeLogo: mode === "expanded",
|
|
361
|
+
showPanelSeparators: mode === "expanded",
|
|
362
|
+
showPanelColumnHeaders: mode === "expanded",
|
|
363
|
+
};
|
|
149
364
|
}
|
|
150
365
|
|
|
151
366
|
export function createLayoutSnapshot(
|
|
@@ -154,16 +369,28 @@ export function createLayoutSnapshot(
|
|
|
154
369
|
fallback: Layout = {
|
|
155
370
|
cols: DEFAULT_COLUMNS,
|
|
156
371
|
rows: DEFAULT_ROWS,
|
|
157
|
-
mode: computeMode(DEFAULT_COLUMNS),
|
|
372
|
+
mode: computeMode(DEFAULT_COLUMNS, DEFAULT_ROWS),
|
|
158
373
|
},
|
|
159
|
-
):
|
|
374
|
+
): TerminalViewport {
|
|
160
375
|
const nextCols = normalizeDimension(cols, fallback.cols);
|
|
161
376
|
const nextRows = normalizeDimension(rows, fallback.rows);
|
|
162
377
|
|
|
163
|
-
|
|
378
|
+
const stableLayout = {
|
|
164
379
|
cols: nextCols,
|
|
165
380
|
rows: nextRows,
|
|
166
|
-
mode: computeMode(nextCols),
|
|
381
|
+
mode: computeMode(nextCols, nextRows),
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const isCramped = isCrampedTerminal(nextCols, nextRows);
|
|
385
|
+
const contentWidth = getContentWidth(nextCols);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
...stableLayout,
|
|
389
|
+
contentWidth,
|
|
390
|
+
isCramped,
|
|
391
|
+
unstable: false,
|
|
392
|
+
layoutEpoch: 0,
|
|
393
|
+
isResizing: false,
|
|
167
394
|
};
|
|
168
395
|
}
|
|
169
396
|
|
|
@@ -175,6 +402,7 @@ export function createTerminalViewport(
|
|
|
175
402
|
cols: number | undefined,
|
|
176
403
|
rows: number | undefined,
|
|
177
404
|
fallback?: TerminalViewport,
|
|
405
|
+
isResizing = false,
|
|
178
406
|
): TerminalViewport {
|
|
179
407
|
const fallbackLayout = fallback
|
|
180
408
|
? { cols: fallback.cols, rows: fallback.rows, mode: fallback.mode }
|
|
@@ -184,12 +412,18 @@ export function createTerminalViewport(
|
|
|
184
412
|
? fallbackLayout
|
|
185
413
|
: createLayoutSnapshot(cols, rows, fallbackLayout);
|
|
186
414
|
|
|
415
|
+
const isCramped = isCrampedTerminal(cols, rows);
|
|
416
|
+
const contentWidth = getContentWidth(stableLayout.cols);
|
|
417
|
+
|
|
187
418
|
return {
|
|
188
419
|
...stableLayout,
|
|
189
420
|
rawCols: cols,
|
|
190
421
|
rawRows: rows,
|
|
422
|
+
contentWidth,
|
|
423
|
+
isCramped,
|
|
191
424
|
unstable,
|
|
192
425
|
layoutEpoch: fallback?.layoutEpoch ?? 0,
|
|
426
|
+
isResizing,
|
|
193
427
|
};
|
|
194
428
|
}
|
|
195
429
|
|
|
@@ -197,8 +431,21 @@ export function advanceTerminalViewport(
|
|
|
197
431
|
current: TerminalViewport,
|
|
198
432
|
cols: number | undefined,
|
|
199
433
|
rows: number | undefined,
|
|
434
|
+
isResizing = false,
|
|
200
435
|
): TerminalViewport {
|
|
201
|
-
const next = createTerminalViewport(cols, rows, current);
|
|
436
|
+
const next = createTerminalViewport(cols, rows, current, isResizing);
|
|
437
|
+
|
|
438
|
+
if (process.env.CODEXA_LAYOUT_DEBUG === "1") {
|
|
439
|
+
renderDebug.traceEvent("layout", "advanceViewport", {
|
|
440
|
+
cols: next.cols,
|
|
441
|
+
rows: next.rows,
|
|
442
|
+
contentWidth: next.contentWidth,
|
|
443
|
+
isCramped: next.isCramped,
|
|
444
|
+
isResizing: next.isResizing,
|
|
445
|
+
mode: next.mode,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
202
449
|
if (!next.unstable && current.unstable) {
|
|
203
450
|
return {
|
|
204
451
|
...next,
|
|
@@ -221,9 +468,9 @@ export function useTerminalViewport(): TerminalViewport {
|
|
|
221
468
|
const settleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
222
469
|
|
|
223
470
|
useEffect(() => {
|
|
224
|
-
const commit = () => {
|
|
471
|
+
const commit = (isResizing = false) => {
|
|
225
472
|
setViewport((current) => {
|
|
226
|
-
const nextViewport = advanceTerminalViewport(current, stdout.columns, stdout.rows);
|
|
473
|
+
const nextViewport = advanceTerminalViewport(current, stdout.columns, stdout.rows, isResizing);
|
|
227
474
|
if (
|
|
228
475
|
current.cols === nextViewport.cols &&
|
|
229
476
|
current.rows === nextViewport.rows &&
|
|
@@ -231,7 +478,8 @@ export function useTerminalViewport(): TerminalViewport {
|
|
|
231
478
|
current.unstable === nextViewport.unstable &&
|
|
232
479
|
current.layoutEpoch === nextViewport.layoutEpoch &&
|
|
233
480
|
current.rawCols === nextViewport.rawCols &&
|
|
234
|
-
current.rawRows === nextViewport.rawRows
|
|
481
|
+
current.rawRows === nextViewport.rawRows &&
|
|
482
|
+
current.isResizing === nextViewport.isResizing
|
|
235
483
|
) {
|
|
236
484
|
renderDebug.traceFlickerEvent("measurementUpdate", {
|
|
237
485
|
result: "skipped",
|
|
@@ -239,6 +487,7 @@ export function useTerminalViewport(): TerminalViewport {
|
|
|
239
487
|
rows: nextViewport.rows,
|
|
240
488
|
mode: nextViewport.mode,
|
|
241
489
|
unstable: nextViewport.unstable,
|
|
490
|
+
isResizing: nextViewport.isResizing,
|
|
242
491
|
});
|
|
243
492
|
return current;
|
|
244
493
|
}
|
|
@@ -249,6 +498,7 @@ export function useTerminalViewport(): TerminalViewport {
|
|
|
249
498
|
rows: nextViewport.rows,
|
|
250
499
|
mode: nextViewport.mode,
|
|
251
500
|
unstable: nextViewport.unstable,
|
|
501
|
+
isResizing: nextViewport.isResizing,
|
|
252
502
|
});
|
|
253
503
|
return nextViewport;
|
|
254
504
|
});
|
|
@@ -264,13 +514,22 @@ export function useTerminalViewport(): TerminalViewport {
|
|
|
264
514
|
rawCols: stdout.columns,
|
|
265
515
|
rawRows: stdout.rows,
|
|
266
516
|
});
|
|
267
|
-
|
|
517
|
+
|
|
518
|
+
// Leading edge: immediately enter isResizing state but do NOT commit
|
|
519
|
+
// new dimensions yet. This freezes the layout to prevent tearing while
|
|
520
|
+
// dragging, and signals animations to pause.
|
|
521
|
+
setTerminalResizing(true);
|
|
522
|
+
setViewport((current) => ({ ...current, isResizing: true }));
|
|
523
|
+
|
|
268
524
|
if (settleTimerRef.current) {
|
|
269
525
|
clearTimeout(settleTimerRef.current);
|
|
270
526
|
}
|
|
527
|
+
|
|
271
528
|
settleTimerRef.current = setTimeout(() => {
|
|
272
529
|
settleTimerRef.current = null;
|
|
273
|
-
|
|
530
|
+
setTerminalResizing(false);
|
|
531
|
+
// Trailing edge: commit final dimensions and exit isResizing state.
|
|
532
|
+
commit(false);
|
|
274
533
|
}, RESTORE_SETTLE_MS);
|
|
275
534
|
};
|
|
276
535
|
|
|
@@ -285,3 +544,18 @@ export function useTerminalViewport(): TerminalViewport {
|
|
|
285
544
|
|
|
286
545
|
return viewport;
|
|
287
546
|
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Calculate available vertical rows for active panels, falling back to a layout-based
|
|
550
|
+
* budget if availableRows is not explicitly provided.
|
|
551
|
+
*/
|
|
552
|
+
export function getAvailableRowsForPanel(
|
|
553
|
+
layout: Layout,
|
|
554
|
+
passedAvailableRows?: number
|
|
555
|
+
): number {
|
|
556
|
+
if (passedAvailableRows !== undefined) {
|
|
557
|
+
return passedAvailableRows;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return computeAppLayoutBudget({ cols: layout.cols, rows: layout.rows }).panelRows;
|
|
561
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for windowing large lists in limited terminal rows.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ListWindowOptions {
|
|
6
|
+
itemCount: number;
|
|
7
|
+
selectedIndex: number;
|
|
8
|
+
availableRows: number;
|
|
9
|
+
/** Fixed rows consumed by headers, borders, titles, etc. */
|
|
10
|
+
chromeRows: number;
|
|
11
|
+
/** Whether to show "↑ N more" and "↓ N more" indicators. */
|
|
12
|
+
showIndicators?: boolean;
|
|
13
|
+
/** Whether to show "Showing X-Y of N" range line. */
|
|
14
|
+
showRangeLine?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ListWindowResult {
|
|
18
|
+
start: number;
|
|
19
|
+
end: number;
|
|
20
|
+
visibleCount: number;
|
|
21
|
+
showAbove: boolean;
|
|
22
|
+
showBelow: boolean;
|
|
23
|
+
showRange: boolean;
|
|
24
|
+
hiddenAbove: number;
|
|
25
|
+
hiddenBelow: number;
|
|
26
|
+
/** Total rows consumed including chrome and indicators. */
|
|
27
|
+
totalRows: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Calculates a contiguous window of items that fits within available rows,
|
|
32
|
+
* ensuring the selected item is always visible.
|
|
33
|
+
*/
|
|
34
|
+
export function calculateListWindow({
|
|
35
|
+
itemCount,
|
|
36
|
+
selectedIndex,
|
|
37
|
+
availableRows,
|
|
38
|
+
chromeRows,
|
|
39
|
+
showIndicators = true,
|
|
40
|
+
showRangeLine = false,
|
|
41
|
+
}: ListWindowOptions): ListWindowResult {
|
|
42
|
+
if (itemCount === 0) {
|
|
43
|
+
return {
|
|
44
|
+
start: 0,
|
|
45
|
+
end: 0,
|
|
46
|
+
visibleCount: 0,
|
|
47
|
+
showAbove: false,
|
|
48
|
+
showBelow: false,
|
|
49
|
+
showRange: false,
|
|
50
|
+
hiddenAbove: 0,
|
|
51
|
+
hiddenBelow: 0,
|
|
52
|
+
totalRows: chromeRows,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rangeReserved = showRangeLine ? 1 : 0;
|
|
57
|
+
|
|
58
|
+
// If everything fits including the range line, just show everything.
|
|
59
|
+
if (itemCount + chromeRows + rangeReserved <= availableRows) {
|
|
60
|
+
return {
|
|
61
|
+
start: 0,
|
|
62
|
+
end: itemCount,
|
|
63
|
+
visibleCount: itemCount,
|
|
64
|
+
showAbove: false,
|
|
65
|
+
showBelow: false,
|
|
66
|
+
showRange: showRangeLine,
|
|
67
|
+
hiddenAbove: 0,
|
|
68
|
+
hiddenBelow: 0,
|
|
69
|
+
totalRows: itemCount + chromeRows + rangeReserved,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Otherwise we must window.
|
|
74
|
+
// We reserve space for indicators if requested.
|
|
75
|
+
// We MUST fit within availableRows.
|
|
76
|
+
const indicatorReserved = showIndicators ? 2 : 0;
|
|
77
|
+
const baseReserved = chromeRows + indicatorReserved + rangeReserved;
|
|
78
|
+
|
|
79
|
+
let itemRows = Math.max(1, availableRows - baseReserved);
|
|
80
|
+
let start = 0;
|
|
81
|
+
let end = 0;
|
|
82
|
+
|
|
83
|
+
// Iteratively refine to account for dynamic indicators.
|
|
84
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
85
|
+
const visibleCount = Math.max(1, Math.min(itemCount, itemRows));
|
|
86
|
+
|
|
87
|
+
// Center selected item if possible
|
|
88
|
+
start = Math.max(0, selectedIndex - Math.floor(visibleCount / 2));
|
|
89
|
+
if (start + visibleCount > itemCount) {
|
|
90
|
+
start = Math.max(0, itemCount - visibleCount);
|
|
91
|
+
}
|
|
92
|
+
end = Math.min(itemCount, start + visibleCount);
|
|
93
|
+
|
|
94
|
+
const actualAbove = start > 0;
|
|
95
|
+
const actualBelow = end < itemCount;
|
|
96
|
+
const dynamicReserved = chromeRows
|
|
97
|
+
+ (showRangeLine ? 1 : 0)
|
|
98
|
+
+ (showIndicators && actualAbove ? 1 : 0)
|
|
99
|
+
+ (showIndicators && actualBelow ? 1 : 0);
|
|
100
|
+
|
|
101
|
+
const nextItemRows = Math.max(1, availableRows - dynamicReserved);
|
|
102
|
+
if (nextItemRows === itemRows) break;
|
|
103
|
+
itemRows = nextItemRows;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Final visibility check for selectedIndex
|
|
107
|
+
if (selectedIndex < start) {
|
|
108
|
+
start = selectedIndex;
|
|
109
|
+
end = Math.min(itemCount, start + itemRows);
|
|
110
|
+
} else if (selectedIndex >= end) {
|
|
111
|
+
end = selectedIndex + 1;
|
|
112
|
+
start = Math.max(0, end - itemRows);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Final clamping to ensure we don't exceed availableRows if indicators are shown
|
|
116
|
+
let finalAbove = showIndicators && start > 0;
|
|
117
|
+
let finalBelow = showIndicators && end < itemCount;
|
|
118
|
+
|
|
119
|
+
while (end - start + chromeRows + (showRangeLine ? 1 : 0) + (finalAbove ? 1 : 0) + (finalBelow ? 1 : 0) > availableRows && end - start > 1) {
|
|
120
|
+
// Shrink window from the side further from selectedIndex
|
|
121
|
+
if (selectedIndex - start > end - 1 - selectedIndex) {
|
|
122
|
+
start++;
|
|
123
|
+
} else {
|
|
124
|
+
end--;
|
|
125
|
+
}
|
|
126
|
+
finalAbove = showIndicators && start > 0;
|
|
127
|
+
finalBelow = showIndicators && end < itemCount;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
start,
|
|
132
|
+
end,
|
|
133
|
+
visibleCount: end - start,
|
|
134
|
+
showAbove: finalAbove,
|
|
135
|
+
showBelow: finalBelow,
|
|
136
|
+
showRange: showRangeLine,
|
|
137
|
+
hiddenAbove: start,
|
|
138
|
+
hiddenBelow: itemCount - end,
|
|
139
|
+
totalRows: (end - start)
|
|
140
|
+
+ chromeRows
|
|
141
|
+
+ (showRangeLine ? 1 : 0)
|
|
142
|
+
+ (finalAbove ? 1 : 0)
|
|
143
|
+
+ (finalBelow ? 1 : 0),
|
|
144
|
+
};
|
|
145
|
+
}
|
package/src/ui/logoVariants.ts
CHANGED
|
@@ -10,8 +10,8 @@ import { getTextWidth } from "./textLayout.js";
|
|
|
10
10
|
// The companion `wrap="truncate"` rule keeps each row on exactly one terminal
|
|
11
11
|
// line regardless of the surrounding Ink flex layout.
|
|
12
12
|
|
|
13
|
-
// Canonical Codexa brand wordmark — the ██ block art is the authoritative
|
|
14
|
-
//
|
|
13
|
+
// Canonical Codexa brand wordmark — the ██ block art is the authoritative
|
|
14
|
+
// large logo for wide/max layouts.
|
|
15
15
|
export const CODEXA_WORDMARK = [
|
|
16
16
|
" ██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗ █████╗ ",
|
|
17
17
|
"██╔════╝██╔═══██╗██╔══██╗██╔════╝╚██╗██╔╝██╔══██╗",
|
|
@@ -48,16 +48,13 @@ export const LOGO_COMPACT_MIN_COLS = 48;
|
|
|
48
48
|
// Minimum terminal rows each variant needs to render without crowding out the
|
|
49
49
|
// metadata + composer. A wide-but-short terminal (e.g. VS Code's bottom panel)
|
|
50
50
|
// must step DOWN to a smaller logo instead of dropping straight to text-only.
|
|
51
|
-
export const LOGO_LARGE_MIN_ROWS =
|
|
51
|
+
export const LOGO_LARGE_MIN_ROWS = 35;
|
|
52
52
|
export const LOGO_MEDIUM_MIN_ROWS = 16;
|
|
53
53
|
export const LOGO_COMPACT_MIN_ROWS = 12;
|
|
54
54
|
|
|
55
|
-
// LOGO_MEDIUM is kept as an exported constant but intentionally omitted from
|
|
56
|
-
// LOGO_VARIANTS. At 72+ cols, LOGO_LARGE_MIN_COLS = LOGO_MEDIUM_MIN_COLS = 72,
|
|
57
|
-
// so LOGO_LARGE always wins. Below 72 cols the viewport is too narrow for the
|
|
58
|
-
// thin ASCII art to add value over the compact single-line fallback.
|
|
59
55
|
const LOGO_VARIANTS: readonly { logo: readonly string[]; minCols: number; minRows: number }[] = [
|
|
60
56
|
{ logo: LOGO_LARGE, minCols: LOGO_LARGE_MIN_COLS, minRows: LOGO_LARGE_MIN_ROWS },
|
|
57
|
+
{ logo: LOGO_MEDIUM, minCols: LOGO_MEDIUM_MIN_COLS, minRows: LOGO_MEDIUM_MIN_ROWS },
|
|
61
58
|
{ logo: LOGO_COMPACT, minCols: LOGO_COMPACT_MIN_COLS, minRows: LOGO_COMPACT_MIN_ROWS },
|
|
62
59
|
];
|
|
63
60
|
|
|
@@ -92,7 +89,6 @@ export function selectLogoVariantForViewport(cols: number, rows: number): readon
|
|
|
92
89
|
if (process.env["CODEXA_COMPACT_LOGO"] === "1") {
|
|
93
90
|
return rows >= LOGO_COMPACT_MIN_ROWS ? LOGO_COMPACT : [];
|
|
94
91
|
}
|
|
95
|
-
if (cols >= LOGO_LARGE_MIN_COLS) return LOGO_LARGE;
|
|
96
92
|
for (const variant of LOGO_VARIANTS) {
|
|
97
93
|
if (cols >= variant.minCols && rows >= variant.minRows) {
|
|
98
94
|
return variant.logo;
|
package/src/ui/runtimeDisplay.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ModelSpec } from "../core/models/modelSpecs.js";
|
|
|
5
5
|
import type { ModelContextMetadata } from "../core/providerRuntime/contextMetadata.js";
|
|
6
6
|
import { contextMetadataToModelSpec, formatContextCompact } from "../core/providerRuntime/contextMetadata.js";
|
|
7
7
|
import type { ActiveProviderRoute } from "../core/providerRuntime/types.js";
|
|
8
|
+
import { getAntigravityModelLabel } from "../core/providerRuntime/antigravity.js";
|
|
8
9
|
|
|
9
10
|
export interface ActiveRuntimeDisplayInput {
|
|
10
11
|
route: ActiveProviderRoute;
|
|
@@ -29,6 +30,7 @@ const PROVIDER_DISPLAY: Record<string, string> = {
|
|
|
29
30
|
anthropic: "Claude Code CLI",
|
|
30
31
|
google: "Gemini CLI",
|
|
31
32
|
local: "Local",
|
|
33
|
+
antigravity: "Antigravity CLI",
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
function formatContextLimit(value: number): string {
|
|
@@ -53,6 +55,9 @@ function getModelLabel(route: ActiveProviderRoute, capability?: CodexModelCapabi
|
|
|
53
55
|
if (route.providerId === "anthropic") {
|
|
54
56
|
return capability?.label ?? route.modelId;
|
|
55
57
|
}
|
|
58
|
+
if (route.providerId === "antigravity") {
|
|
59
|
+
return getAntigravityModelLabel(route.modelId);
|
|
60
|
+
}
|
|
56
61
|
return route.modelId;
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -65,7 +70,10 @@ export function buildActiveRuntimeDisplay({
|
|
|
65
70
|
contextMetadata = null,
|
|
66
71
|
}: ActiveRuntimeDisplayInput): ActiveRuntimeDisplay {
|
|
67
72
|
const providerLabel = PROVIDER_DISPLAY[route.providerId] ?? route.providerId;
|
|
68
|
-
const
|
|
73
|
+
const rawReasoning = route.providerId === "antigravity"
|
|
74
|
+
? route.reasoning
|
|
75
|
+
: route.reasoning ?? reasoningLevel;
|
|
76
|
+
const reasoning = rawReasoning ? formatReasoningLabel(rawReasoning) : null;
|
|
69
77
|
const modelLabel = getModelLabel(route, modelCapability);
|
|
70
78
|
const validContextMetadata = isContextForRoute(contextMetadata, route) ? contextMetadata : null;
|
|
71
79
|
const contextDisplay = validContextMetadata?.contextLength != null
|
|
@@ -82,8 +90,12 @@ export function buildActiveRuntimeDisplay({
|
|
|
82
90
|
|
|
83
91
|
return {
|
|
84
92
|
providerLabel,
|
|
85
|
-
modelDisplay:
|
|
86
|
-
|
|
93
|
+
modelDisplay: reasoning
|
|
94
|
+
? `${providerLabel} / ${modelLabel} / reasoning: ${reasoning}`
|
|
95
|
+
: `${providerLabel} / ${modelLabel}`,
|
|
96
|
+
footerModelDisplay: reasoning
|
|
97
|
+
? `${providerLabel} / ${modelLabel} (${reasoning})`
|
|
98
|
+
: `${providerLabel} / ${modelLabel}`,
|
|
87
99
|
contextDisplay,
|
|
88
100
|
modeLabel: formatModeLabel(mode),
|
|
89
101
|
modelSpec,
|