@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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text, useFocus, useInput } from "ink";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { useTheme } from "./theme.js";
|
|
5
|
+
import { CODEXA_NPM_PACKAGE, CODEXA_UPDATE_COMMAND, formatVersionLabel } from "../core/version/updateCheck.js";
|
|
6
|
+
|
|
7
|
+
type Phase = "menu" | "running" | "done" | "error";
|
|
8
|
+
|
|
9
|
+
const MENU_ITEMS = [
|
|
10
|
+
{ label: "Update now" },
|
|
11
|
+
{ label: "Skip" },
|
|
12
|
+
{ label: "Skip until next version" },
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
interface UpdatePromptPanelProps {
|
|
16
|
+
focusId: string;
|
|
17
|
+
currentVersion: string;
|
|
18
|
+
latestVersion: string;
|
|
19
|
+
onSkip: () => void;
|
|
20
|
+
onSkipUntilNextVersion: (version: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function UpdatePromptPanel({
|
|
24
|
+
focusId,
|
|
25
|
+
currentVersion,
|
|
26
|
+
latestVersion,
|
|
27
|
+
onSkip,
|
|
28
|
+
onSkipUntilNextVersion,
|
|
29
|
+
}: UpdatePromptPanelProps) {
|
|
30
|
+
const theme = useTheme();
|
|
31
|
+
const { isFocused } = useFocus({ id: focusId, autoFocus: true });
|
|
32
|
+
|
|
33
|
+
const [phase, setPhase] = useState<Phase>("menu");
|
|
34
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
35
|
+
const [outputLines, setOutputLines] = useState<string[]>([]);
|
|
36
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
37
|
+
|
|
38
|
+
const spawnStartedRef = useRef(false);
|
|
39
|
+
|
|
40
|
+
useInput((input, key) => {
|
|
41
|
+
if (key.escape) {
|
|
42
|
+
onSkip();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (phase === "menu") {
|
|
46
|
+
if (key.upArrow || input === "k") {
|
|
47
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (key.downArrow || input === "j") {
|
|
51
|
+
setSelectedIndex((i) => Math.min(MENU_ITEMS.length - 1, i + 1));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (key.return) {
|
|
55
|
+
if (selectedIndex === 0) {
|
|
56
|
+
setPhase("running");
|
|
57
|
+
} else if (selectedIndex === 1) {
|
|
58
|
+
onSkip();
|
|
59
|
+
} else {
|
|
60
|
+
onSkipUntilNextVersion(latestVersion);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
} else if (phase === "done" || phase === "error") {
|
|
65
|
+
if (key.return) {
|
|
66
|
+
onSkip();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, { isActive: isFocused });
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (phase !== "running") return;
|
|
73
|
+
if (spawnStartedRef.current) return;
|
|
74
|
+
spawnStartedRef.current = true;
|
|
75
|
+
|
|
76
|
+
const child = spawn("npm", ["install", "-g", `${CODEXA_NPM_PACKAGE}@latest`], {
|
|
77
|
+
shell: false,
|
|
78
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const appendLines = (buf: Buffer) => {
|
|
82
|
+
const lines = buf.toString("utf8").split(/\r?\n/).filter(Boolean);
|
|
83
|
+
if (lines.length > 0) {
|
|
84
|
+
setOutputLines((prev) => [...prev, ...lines]);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
child.stdout?.on("data", appendLines);
|
|
89
|
+
child.stderr?.on("data", appendLines);
|
|
90
|
+
|
|
91
|
+
child.once("error", (err: Error) => {
|
|
92
|
+
setErrorMessage(err.message);
|
|
93
|
+
setPhase("error");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
child.once("close", (code: number | null) => {
|
|
97
|
+
if (code === 0) {
|
|
98
|
+
setPhase("done");
|
|
99
|
+
} else {
|
|
100
|
+
setErrorMessage(`npm exited with code ${code ?? "unknown"}.`);
|
|
101
|
+
setPhase("error");
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
try { child.kill(); } catch { /* ignore */ }
|
|
107
|
+
};
|
|
108
|
+
}, [phase]);
|
|
109
|
+
|
|
110
|
+
const footerText = phase === "menu"
|
|
111
|
+
? "Esc to close · Enter to confirm"
|
|
112
|
+
: "Esc to close";
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Box flexDirection="column" width="100%" marginTop={1}>
|
|
116
|
+
<Box
|
|
117
|
+
borderStyle="round"
|
|
118
|
+
borderColor={theme.border}
|
|
119
|
+
paddingX={2}
|
|
120
|
+
paddingY={1}
|
|
121
|
+
width="100%"
|
|
122
|
+
flexDirection="column"
|
|
123
|
+
>
|
|
124
|
+
<Box>
|
|
125
|
+
<Text color={theme.accent} bold>{`Update available: Codexa ${formatVersionLabel(latestVersion)}`}</Text>
|
|
126
|
+
</Box>
|
|
127
|
+
<Box marginTop={1}>
|
|
128
|
+
<Text color={theme.text}>{`${formatVersionLabel(currentVersion)} -> ${formatVersionLabel(latestVersion)}`}</Text>
|
|
129
|
+
</Box>
|
|
130
|
+
<Box>
|
|
131
|
+
<Text color={theme.textMuted}>{`Package: ${CODEXA_NPM_PACKAGE}`}</Text>
|
|
132
|
+
</Box>
|
|
133
|
+
<Box>
|
|
134
|
+
<Text color={theme.textMuted}>{`Run: ${CODEXA_UPDATE_COMMAND}`}</Text>
|
|
135
|
+
</Box>
|
|
136
|
+
</Box>
|
|
137
|
+
|
|
138
|
+
<Box
|
|
139
|
+
borderStyle="round"
|
|
140
|
+
borderColor={phase === "menu" ? theme.borderFocused : theme.border}
|
|
141
|
+
paddingX={2}
|
|
142
|
+
paddingY={1}
|
|
143
|
+
marginTop={1}
|
|
144
|
+
width="100%"
|
|
145
|
+
flexDirection="column"
|
|
146
|
+
>
|
|
147
|
+
{phase === "menu" && (
|
|
148
|
+
<>
|
|
149
|
+
{MENU_ITEMS.map((item, index) => (
|
|
150
|
+
<Box key={item.label}>
|
|
151
|
+
<Text color={index === selectedIndex ? theme.accent : theme.textMuted}>
|
|
152
|
+
{index === selectedIndex ? "› " : " "}
|
|
153
|
+
</Text>
|
|
154
|
+
<Text
|
|
155
|
+
color={index === selectedIndex ? theme.text : theme.textMuted}
|
|
156
|
+
bold={index === selectedIndex}
|
|
157
|
+
>
|
|
158
|
+
{`${index + 1}. ${item.label}`}
|
|
159
|
+
</Text>
|
|
160
|
+
</Box>
|
|
161
|
+
))}
|
|
162
|
+
</>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{phase === "running" && (
|
|
166
|
+
<>
|
|
167
|
+
<Text color={theme.text}>{`Installing Codexa ${latestVersion}...`}</Text>
|
|
168
|
+
{outputLines.map((line, i) => (
|
|
169
|
+
<Text key={i} color={theme.textMuted}>{line}</Text>
|
|
170
|
+
))}
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{phase === "done" && (
|
|
175
|
+
<>
|
|
176
|
+
<Text color={theme.success}>{"Codexa was updated successfully."}</Text>
|
|
177
|
+
<Text color={theme.textMuted}>{"Restart Codexa to use the new version."}</Text>
|
|
178
|
+
</>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{phase === "error" && (
|
|
182
|
+
<>
|
|
183
|
+
<Text color={theme.error}>{"Update failed."}</Text>
|
|
184
|
+
{errorMessage != null && <Text color={theme.textMuted}>{errorMessage}</Text>}
|
|
185
|
+
{outputLines.slice(-5).map((line, i) => (
|
|
186
|
+
<Text key={i} color={theme.textDim}>{line}</Text>
|
|
187
|
+
))}
|
|
188
|
+
</>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
<Box marginTop={1}>
|
|
192
|
+
<Text color={theme.textDim}>{footerText}</Text>
|
|
193
|
+
</Box>
|
|
194
|
+
</Box>
|
|
195
|
+
</Box>
|
|
196
|
+
);
|
|
197
|
+
}
|
package/src/ui/focus.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const FOCUS_IDS = {
|
|
|
18
18
|
permissionsAddWritableRoot: "permissions-add-writable-root",
|
|
19
19
|
permissionsRemoveWritableRoot: "permissions-remove-writable-root",
|
|
20
20
|
importConfirmationPanel: "import-confirmation",
|
|
21
|
+
updatePrompt: "update-prompt",
|
|
21
22
|
} as const;
|
|
22
23
|
|
|
23
24
|
export type FocusTargetId = (typeof FOCUS_IDS)[keyof typeof FOCUS_IDS];
|
|
@@ -54,6 +55,8 @@ export function getFocusTargetForScreen(screen: Screen): FocusTargetId {
|
|
|
54
55
|
return FOCUS_IDS.permissionsRemoveWritableRoot;
|
|
55
56
|
case "import-confirmation":
|
|
56
57
|
return FOCUS_IDS.importConfirmationPanel;
|
|
58
|
+
case "update-prompt":
|
|
59
|
+
return FOCUS_IDS.updatePrompt;
|
|
57
60
|
case "main":
|
|
58
61
|
default:
|
|
59
62
|
return FOCUS_IDS.composer;
|
package/src/ui/layout.ts
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
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
|
-
export const STARTUP_FULL_MIN_COLS =
|
|
35
|
+
export const STARTUP_FULL_MIN_COLS = 100; // matches LOGO_LARGE_MIN_COLS in logoVariants.ts
|
|
24
36
|
export const STARTUP_FULL_MIN_BODY_ROWS = 4;
|
|
25
37
|
export const STARTUP_FULL_SAFE_PADDING_ROWS = 1;
|
|
26
38
|
export const STARTUP_COMPACT_INTRO_ROWS = 4;
|
|
@@ -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
|
+
}
|