From 721fee666fcdd761e48cc2b67088a73bc4f5798b Mon Sep 17 00:00:00 2001 From: Sarav Date: Tue, 4 Aug 2026 07:40:09 +0530 Subject: [PATCH 1/2] fix(tui): make the welcome panel responsive to terminal size (#1067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The full two-column boot box (block wordmark + description) is ~65 cols wide and ~13 rows tall, so on a typical terminal it ate ~40-50% of the screen with no way to shrink it (#1067). It now scales by terminal size, using the repo's breakpoint idiom (useTerminalDimensions + createMemo, cf. routes/session/permission.tsx, component/upgrade-indicator.tsx) — reactive, so it adapts live on resize: - full — the two-column wordmark box, reserved for large windows (>=110w & >=44h) - medium — title + one condensed line, no wordmark (the common case, ~6 rows) - compact — a single line; the border title already shows the version (<60w or <18h) The breakpoint decision is a pure function in welcome-panel-utils.ts (mirroring upgrade-indicator-utils.ts) so it's unit-testable without the render/sync context; 4 tests cover the thresholds and that the smaller dimension wins. Scope: responsiveness only — no dismiss/collapse behavior change. Co-Authored-By: Claude Opus 4.8 --- .../tui/src/component/welcome-panel-utils.ts | 29 ++++ packages/tui/src/component/welcome-panel.tsx | 143 ++++++++++++------ .../component/welcome-panel-utils.test.ts | 32 ++++ 3 files changed, 159 insertions(+), 45 deletions(-) create mode 100644 packages/tui/src/component/welcome-panel-utils.ts create mode 100644 packages/tui/test/component/welcome-panel-utils.test.ts diff --git a/packages/tui/src/component/welcome-panel-utils.ts b/packages/tui/src/component/welcome-panel-utils.ts new file mode 100644 index 0000000000..5a6b7a32b8 --- /dev/null +++ b/packages/tui/src/component/welcome-panel-utils.ts @@ -0,0 +1,29 @@ +// Pure breakpoint logic for WelcomePanel, split out (like upgrade-indicator-utils) +// so it's unit-testable without the component's render/context dependencies. +// +// The full two-column boot box (block wordmark + description) is ~65 cols wide +// and ~8 rows tall, which eats half a small terminal (issue #1067). We scale it +// down by terminal size. Both axes matter — the wordmark is wide AND tall — so +// each threshold is a min across width and height. Values follow the repo's +// breakpoint idiom (routes/session/permission.tsx uses width < 80; upgrade- +// indicator uses width < 100). + +export type WelcomePanelVariant = "full" | "medium" | "compact" + +// The full panel is ~13 rows tall, so it's reserved for genuinely large windows +// — on a typical laptop terminal (80–110 wide, 24–35 tall) it would still eat +// ~40% of the screen, which is the problem #1067 is about. So `medium` (no +// wordmark, ~6 rows) is the common case; `full` only when there's real room. +/** Below these, drop to the single-line compact panel. */ +export const COMPACT_MAX_WIDTH = 60 +export const COMPACT_MAX_HEIGHT = 18 +/** At/above these, show the full two-column wordmark panel; below → medium. */ +export const MEDIUM_MAX_WIDTH = 110 +export const MEDIUM_MAX_HEIGHT = 44 + +/** Choose the WelcomePanel layout for a given terminal size. */ +export function welcomePanelVariant(width: number, height: number): WelcomePanelVariant { + if (width < COMPACT_MAX_WIDTH || height < COMPACT_MAX_HEIGHT) return "compact" + if (width < MEDIUM_MAX_WIDTH || height < MEDIUM_MAX_HEIGHT) return "medium" + return "full" +} diff --git a/packages/tui/src/component/welcome-panel.tsx b/packages/tui/src/component/welcome-panel.tsx index b9ea310bfd..a0dcbbdec5 100644 --- a/packages/tui/src/component/welcome-panel.tsx +++ b/packages/tui/src/component/welcome-panel.tsx @@ -1,78 +1,131 @@ -import { Show } from "solid-js" +import { Show, createMemo } from "solid-js" import { TextAttributes } from "@opentui/core" +import { useTerminalDimensions } from "@opentui/solid" import { useTheme } from "../context/theme" import { Logo } from "./logo" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { useReady } from "./altimate-onboarding" +import { welcomePanelVariant } from "./welcome-panel-utils" -// altimate_change — Claude-Code-style full-width boot box: big block wordmark on -// the left and a single "What is Altimate Code" section on the right. Shared -// between the home route and the session view so the header stays consistent -// when a command (e.g. /discover) starts a session. zIndex keeps it above -// transient top toasts (update/MCP) that would otherwise blank its top rows. +// altimate_change — Claude-Code-style boot box: "What is Altimate Code" plus the +// readiness-aware CTA. Shared between the home route and the session view so the +// header stays consistent when a command (e.g. /discover) starts a session. +// zIndex keeps it above transient top toasts (update/MCP) that would otherwise +// blank its top rows. +// +// Responsive (issue #1067): the full two-column boot box with the block wordmark +// is ~65 cols wide and ~8 rows tall, which eats half a small terminal. So it +// scales down by terminal size, following the repo's breakpoint idiom +// (createMemo on useTerminalDimensions, e.g. routes/session/permission.tsx, +// component/upgrade-indicator.tsx). Both axes matter — the wordmark is wide AND +// tall: +// full — wordmark + full description (large terminals) +// medium — title + one condensed line, no wordmark +// compact — a single line; the border title already shows the version export function WelcomePanel() { const { theme } = useTheme() const ready = useReady() + const dimensions = useTerminalDimensions() + + const variant = createMemo(() => welcomePanelVariant(dimensions().width, dimensions().height)) + const compact = createMemo(() => variant() === "compact") + const medium = createMemo(() => variant() === "medium") + const full = createMemo(() => variant() === "full") + + const title = InstallationVersion === "local" ? " Altimate Code " : ` Altimate Code v${InstallationVersion} ` + return ( - {/* left column — the block-letter wordmark (59 cols wide) */} - - - Welcome to Altimate Code - - - - {/* right column — tips + what-is sections */} - - - - What is Altimate Code + {/* compact — one line; the border title already carries the version */} + + + + {ready() ? "Your data-aware AI harness." : "Connect your AI model to start."} - - Altimate Code is a specialized data engineering harness that sits between any LLM and your entire data - stack. + + + + {/* medium — title + one condensed description + CTA, no block wordmark */} + + + + Welcome to Altimate Code - It gives your AI real context — column-level lineage, SQL analysis, dbt, and live warehouse metadata — so it - reasons about your data instead of guessing. + A data-engineering harness that gives your AI real context — column-level lineage, SQL analysis, dbt, and + live warehouse metadata. - {/* CTA only until a model is connected — stale afterwards */} Connect your AI model to start. - + + + {/* full — the original two-column boot box */} + + + {/* left column — the block-letter wordmark */} + + + Welcome to Altimate Code + + + + {/* right column — what-is section */} + + + + What is Altimate Code + + + Altimate Code is a specialized data engineering harness that sits between any LLM and your entire data + stack. + + + It gives your AI real context — column-level lineage, SQL analysis, dbt, and live warehouse metadata — + so it reasons about your data instead of guessing. + + {/* CTA only until a model is connected — stale afterwards */} + + + Connect your AI model to start. + + + + + + ) } diff --git a/packages/tui/test/component/welcome-panel-utils.test.ts b/packages/tui/test/component/welcome-panel-utils.test.ts new file mode 100644 index 0000000000..c9d6561a2a --- /dev/null +++ b/packages/tui/test/component/welcome-panel-utils.test.ts @@ -0,0 +1,32 @@ +import { expect, test } from "bun:test" +import { + COMPACT_MAX_HEIGHT, + COMPACT_MAX_WIDTH, + MEDIUM_MAX_HEIGHT, + MEDIUM_MAX_WIDTH, + welcomePanelVariant, +} from "../../src/component/welcome-panel-utils" + +test("large terminals get the full two-column boot box", () => { + expect(welcomePanelVariant(MEDIUM_MAX_WIDTH + 20, MEDIUM_MAX_HEIGHT + 10)).toBe("full") + expect(welcomePanelVariant(MEDIUM_MAX_WIDTH, MEDIUM_MAX_HEIGHT)).toBe("full") // at the threshold +}) + +test("a narrow OR short terminal drops the wordmark (medium)", () => { + expect(welcomePanelVariant(80, 24)).toBe("medium") + expect(welcomePanelVariant(MEDIUM_MAX_WIDTH - 1, 40)).toBe("medium") // narrow but tall + expect(welcomePanelVariant(120, MEDIUM_MAX_HEIGHT - 1)).toBe("medium") // wide but short +}) + +test("a very small terminal collapses to the single-line compact panel", () => { + expect(welcomePanelVariant(50, 12)).toBe("compact") + expect(welcomePanelVariant(COMPACT_MAX_WIDTH - 1, 40)).toBe("compact") // very narrow, any height + expect(welcomePanelVariant(120, COMPACT_MAX_HEIGHT - 1)).toBe("compact") // very short, any width +}) + +test("both axes gate each step down — the smaller dimension wins", () => { + // Wide enough for full on width, but height forces compact. + expect(welcomePanelVariant(200, COMPACT_MAX_HEIGHT - 1)).toBe("compact") + // Tall enough for full on height, but width forces compact. + expect(welcomePanelVariant(COMPACT_MAX_WIDTH - 1, 200)).toBe("compact") +}) From 4db83eaa7acfe7eec484a78daff8c3ed5e98f613 Mon Sep 17 00:00:00 2001 From: Sarav Date: Tue, 4 Aug 2026 16:23:43 +0530 Subject: [PATCH 2/2] fix(tui): size welcome panel from available width+height; strengthen tests (#1069 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Blocker 1 — the breakpoint read the whole terminal, but the panel never gets it. The home slot pads it and the session view puts it in a content column beside a 42-col sidebar, so `full` was still chosen at ~84 usable cols and swelled back to ~44% (the #1067 bug). WelcomePanel now takes `availableWidth`/`availableHeight`; home passes `width-4` / `height-PANEL_VERTICAL_RESERVE`, session passes the existing `contentWidth()` (already minus sidebar+padding) / `height-RESERVE`. Falls back to the terminal dimension when omitted. Reactive to resize AND sidebar toggle. Height gets the same treatment: subtract the fixed prompt/footer chrome (PANEL_VERTICAL_RESERVE = 8) so a short window drops to a smaller variant instead of a too-tall panel that crowds the prompt. Thresholds re-expressed in usable terms (MEDIUM_MIN_HEIGHT 16, FULL_MIN_HEIGHT 36) — full still engages at a ~44-row terminal; ≤~23 rows now → compact. Blocker 2 — the tests didn't prove the width gate (deleting `width < …` left them green). Rewrote to isolate each gate on one axis (e.g. `(FULL_MIN_WIDTH-1, tall) -> medium`), plus exact boundaries, the sidebar case, a short-terminal height case, and degenerate `(0,0)/(1,1)`. Also from review: - Rename `*_MAX_*` -> `MEDIUM_MIN_*` / `FULL_MIN_*` — floors matched with strict `<`. - `/` instead of three memos + three ``. - Dedupe the CTA into a `CONNECT_CTA` constant. - Trim the full left column 65 -> 54 (logo is ~50 cols) so `full` engages sooner. - Shorten the medium description to one line. - Consistent "~13-row bordered box" framing in the comments. FULL_MIN_HEIGHT stays tuned so the wordmark shows only on large windows (product choice). Co-Authored-By: Claude Opus 4.8 --- .../tui/src/component/welcome-panel-utils.ts | 55 ++++-- packages/tui/src/component/welcome-panel.tsx | 176 +++++++++--------- packages/tui/src/routes/home.tsx | 9 +- packages/tui/src/routes/session/index.tsx | 9 +- .../component/welcome-panel-utils.test.ts | 72 +++++-- 5 files changed, 196 insertions(+), 125 deletions(-) diff --git a/packages/tui/src/component/welcome-panel-utils.ts b/packages/tui/src/component/welcome-panel-utils.ts index 5a6b7a32b8..853abc2e61 100644 --- a/packages/tui/src/component/welcome-panel-utils.ts +++ b/packages/tui/src/component/welcome-panel-utils.ts @@ -1,29 +1,46 @@ // Pure breakpoint logic for WelcomePanel, split out (like upgrade-indicator-utils) // so it's unit-testable without the component's render/context dependencies. // -// The full two-column boot box (block wordmark + description) is ~65 cols wide -// and ~8 rows tall, which eats half a small terminal (issue #1067). We scale it -// down by terminal size. Both axes matter — the wordmark is wide AND tall — so -// each threshold is a min across width and height. Values follow the repo's -// breakpoint idiom (routes/session/permission.tsx uses width < 80; upgrade- -// indicator uses width < 100). +// The full two-column boot box (block wordmark + description) is a ~13-row +// bordered box, so on a typical terminal it eats ~40% of the screen with no way +// to shrink it (issue #1067). We scale it down by the space the panel ACTUALLY +// has on both axes: +// - width: the terminal minus the caller's padding and any sibling sidebar. +// - height: the terminal minus the fixed chrome that always shares the column +// with the panel (top spacer + prompt + footer), so a big panel is +// chosen only when it won't crowd the prompt off a short terminal. +// `medium` (no wordmark) is the common case; `full` only when there's real room. +// +// Naming: these are the MINIMUMS a tier requires, matched with strict `<` +// (`width < FULL_MIN_WIDTH` → not full). MEDIUM_MIN_* is the floor for medium +// (below → compact); FULL_MIN_* is the floor for full (below → medium). All in +// terms of AVAILABLE (usable) size, not the raw terminal. export type WelcomePanelVariant = "full" | "medium" | "compact" -// The full panel is ~13 rows tall, so it's reserved for genuinely large windows -// — on a typical laptop terminal (80–110 wide, 24–35 tall) it would still eat -// ~40% of the screen, which is the problem #1067 is about. So `medium` (no -// wordmark, ~6 rows) is the common case; `full` only when there's real room. -/** Below these, drop to the single-line compact panel. */ -export const COMPACT_MAX_WIDTH = 60 -export const COMPACT_MAX_HEIGHT = 18 -/** At/above these, show the full two-column wordmark panel; below → medium. */ -export const MEDIUM_MAX_WIDTH = 110 -export const MEDIUM_MAX_HEIGHT = 44 +/** + * Rows the panel must leave for the always-present chrome below/around it (the + * prompt, the footer, and the home top spacer). Callers subtract this from the + * terminal height to get the panel's usable height. An estimate — the prompt can + * grow with multi-line input, but at rest this is the fixed cost. + */ +export const PANEL_VERTICAL_RESERVE = 8 + +/** Minimum usable size for the medium panel; below either → compact (one line). */ +export const MEDIUM_MIN_WIDTH = 60 +export const MEDIUM_MIN_HEIGHT = 16 +/** Minimum usable size for the full wordmark panel; below either → medium. */ +export const FULL_MIN_WIDTH = 110 +export const FULL_MIN_HEIGHT = 36 -/** Choose the WelcomePanel layout for a given terminal size. */ +/** + * Choose the WelcomePanel layout from the panel's AVAILABLE size — width already + * minus padding/sidebar, height already minus PANEL_VERTICAL_RESERVE. Not the + * raw terminal (that's the #1067 bug: a sidebar-narrowed column, or a short + * terminal, would still pick `full`). + */ export function welcomePanelVariant(width: number, height: number): WelcomePanelVariant { - if (width < COMPACT_MAX_WIDTH || height < COMPACT_MAX_HEIGHT) return "compact" - if (width < MEDIUM_MAX_WIDTH || height < MEDIUM_MAX_HEIGHT) return "medium" + if (width < MEDIUM_MIN_WIDTH || height < MEDIUM_MIN_HEIGHT) return "compact" + if (width < FULL_MIN_WIDTH || height < FULL_MIN_HEIGHT) return "medium" return "full" } diff --git a/packages/tui/src/component/welcome-panel.tsx b/packages/tui/src/component/welcome-panel.tsx index a0dcbbdec5..b8c16eff9c 100644 --- a/packages/tui/src/component/welcome-panel.tsx +++ b/packages/tui/src/component/welcome-panel.tsx @@ -1,4 +1,4 @@ -import { Show, createMemo } from "solid-js" +import { Match, Show, Switch, createMemo } from "solid-js" import { TextAttributes } from "@opentui/core" import { useTerminalDimensions } from "@opentui/solid" import { useTheme } from "../context/theme" @@ -7,30 +7,36 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version" import { useReady } from "./altimate-onboarding" import { welcomePanelVariant } from "./welcome-panel-utils" +const CONNECT_CTA = "Connect your AI model to start." + // altimate_change — Claude-Code-style boot box: "What is Altimate Code" plus the // readiness-aware CTA. Shared between the home route and the session view so the // header stays consistent when a command (e.g. /discover) starts a session. // zIndex keeps it above transient top toasts (update/MCP) that would otherwise // blank its top rows. // -// Responsive (issue #1067): the full two-column boot box with the block wordmark -// is ~65 cols wide and ~8 rows tall, which eats half a small terminal. So it -// scales down by terminal size, following the repo's breakpoint idiom -// (createMemo on useTerminalDimensions, e.g. routes/session/permission.tsx, -// component/upgrade-indicator.tsx). Both axes matter — the wordmark is wide AND -// tall: -// full — wordmark + full description (large terminals) -// medium — title + one condensed line, no wordmark -// compact — a single line; the border title already shows the version -export function WelcomePanel() { +// Responsive (issue #1067): the full two-column boot box is a ~13-row bordered +// box that ate ~40% of the screen. It now scales down by AVAILABLE size, +// following the repo's breakpoint idiom (createMemo over useTerminalDimensions, +// cf. routes/session/permission.tsx:450, component/upgrade-indicator.tsx:14): +// full — wordmark + full description (large windows only) +// medium — title + one condensed line, no wordmark (the common case) +// compact — a short line; the border title already carries the version +// +// `availableWidth` / `availableHeight` are the space the panel actually gets, not +// the whole terminal — the caller subtracts its padding, any sibling sidebar +// (session's contentWidth), and the fixed prompt/footer chrome +// (PANEL_VERTICAL_RESERVE). Using the raw terminal would keep `full` selected in +// a sidebar-narrowed column or a short window and swell the panel back up — the +// bug #1067 is about. Both fall back to the terminal dimension when omitted. +export function WelcomePanel(props: { availableWidth?: number; availableHeight?: number }) { const { theme } = useTheme() const ready = useReady() const dimensions = useTerminalDimensions() - const variant = createMemo(() => welcomePanelVariant(dimensions().width, dimensions().height)) - const compact = createMemo(() => variant() === "compact") - const medium = createMemo(() => variant() === "medium") - const full = createMemo(() => variant() === "full") + const variant = createMemo(() => + welcomePanelVariant(props.availableWidth ?? dimensions().width, props.availableHeight ?? dimensions().height), + ) const title = InstallationVersion === "local" ? " Altimate Code " : ` Altimate Code v${InstallationVersion} ` @@ -47,85 +53,87 @@ export function WelcomePanel() { backgroundColor={theme.background} flexDirection="column" > - {/* compact — one line; the border title already carries the version */} - - - - {ready() ? "Your data-aware AI harness." : "Connect your AI model to start."} - - - - - {/* medium — title + one condensed description + CTA, no block wordmark */} - - - - Welcome to Altimate Code - - - A data-engineering harness that gives your AI real context — column-level lineage, SQL analysis, dbt, and - live warehouse metadata. - - - - Connect your AI model to start. + + {/* compact — a short line; the border title already carries the version. + wrapMode keeps it readable if the terminal is extremely narrow. */} + + + + {ready() ? "Your data-aware AI harness." : CONNECT_CTA} - - - + + - {/* full — the original two-column boot box */} - - - {/* left column — the block-letter wordmark */} - + {/* medium — title + one condensed description + CTA, no block wordmark */} + + Welcome to Altimate Code - - - {/* right column — what-is section */} - - - - What is Altimate Code - - - Altimate Code is a specialized data engineering harness that sits between any LLM and your entire data - stack. + + Gives your AI real context on your data stack — lineage, SQL, dbt, and live warehouse metadata. + + + + {CONNECT_CTA} - - It gives your AI real context — column-level lineage, SQL analysis, dbt, and live warehouse metadata — - so it reasons about your data instead of guessing. + + + + + {/* full — the original two-column boot box */} + + + {/* left column — the block-letter wordmark (logo is ~50 cols) */} + + + Welcome to Altimate Code - {/* CTA only until a model is connected — stale afterwards */} - - - Connect your AI model to start. + + + {/* right column — what-is section */} + + + + What is Altimate Code + + + Altimate Code is a specialized data engineering harness that sits between any LLM and your entire data + stack. + + + It gives your AI real context — column-level lineage, SQL analysis, dbt, and live warehouse metadata — + so it reasons about your data instead of guessing. - + {/* CTA only until a model is connected — stale afterwards */} + + + {CONNECT_CTA} + + + - - + + ) } diff --git a/packages/tui/src/routes/home.tsx b/packages/tui/src/routes/home.tsx index d0926bec82..caa6d10e88 100644 --- a/packages/tui/src/routes/home.tsx +++ b/packages/tui/src/routes/home.tsx @@ -19,6 +19,7 @@ import { useTheme } from "../context/theme" // one-line "Get started: /connect ... /discover ..." hint below, which duplicated the // same guidance the panel's "Tips for getting started" section now covers. import { WelcomePanel } from "../component/welcome-panel" +import { PANEL_VERTICAL_RESERVE } from "../component/welcome-panel-utils" // altimate_change end let once = false @@ -108,7 +109,13 @@ export function Home() { - + {/* Size to the panel's real space, not the whole terminal (#1067): + -4 for this column's paddingLeft/Right; -PANEL_VERTICAL_RESERVE for + the top spacer + prompt + footer that share the height. */} + diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index a00e321070..0521a6248c 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -26,6 +26,7 @@ import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime" import { Spinner } from "../../component/spinner" // altimate_change — shared boot box at the top of the session scrollback import { WelcomePanel } from "../../component/welcome-panel" +import { PANEL_VERTICAL_RESERVE } from "../../component/welcome-panel-utils" import { createSyntaxStyleMemo, generateSubtleSyntax, selectedForeground, useTheme } from "../../context/theme" import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core" import { Prompt, type PromptRef } from "../../component/prompt" @@ -1186,7 +1187,13 @@ export function Session() { /discover) starts a session. Outside the scrollbox: the bordered panel does not paint reliably inside the scroll viewport. */} - + {/* Size to the panel's real space, not the terminal (#1067): + contentWidth already subtracts the sidebar + padding; + -PANEL_VERTICAL_RESERVE leaves room for the prompt + footer. */} + {/* altimate_change end */} { - expect(welcomePanelVariant(MEDIUM_MAX_WIDTH + 20, MEDIUM_MAX_HEIGHT + 10)).toBe("full") - expect(welcomePanelVariant(MEDIUM_MAX_WIDTH, MEDIUM_MAX_HEIGHT)).toBe("full") // at the threshold +// Comfortably above the full floor on one axis, used to isolate the OTHER axis +// so a single gate's removal is provable (each test below fails if its `<` check +// is deleted from the source). +const TALL = FULL_MIN_HEIGHT + 10 +const WIDE = FULL_MIN_WIDTH + 20 + +test("full requires BOTH width and height to clear the full floor", () => { + expect(welcomePanelVariant(WIDE, TALL)).toBe("full") + expect(welcomePanelVariant(FULL_MIN_WIDTH, FULL_MIN_HEIGHT)).toBe("full") // exactly at the floor +}) + +test("full's width gate is real — width one below the floor drops to medium even when tall", () => { + expect(welcomePanelVariant(FULL_MIN_WIDTH - 1, TALL)).toBe("medium") +}) + +test("full's height gate is real — height one below the floor drops to medium even when wide", () => { + expect(welcomePanelVariant(WIDE, FULL_MIN_HEIGHT - 1)).toBe("medium") +}) + +test("compact's width gate is real — width one below the medium floor is compact even when tall", () => { + expect(welcomePanelVariant(MEDIUM_MIN_WIDTH - 1, TALL)).toBe("compact") +}) + +test("compact's height gate is real — height one below the medium floor is compact even when wide", () => { + expect(welcomePanelVariant(WIDE, MEDIUM_MIN_HEIGHT - 1)).toBe("compact") +}) + +test("compact→medium boundary is exact (at the floor is medium)", () => { + expect(welcomePanelVariant(MEDIUM_MIN_WIDTH, MEDIUM_MIN_HEIGHT)).toBe("medium") + expect(welcomePanelVariant(MEDIUM_MIN_WIDTH, TALL)).toBe("medium") + expect(welcomePanelVariant(WIDE, MEDIUM_MIN_HEIGHT)).toBe("medium") }) -test("a narrow OR short terminal drops the wordmark (medium)", () => { - expect(welcomePanelVariant(80, 24)).toBe("medium") - expect(welcomePanelVariant(MEDIUM_MAX_WIDTH - 1, 40)).toBe("medium") // narrow but tall - expect(welcomePanelVariant(120, MEDIUM_MAX_HEIGHT - 1)).toBe("medium") // wide but short +test("everyday terminals get medium, not the oversized wordmark", () => { + // Inputs are AVAILABLE size (terminal minus padding/sidebar on width, minus + // PANEL_VERTICAL_RESERVE on height). A 106x31 terminal → ~(102, 23): + expect(welcomePanelVariant(102, 23)).toBe("medium") + // 80x24 terminal → ~(76, 16) — medium exactly at the height floor: + expect(welcomePanelVariant(76, 16)).toBe("medium") + // #1067 session case: a 130-col terminal with the 42-col sidebar leaves ~84 + // usable cols → medium (was wrongly full when it used the whole terminal width). + expect(welcomePanelVariant(130 - 42 - 4, 50 - PANEL_VERTICAL_RESERVE)).toBe("medium") }) -test("a very small terminal collapses to the single-line compact panel", () => { - expect(welcomePanelVariant(50, 12)).toBe("compact") - expect(welcomePanelVariant(COMPACT_MAX_WIDTH - 1, 40)).toBe("compact") // very narrow, any height - expect(welcomePanelVariant(120, COMPACT_MAX_HEIGHT - 1)).toBe("compact") // very short, any width +test("a short terminal drops to compact once prompt/footer chrome is reserved (#1067 height)", () => { + // 120x22: wide, but only ~14 usable rows after the ~8-row chrome → compact, + // where the raw terminal height (22) would have picked medium. + expect(welcomePanelVariant(120 - 4, 22 - PANEL_VERTICAL_RESERVE)).toBe("compact") }) -test("both axes gate each step down — the smaller dimension wins", () => { - // Wide enough for full on width, but height forces compact. - expect(welcomePanelVariant(200, COMPACT_MAX_HEIGHT - 1)).toBe("compact") - // Tall enough for full on height, but width forces compact. - expect(welcomePanelVariant(COMPACT_MAX_WIDTH - 1, 200)).toBe("compact") +test("degenerate sizes collapse to compact", () => { + expect(welcomePanelVariant(0, 0)).toBe("compact") + expect(welcomePanelVariant(1, 1)).toBe("compact") })