@golba98/codexa 1.0.3 → 1.0.5
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 +642 -303
- package/src/commands/handler.ts +7 -18
- 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/models/codexModelCapabilities.ts +57 -4
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/providerModelCache.ts +106 -0
- package/src/core/perf/renderDebug.ts +10 -6
- package/src/core/process/CommandRunner.ts +12 -1
- package/src/core/providerLauncher/registry.ts +64 -18
- package/src/core/providerLauncher/types.ts +12 -9
- package/src/core/providerLauncher/workspaceConfig.ts +41 -26
- package/src/core/providerRuntime/anthropic.ts +10 -6
- package/src/core/providerRuntime/antigravity.ts +461 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -446
- 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/mistralVibe.ts +663 -0
- package/src/core/providerRuntime/models.ts +40 -15
- package/src/core/providerRuntime/reasoning.ts +9 -6
- package/src/core/providerRuntime/registry.ts +76 -4
- package/src/core/providerRuntime/types.ts +20 -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 +66 -98
- 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/test/runtimeTestUtils.ts +90 -0
- package/src/ui/{ActivityBars.tsx → chrome/ActivityBars.tsx} +1 -1
- package/src/ui/{ActivityIndicator.tsx → chrome/ActivityIndicator.tsx} +3 -3
- package/src/ui/{AnimatedStatusText.tsx → chrome/AnimatedStatusText.tsx} +3 -3
- package/src/ui/chrome/AppShell.tsx +672 -0
- package/src/ui/{BottomComposer.tsx → chrome/BottomComposer.tsx} +159 -158
- package/src/ui/{DashCard.tsx → chrome/DashCard.tsx} +2 -2
- package/src/ui/{RunFooter.tsx → chrome/RunFooter.tsx} +3 -3
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/{Spinner.tsx → chrome/Spinner.tsx} +1 -1
- package/src/ui/{TopHeader.tsx → chrome/TopHeader.tsx} +54 -38
- package/src/ui/{UpdateAvailableCard.tsx → chrome/UpdateAvailableCard.tsx} +5 -5
- package/src/ui/{commandNormalize.ts → input/commandNormalize.ts} +1 -1
- package/src/ui/{focus.ts → input/focus.ts} +1 -1
- package/src/ui/{inputBuffer.ts → input/inputBuffer.ts} +3 -3
- package/src/ui/layout.ts +298 -24
- package/src/ui/{AttachmentImportPanel.tsx → panels/AttachmentImportPanel.tsx} +3 -3
- package/src/ui/{AuthPanel.tsx → panels/AuthPanel.tsx} +5 -5
- package/src/ui/{BackendPicker.tsx → panels/BackendPicker.tsx} +2 -2
- package/src/ui/{ModePicker.tsx → panels/ModePicker.tsx} +2 -2
- package/src/ui/{ModelPicker.tsx → panels/ModelPicker.tsx} +2 -2
- package/src/ui/{ModelPickerScreen.tsx → panels/ModelPickerScreen.tsx} +224 -42
- package/src/ui/{ModelReasoningPicker.tsx → panels/ModelReasoningPicker.tsx} +5 -5
- package/src/ui/{Panel.tsx → panels/Panel.tsx} +1 -1
- package/src/ui/{PermissionsPanel.tsx → panels/PermissionsPanel.tsx} +3 -3
- package/src/ui/{PlanActionPicker.tsx → panels/PlanActionPicker.tsx} +2 -2
- package/src/ui/{PlanReviewPanel.tsx → panels/PlanReviewPanel.tsx} +5 -5
- package/src/ui/panels/ProviderPicker.tsx +737 -0
- package/src/ui/{ReasoningPicker.tsx → panels/ReasoningPicker.tsx} +3 -3
- package/src/ui/{SelectionPanel.tsx → panels/SelectionPanel.tsx} +5 -1
- package/src/ui/{SettingsPanel.tsx → panels/SettingsPanel.tsx} +4 -4
- package/src/ui/{TextEntryPanel.tsx → panels/TextEntryPanel.tsx} +4 -4
- package/src/ui/{ThemePicker.tsx → panels/ThemePicker.tsx} +2 -2
- package/src/ui/{UpdatePromptPanel.tsx → panels/UpdatePromptPanel.tsx} +17 -23
- package/src/ui/{Markdown.tsx → render/Markdown.tsx} +2 -2
- package/src/ui/{diffRenderer.ts → render/diffRenderer.ts} +1 -1
- package/src/ui/{logoVariants.ts → render/logoVariants.ts} +4 -8
- package/src/ui/{modeDisplay.ts → render/modeDisplay.ts} +2 -2
- package/src/ui/{outputPipeline.ts → render/outputPipeline.ts} +2 -2
- package/src/ui/{runtimeDisplay.ts → render/runtimeDisplay.ts} +23 -10
- package/src/ui/{textLayout.ts → render/textLayout.ts} +15 -4
- package/src/ui/{ActionRequiredBlock.tsx → timeline/ActionRequiredBlock.tsx} +3 -3
- package/src/ui/{AgentBlock.tsx → timeline/AgentBlock.tsx} +10 -10
- package/src/ui/{StaticIntroItem.tsx → timeline/StaticIntroItem.tsx} +2 -2
- package/src/ui/{ThinkingBlock.tsx → timeline/ThinkingBlock.tsx} +4 -4
- package/src/ui/{Timeline.tsx → timeline/Timeline.tsx} +1620 -1471
- package/src/ui/timeline/TranscriptShell.tsx +322 -0
- package/src/ui/{TurnGroup.tsx → timeline/TurnGroup.tsx} +15 -15
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/{progressEntries.ts → timeline/progressEntries.ts} +3 -3
- package/src/ui/{runActivityView.ts → timeline/runActivityView.ts} +1 -1
- package/src/ui/{timelineMeasure.ts → timeline/timelineMeasure.ts} +206 -134
- package/src/core/codex.ts +0 -124
- package/src/ui/AppShell.tsx +0 -706
- package/src/ui/ProviderPicker.tsx +0 -321
- 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/{busyStatusAnimation.ts → chrome/busyStatusAnimation.ts} +0 -0
- /package/src/ui/{slashCommands.ts → input/slashCommands.ts} +0 -0
- /package/src/ui/{terminalAnswerFormat.ts → render/terminalAnswerFormat.ts} +0 -0
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "
|
|
4
|
-
import { formatCodexaBrandLabel } from "
|
|
5
|
-
import
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
3
|
+
import { HEADER_CONFIG_DEFAULTS, type HeaderConfig } from "../../config/settings.js";
|
|
4
|
+
import { formatCodexaBrandLabel } from "../../core/version/channel.js";
|
|
5
|
+
import { formatVersionLabel } from "../../core/version/updateCheck.js";
|
|
6
|
+
import type { RuntimeSummary } from "../../config/runtimeConfig.js";
|
|
7
|
+
import type { CodexAuthState } from "../../core/auth/codexAuth.js";
|
|
8
|
+
import { getAuthStateLabel } from "../../core/auth/codexAuth.js";
|
|
9
|
+
import * as renderDebug from "../../core/perf/renderDebug.js";
|
|
10
|
+
import { useTheme } from "../theme.js";
|
|
11
|
+
import { clampVisualText, isDecorativeLayoutMode, type Layout, useAppLayoutBudget } from "../layout.js";
|
|
12
|
+
import { getTextWidth } from "../render/textLayout.js";
|
|
12
13
|
import { UPDATE_CARD_ROWS, UpdateAvailableCard } from "./UpdateAvailableCard.js";
|
|
13
14
|
import {
|
|
14
15
|
LOGO_LARGE,
|
|
16
|
+
LOGO_COMPACT,
|
|
15
17
|
LOGO_COMPACT_MIN_COLS,
|
|
16
18
|
LOGO_LARGE_MIN_COLS,
|
|
17
19
|
LOGO_LARGE_MIN_ROWS,
|
|
18
|
-
LOGO_MEDIUM_MIN_COLS,
|
|
19
|
-
selectLogoVariantForViewport,
|
|
20
20
|
getLogoWidth,
|
|
21
|
-
} from "
|
|
21
|
+
} from "../render/logoVariants.js";
|
|
22
22
|
|
|
23
23
|
// Re-exported for backward compatibility with existing tests.
|
|
24
24
|
export const HEADER_WORDMARK_LINES = LOGO_LARGE;
|
|
@@ -27,9 +27,8 @@ const HEADER_PADDING_COLUMNS = 2;
|
|
|
27
27
|
const SHELL_GUTTER_COLUMNS = 1;
|
|
28
28
|
// Require 130+ cols for wide side-by-side so the UpdateAvailableCard has room.
|
|
29
29
|
const WIDE_HEADER_MIN_COLUMNS = 130;
|
|
30
|
-
// Require 72+ cols
|
|
31
|
-
|
|
32
|
-
const MEDIUM_HEADER_MIN_COLUMNS = LOGO_MEDIUM_MIN_COLS; // 72
|
|
30
|
+
// Require 72+ cols for medium side-by-side canonical-logo layout.
|
|
31
|
+
const MEDIUM_HEADER_MIN_COLUMNS = LOGO_LARGE_MIN_COLS;
|
|
33
32
|
const MIN_SIDE_BY_SIDE_METADATA_WIDTH = 18;
|
|
34
33
|
const STACKED_METADATA_GAP_ROWS = 1;
|
|
35
34
|
// Gap row between the UpdateAvailableCard and the metadata lines.
|
|
@@ -84,7 +83,7 @@ function getMetadataRowCount(headerConfig: HeaderConfig): number {
|
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
function getHeaderVerticalMargins(layout: Layout): { topMarginRows: number; bottomMarginRows: number } {
|
|
87
|
-
if (layout.mode
|
|
86
|
+
if (!isDecorativeLayoutMode(layout.mode)) {
|
|
88
87
|
return {
|
|
89
88
|
topMarginRows: 0,
|
|
90
89
|
bottomMarginRows: 0,
|
|
@@ -101,6 +100,19 @@ function getHeaderVerticalMargins(layout: Layout): { topMarginRows: number; bott
|
|
|
101
100
|
};
|
|
102
101
|
}
|
|
103
102
|
|
|
103
|
+
export function selectHeaderLogo(layout: Layout): readonly string[] {
|
|
104
|
+
if (process.env["CODEXA_NO_ASCII_LOGO"] === "1") return [];
|
|
105
|
+
const showNormalLogo =
|
|
106
|
+
process.env["CODEXA_NO_ASCII_LOGO"] !== "1" && (
|
|
107
|
+
layout.mode === "regular" ||
|
|
108
|
+
layout.mode === "expanded" ||
|
|
109
|
+
(layout.mode === "compact" && layout.cols >= 72)
|
|
110
|
+
);
|
|
111
|
+
if (!showNormalLogo) return [];
|
|
112
|
+
if (process.env["CODEXA_COMPACT_LOGO"] === "1") return LOGO_COMPACT;
|
|
113
|
+
return LOGO_LARGE;
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
export function getHeaderHeroLayout(
|
|
105
117
|
layout: Layout,
|
|
106
118
|
headerConfig: HeaderConfig = HEADER_CONFIG_DEFAULTS,
|
|
@@ -110,17 +122,29 @@ export function getHeaderHeroLayout(
|
|
|
110
122
|
const metadataRows = getMetadataRowCount(headerConfig);
|
|
111
123
|
const contentWidth = getHeaderContentWidth(layout.cols);
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
const showNormalLogo =
|
|
126
|
+
process.env["CODEXA_NO_ASCII_LOGO"] !== "1" && (
|
|
127
|
+
layout.mode === "regular" ||
|
|
128
|
+
layout.mode === "expanded" ||
|
|
129
|
+
(layout.mode === "compact" && layout.cols >= 72)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const placeMetadataBesideLogo = showNormalLogo && layout.cols >= 95;
|
|
133
|
+
|
|
134
|
+
let mode: HeaderHeroMode;
|
|
135
|
+
if (!showNormalLogo) {
|
|
136
|
+
mode = "compact";
|
|
137
|
+
} else if (placeMetadataBesideLogo) {
|
|
138
|
+
mode = layout.cols >= 130 ? "wide" : "medium";
|
|
139
|
+
} else {
|
|
140
|
+
mode = "narrow";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const logo = selectHeaderLogo(layout);
|
|
117
144
|
const logoWidth = logo.length > 0 ? getLogoWidth(logo) : 0;
|
|
118
|
-
const canRenderLogo = logo.length > 0;
|
|
119
145
|
|
|
120
|
-
if (
|
|
121
|
-
// Compact / text-only header.
|
|
122
|
-
// dropped purely for size (not because the user disabled ASCII art).
|
|
123
|
-
const sizeHintRows = process.env["CODEXA_NO_ASCII_LOGO"] === "1" ? 0 : 1;
|
|
146
|
+
if (mode === "compact") {
|
|
147
|
+
// Compact / micro text-only header.
|
|
124
148
|
return {
|
|
125
149
|
mode: "compact",
|
|
126
150
|
topMarginRows,
|
|
@@ -129,21 +153,13 @@ export function getHeaderHeroLayout(
|
|
|
129
153
|
metadataGapRows: 0,
|
|
130
154
|
logoRows: 1,
|
|
131
155
|
metadataRows: 0,
|
|
132
|
-
compactHintRows:
|
|
133
|
-
totalRows: topMarginRows + 1 +
|
|
156
|
+
compactHintRows: 0,
|
|
157
|
+
totalRows: topMarginRows + 1 + bottomMarginRows,
|
|
134
158
|
};
|
|
135
159
|
}
|
|
136
160
|
|
|
137
161
|
const logoRowCount = logo.length;
|
|
138
162
|
const metadataGapColumns = layout.cols >= WIDE_HEADER_MIN_COLUMNS ? 4 : 2;
|
|
139
|
-
const metadataColumnWidth = contentWidth - logoWidth - metadataGapColumns;
|
|
140
|
-
const canUseSideBySide = metadataRows === 0
|
|
141
|
-
|| metadataColumnWidth >= MIN_SIDE_BY_SIDE_METADATA_WIDTH;
|
|
142
|
-
const mode: HeaderHeroMode = canUseSideBySide && layout.cols >= WIDE_HEADER_MIN_COLUMNS
|
|
143
|
-
? "wide"
|
|
144
|
-
: canUseSideBySide && layout.cols >= MEDIUM_HEADER_MIN_COLUMNS
|
|
145
|
-
? "medium"
|
|
146
|
-
: "narrow";
|
|
147
163
|
const metadataGapRows = mode === "narrow" && metadataRows > 0 ? STACKED_METADATA_GAP_ROWS : 0;
|
|
148
164
|
|
|
149
165
|
const isSideBySide = mode === "wide" || mode === "medium";
|
|
@@ -244,7 +260,7 @@ export function TopHeader({
|
|
|
244
260
|
: authLabelRaw;
|
|
245
261
|
|
|
246
262
|
const heroLayout = getHeaderHeroLayout(layout, headerConfig, !!updateAvailable);
|
|
247
|
-
const selectedLogo =
|
|
263
|
+
const selectedLogo = selectHeaderLogo(layout);
|
|
248
264
|
const selectedLogoWidth = selectedLogo.length > 0 ? getLogoWidth(selectedLogo) : 0;
|
|
249
265
|
|
|
250
266
|
const contentWidth = getHeaderContentWidth(layout.cols);
|
|
@@ -343,7 +359,7 @@ export function TopHeader({
|
|
|
343
359
|
)}
|
|
344
360
|
{metadataColumn}
|
|
345
361
|
{updateAvailable && (
|
|
346
|
-
<Text color={theme.warning} wrap="truncate">{
|
|
362
|
+
<Text color={theme.warning} wrap="truncate">{`Update available: Codexa ${formatVersionLabel(updateAvailable.latestVersion)} — Run: npm install -g @golba98/codexa@latest`}</Text>
|
|
347
363
|
)}
|
|
348
364
|
</Box>
|
|
349
365
|
)}
|
|
@@ -418,4 +434,4 @@ export const MemoizedTopHeader = memo(TopHeader, (prev, next) => {
|
|
|
418
434
|
export const MIN_LOGO_TERMINAL_WIDTH = LOGO_COMPACT_MIN_COLS;
|
|
419
435
|
|
|
420
436
|
// Re-export for consumers that reference these constants directly.
|
|
421
|
-
export { LOGO_LARGE_MIN_COLS, LOGO_MEDIUM_MIN_COLS, LOGO_COMPACT_MIN_COLS };
|
|
437
|
+
export { LOGO_LARGE_MIN_COLS, LOGO_MEDIUM_MIN_COLS, LOGO_COMPACT_MIN_COLS } from "../render/logoVariants.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { CODEXA_UPDATE_COMMAND } from "
|
|
4
|
-
import { clampVisualText } from "
|
|
5
|
-
import { useTheme } from "
|
|
3
|
+
import { CODEXA_UPDATE_COMMAND, formatVersionLabel } from "../../core/version/updateCheck.js";
|
|
4
|
+
import { clampVisualText } from "../layout.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
6
|
|
|
7
7
|
export const UPDATE_CARD_CONTENT_ROWS = 4; // title + available + using + command
|
|
8
8
|
export const UPDATE_CARD_ROWS = UPDATE_CARD_CONTENT_ROWS + 2; // +2 for top/bottom border rows
|
|
@@ -33,8 +33,8 @@ export function UpdateAvailableCard({ latestVersion, currentVersion, width }: Up
|
|
|
33
33
|
flexShrink={0}
|
|
34
34
|
>
|
|
35
35
|
<Text color={theme.text} bold>{clamp("Update available")}</Text>
|
|
36
|
-
<Text color={theme.textMuted}>{clamp(`Codexa ${latestVersion}
|
|
37
|
-
<Text color={theme.textMuted}>{clamp(`
|
|
36
|
+
<Text color={theme.textMuted}>{clamp(`Codexa ${formatVersionLabel(latestVersion)}`)}</Text>
|
|
37
|
+
<Text color={theme.textMuted}>{clamp(`Using ${formatVersionLabel(currentVersion)}`)}</Text>
|
|
38
38
|
<Text color={theme.textDim}>{clamp(command)}</Text>
|
|
39
39
|
</Box>
|
|
40
40
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatTerminalAnswerInline } from "
|
|
1
|
+
import { formatTerminalAnswerInline } from "../render/terminalAnswerFormat.js";
|
|
2
2
|
|
|
3
3
|
function stripOuterQuotes(s: string): string {
|
|
4
4
|
if ((s.startsWith("'") && s.endsWith("'")) || (s.startsWith('"') && s.endsWith('"'))) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { WrappedTextRow } from "
|
|
2
|
-
import { getTextWidth, normalizeLineBreaks, wrapTextRows } from "
|
|
3
|
-
import { sanitizeTerminalInput } from "
|
|
1
|
+
import type { WrappedTextRow } from "../render/textLayout.js";
|
|
2
|
+
import { getTextWidth, normalizeLineBreaks, wrapTextRows } from "../render/textLayout.js";
|
|
3
|
+
import { sanitizeTerminalInput } from "../../core/terminal/terminalSanitize.js";
|
|
4
4
|
|
|
5
5
|
export type WrappedInputRow = WrappedTextRow;
|
|
6
6
|
|
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
|
+
}
|