Skip to content

Commit deb93e9

Browse files
fix(tui): per-route vertical reserve + required panel props (#1069 round-2 follow-up) (#1071)
* fix(tui): per-route vertical reserve, required panel props (#1069 round-2 review) Follow-up to the merged #1069, addressing the round-2 review. - Split the vertical reserve per route: HOME_VERTICAL_RESERVE (10) and SESSION_VERTICAL_RESERVE (7) accurately reflect each route's chrome (home has a top spacer + footer; session doesn't), instead of one constant that fit only session. Thresholds re-expressed in usable terms (MEDIUM_MIN_HEIGHT 13, FULL_MIN_HEIGHT 34) so `full` still engages at a ~44-row terminal on home and the classic 80x24 clears the medium floor with margin. - Make WelcomePanel's availableWidth/availableHeight props REQUIRED (drop the raw-terminal fallback + the component's own useTerminalDimensions), so a call site that forgets to scale is a compile error, not a silent regression. - Height now gets the same available-space treatment as width (terminal minus the route's prompt/footer chrome), so a short window shrinks the panel instead of crowding the prompt. - Centralize PANEL_HORIZONTAL_PADDING (4) — was a bare `-4` in the home call site and test helper. Tests: add the sidebar-toggle transition (session 150x50 full -> medium when the sidebar opens — the exact #1067 case), the classic 80x24 on both routes, and keep the isolated-gate / boundary / degenerate coverage. tsgo clean; oxlint 0/0. Reviewed by a 2-model consensus panel (both SOUND); the PANEL_HORIZONTAL_PADDING and sidebar-toggle test came out of that review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tui): address welcome-panel round-3 review (reserves, overlay, tests) - correct per-route vertical reserves to the real chrome: HOME 10->15, SESSION 7->9, derived from PROMPT_REST_HEIGHT=6 plus the counted home_bottom slot and footer (the prompt is 5-6 rows at rest, not ~4) - retune height thresholds honestly: MEDIUM_MIN_HEIGHT 13->8 (the real medium panel height, so 80x24 is a genuine medium fit, not a pinned margin), FULL_MIN_HEIGHT 34->30 (a documented product breakpoint, not a fit minimum) - centralize the route arithmetic: export homeAvailable/sessionAvailable and call them from both the routes and the tests, so a wrong reserve/threshold fails a test instead of being mirrored in a test-only copy - centralize PANEL_HORIZONTAL_PADDING and add SIDEBAR_WIDTH (drop the duplicated 42) - keep the panel on session's contentWidth basis (narrows with the sidebar, incl. the dimmed overlay) so the panel stays aligned with the messages; documented - tests: reserve-pinned-to-chrome, overlay narrowing, negative dimensions Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(tui): render-test the welcome panel variants (real component) Mounts the real WelcomePanel through the app context stack at fixed available sizes and asserts each variant renders its distinct content (full: "What is Altimate Code"; medium: the condensed line, no what-is section; compact: the single connect-CTA line). Covers the .tsx Switch that the pure welcomePanelVariant unit test can't. Deliberately not a full Home/Session route render — that drags in Prompt's ~28-context dependency graph; the route wiring is now a shared one-liner through homeAvailable/sessionAvailable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tui): wrap panel-sizing memos in altimate_change markers; per-test renderer isolation; derive contentWidth from panelAvailable - Marker Guard: home.tsx panelAvailable and session contentWidth/panelAvailable are edits in upstream-shared files, now wrapped in altimate_change markers - derive contentWidth from panelAvailable().width — one definition, can't drift - render test: own the renderer per test (destroy + reset in finally) instead of a shared module-level var, safe under parallel bun test Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bfb5a7c commit deb93e9

6 files changed

Lines changed: 289 additions & 64 deletions

File tree

‎packages/tui/src/component/welcome-panel-utils.ts‎

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,96 @@
55
// bordered box, so on a typical terminal it eats ~40% of the screen with no way
66
// to shrink it (issue #1067). We scale it down by the space the panel ACTUALLY
77
// has on both axes:
8-
// - width: the terminal minus the caller's padding and any sibling sidebar.
8+
// - width: the terminal minus the caller's padding and any sibling sidebar
9+
// that consumes layout width.
910
// - height: the terminal minus the fixed chrome that always shares the column
10-
// with the panel (top spacer + prompt + footer), so a big panel is
11+
// with the panel (the per-route reserves below), so a big panel is
1112
// chosen only when it won't crowd the prompt off a short terminal.
1213
// `medium` (no wordmark) is the common case; `full` only when there's real room.
1314
//
14-
// Naming: these are the MINIMUMS a tier requires, matched with strict `<`
15-
// (`width < FULL_MIN_WIDTH` → not full). MEDIUM_MIN_* is the floor for medium
16-
// (below → compact); FULL_MIN_* is the floor for full (below → medium). All in
17-
// terms of AVAILABLE (usable) size, not the raw terminal.
15+
// Route arithmetic lives here (homeAvailable / sessionAvailable) so the routes
16+
// and the tests share ONE definition — a test that maps a terminal size to a
17+
// variant then exercises the real call-site math, not a copy of it.
1818

1919
export type WelcomePanelVariant = "full" | "medium" | "compact"
2020

21-
/**
22-
* Rows the panel must leave for the always-present chrome below/around it (the
23-
* prompt, the footer, and the home top spacer). Callers subtract this from the
24-
* terminal height to get the panel's usable height. An estimate — the prompt can
25-
* grow with multi-line input, but at rest this is the fixed cost.
26-
*/
27-
export const PANEL_VERTICAL_RESERVE = 8
21+
// --- Chrome reserves (rows the panel must leave for always-present siblings) ---
22+
//
23+
// Prompt tree at rest (component/prompt/index.tsx): top rule 1 + inner paddingTop
24+
// 1 + textarea 1 + separator 1 + agent/model meta row 1 + idle hint row 1 = 6.
25+
// Measured against the rendered tree, not estimated — the earlier "~4" undercounted
26+
// it (the meta and hint rows are always in flow).
27+
const PROMPT_REST_HEIGHT = 6
2828

29-
/** Minimum usable size for the medium panel; below either → compact (one line). */
29+
// home (routes/home.tsx): top spacer 2 (`<box height={2}>`) + prompt wrapper
30+
// paddingTop 1 + prompt 6 + home_bottom slot 3 (feature-plugins/home/tips.tsx
31+
// paddingTop, rendered unconditionally — flexbox shrinks content, not padding) +
32+
// footer 3 (feature-plugins/home/footer.tsx) = 15.
33+
export const HOME_VERTICAL_RESERVE = 2 + 1 + PROMPT_REST_HEIGHT + 3 + 3
34+
// session (routes/session/index.tsx): two column gaps 2 + paddingBottom 1 +
35+
// prompt 6 = 9. No top spacer and no footer share this column.
36+
export const SESSION_VERTICAL_RESERVE = 2 + 1 + PROMPT_REST_HEIGHT
37+
38+
// Columns the home slot spends on its own left/right padding (2 + 2). Session
39+
// subtracts the same via sessionAvailable(); both routes import this constant so
40+
// the value has a single source of truth.
41+
export const PANEL_HORIZONTAL_PADDING = 4
42+
// Width the session sidebar occupies WHEN it consumes layout width (i.e. it is
43+
// rendered in-flow, not as an overlay). Shared with the route + tests so they
44+
// can't drift — the drift that produced #1067.
45+
export const SIDEBAR_WIDTH = 42
46+
47+
// --- Thresholds ---
48+
// MEDIUM_MIN_* is a real FIT requirement: below it the medium panel (~8 rows /
49+
// ~a title + one/two-line description) would not fit, so drop to the one-line
50+
// compact. FULL_MIN_* is a product BREAKPOINT, not a fit minimum: the full panel
51+
// is only ~13 rows, but we require far more available space so the branded
52+
// wordmark appears only on a genuinely large terminal and never dominates a
53+
// small one (the #1067 ask). All in AVAILABLE (usable) terms, not the raw
54+
// terminal, matched with strict `<`.
3055
export const MEDIUM_MIN_WIDTH = 60
31-
export const MEDIUM_MIN_HEIGHT = 16
32-
/** Minimum usable size for the full wordmark panel; below either → medium. */
56+
export const MEDIUM_MIN_HEIGHT = 8
3357
export const FULL_MIN_WIDTH = 110
34-
export const FULL_MIN_HEIGHT = 36
58+
// ~45-row home terminal / ~39-row session terminal after the reserves above.
59+
export const FULL_MIN_HEIGHT = 30
3560

3661
/**
3762
* Choose the WelcomePanel layout from the panel's AVAILABLE size — width already
38-
* minus padding/sidebar, height already minus PANEL_VERTICAL_RESERVE. Not the
39-
* raw terminal (that's the #1067 bug: a sidebar-narrowed column, or a short
63+
* minus padding/sidebar, height already minus the route's vertical reserve. Not
64+
* the raw terminal (that's the #1067 bug: a sidebar-narrowed column, or a short
4065
* terminal, would still pick `full`).
4166
*/
4267
export function welcomePanelVariant(width: number, height: number): WelcomePanelVariant {
4368
if (width < MEDIUM_MIN_WIDTH || height < MEDIUM_MIN_HEIGHT) return "compact"
4469
if (width < FULL_MIN_WIDTH || height < FULL_MIN_HEIGHT) return "medium"
4570
return "full"
4671
}
72+
73+
/** Available panel size on the home route for a given terminal size. */
74+
export function homeAvailable(terminalWidth: number, terminalHeight: number): { width: number; height: number } {
75+
return {
76+
width: terminalWidth - PANEL_HORIZONTAL_PADDING,
77+
height: terminalHeight - HOME_VERTICAL_RESERVE,
78+
}
79+
}
80+
81+
/**
82+
* Available panel size on the session route. Subtracts SIDEBAR_WIDTH whenever the
83+
* sidebar is open (`sidebarVisible`) — the panel shares the same content-column
84+
* basis as the messages (session's `contentWidth`), so the two stay aligned.
85+
*
86+
* On narrow terminals the sidebar renders as a dimmed full-area overlay rather
87+
* than in-flow; the content column still narrows uniformly (panel + messages)
88+
* and both restore to full width when it closes, so we deliberately size to the
89+
* narrowed column rather than the transient obscured width.
90+
*/
91+
export function sessionAvailable(
92+
terminalWidth: number,
93+
terminalHeight: number,
94+
sidebarVisible: boolean,
95+
): { width: number; height: number } {
96+
return {
97+
width: terminalWidth - (sidebarVisible ? SIDEBAR_WIDTH : 0) - PANEL_HORIZONTAL_PADDING,
98+
height: terminalHeight - SESSION_VERTICAL_RESERVE,
99+
}
100+
}

‎packages/tui/src/component/welcome-panel.tsx‎

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Match, Show, Switch, createMemo } from "solid-js"
22
import { TextAttributes } from "@opentui/core"
3-
import { useTerminalDimensions } from "@opentui/solid"
43
import { useTheme } from "../context/theme"
54
import { Logo } from "./logo"
65
import { InstallationVersion } from "@opencode-ai/core/installation/version"
@@ -16,27 +15,31 @@ const CONNECT_CTA = "Connect your AI model to start."
1615
// blank its top rows.
1716
//
1817
// Responsive (issue #1067): the full two-column boot box is a ~13-row bordered
19-
// box that ate ~40% of the screen. It now scales down by AVAILABLE size,
20-
// following the repo's breakpoint idiom (createMemo over useTerminalDimensions,
21-
// cf. routes/session/permission.tsx:450, component/upgrade-indicator.tsx:14):
18+
// box that ate ~40% of the screen. It now scales down by AVAILABLE size — the
19+
// caller measures the terminal (useTerminalDimensions) and passes what the panel
20+
// actually gets, following the repo's breakpoint idiom (a createMemo over the
21+
// reactive dimensions, cf. routes/session/permission.tsx:450,
22+
// component/upgrade-indicator.tsx:14):
2223
// full — wordmark + full description (large windows only)
23-
// medium — title + one condensed line, no wordmark (the common case)
24+
// medium — title + a condensed description (one line on a wide terminal, two at
25+
// medium's narrow end), no wordmark (the common case)
2426
// compact — a short line; the border title already carries the version
2527
//
2628
// `availableWidth` / `availableHeight` are the space the panel actually gets, not
2729
// the whole terminal — the caller subtracts its padding, any sibling sidebar
28-
// (session's contentWidth), and the fixed prompt/footer chrome
29-
// (PANEL_VERTICAL_RESERVE). Using the raw terminal would keep `full` selected in
30-
// a sidebar-narrowed column or a short window and swell the panel back up — the
31-
// bug #1067 is about. Both fall back to the terminal dimension when omitted.
32-
export function WelcomePanel(props: { availableWidth?: number; availableHeight?: number }) {
30+
// (session's contentWidth), and the route's vertical reserve (HOME/SESSION_
31+
// VERTICAL_RESERVE). Using the raw terminal would keep `full` selected in a
32+
// sidebar-narrowed column or a short window and swell the panel back up — the bug
33+
// #1067 is about. Both props are REQUIRED: a call site that forgot one would
34+
// silently get the pre-fix raw-terminal behavior, so the type system guards it
35+
// (there's no in-repo render test of the call sites).
36+
export function WelcomePanel(props: { availableWidth: number; availableHeight: number }) {
3337
const { theme } = useTheme()
3438
const ready = useReady()
35-
const dimensions = useTerminalDimensions()
3639

37-
const variant = createMemo(() =>
38-
welcomePanelVariant(props.availableWidth ?? dimensions().width, props.availableHeight ?? dimensions().height),
39-
)
40+
// props are reactive getters, so reading them inside the memo tracks — the
41+
// variant recomputes when the caller's dimensions/sidebar change.
42+
const variant = createMemo(() => welcomePanelVariant(props.availableWidth, props.availableHeight))
4043

4144
const title = InstallationVersion === "local" ? " Altimate Code " : ` Altimate Code v${InstallationVersion} `
4245

‎packages/tui/src/routes/home.tsx‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useTheme } from "../context/theme"
1919
// one-line "Get started: /connect ... /discover ..." hint below, which duplicated the
2020
// same guidance the panel's "Tips for getting started" section now covers.
2121
import { WelcomePanel } from "../component/welcome-panel"
22-
import { PANEL_VERTICAL_RESERVE } from "../component/welcome-panel-utils"
22+
import { homeAvailable } from "../component/welcome-panel-utils"
2323
// altimate_change end
2424

2525
let once = false
@@ -68,6 +68,11 @@ export function Home() {
6868
if (configured === "auto") return Math.max(75, Math.floor(dimensions().width * 0.7))
6969
return configured ?? 75
7070
})
71+
// altimate_change start — WelcomePanel responsive sizing (#1067): the panel's
72+
// available space = terminal minus this route's padding + vertical reserve.
73+
// Shared arithmetic in welcome-panel-utils so the tests exercise it.
74+
const panelAvailable = createMemo(() => homeAvailable(dimensions().width, dimensions().height))
75+
// altimate_change end
7176
let sent = false
7277

7378
onMount(() => {
@@ -109,13 +114,10 @@ export function Home() {
109114
<box height={2} flexShrink={0} />
110115
<box width="100%" flexShrink={0}>
111116
<pluginRuntime.Slot name="home_logo" mode="replace">
112-
{/* Size to the panel's real space, not the whole terminal (#1067):
113-
-4 for this column's paddingLeft/Right; -PANEL_VERTICAL_RESERVE for
114-
the top spacer + prompt + footer that share the height. */}
115-
<WelcomePanel
116-
availableWidth={dimensions().width - 4}
117-
availableHeight={dimensions().height - PANEL_VERTICAL_RESERVE}
118-
/>
117+
{/* Size to the panel's real space, not the whole terminal (#1067).
118+
homeAvailable() subtracts this column's padding and the top
119+
spacer + prompt + home_bottom + footer reserve. */}
120+
<WelcomePanel availableWidth={panelAvailable().width} availableHeight={panelAvailable().height} />
119121
</pluginRuntime.Slot>
120122
</box>
121123
<box flexGrow={1} minHeight={0} />

‎packages/tui/src/routes/session/index.tsx‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
2626
import { Spinner } from "../../component/spinner"
2727
// altimate_change — shared boot box at the top of the session scrollback
2828
import { WelcomePanel } from "../../component/welcome-panel"
29-
import { PANEL_VERTICAL_RESERVE } from "../../component/welcome-panel-utils"
29+
import { PANEL_HORIZONTAL_PADDING, SIDEBAR_WIDTH, sessionAvailable } from "../../component/welcome-panel-utils"
3030
import { createSyntaxStyleMemo, generateSubtleSyntax, selectedForeground, useTheme } from "../../context/theme"
3131
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
3232
import { Prompt, type PromptRef } from "../../component/prompt"
@@ -272,7 +272,15 @@ export function Session() {
272272
return false
273273
})
274274
const showTimestamps = createMemo(() => timestamps() === "show")
275-
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
275+
// altimate_change start — WelcomePanel responsive sizing (#1067). panelAvailable is the single
276+
// source of the panel's usable size (via welcome-panel-utils, unit-tested); it narrows with the
277+
// sidebar whenever open — including the dimmed full-area overlay on narrow terminals — so the
278+
// panel and the messages stay aligned and both restore to full width when it closes. contentWidth
279+
// (the shared content-column width) derives from it, so the width math has one definition and the
280+
// two cannot structurally drift.
281+
const panelAvailable = createMemo(() => sessionAvailable(dimensions().width, dimensions().height, sidebarVisible()))
282+
const contentWidth = createMemo(() => panelAvailable().width)
283+
// altimate_change end
276284
const providers = createMemo(() => Model.index(sync.data.provider))
277285

278286
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
@@ -1187,13 +1195,10 @@ export function Session() {
11871195
/discover) starts a session. Outside the scrollbox: the bordered panel
11881196
does not paint reliably inside the scroll viewport. */}
11891197
<box flexShrink={0}>
1190-
{/* Size to the panel's real space, not the terminal (#1067):
1191-
contentWidth already subtracts the sidebar + padding;
1192-
-PANEL_VERTICAL_RESERVE leaves room for the prompt + footer. */}
1193-
<WelcomePanel
1194-
availableWidth={contentWidth()}
1195-
availableHeight={dimensions().height - PANEL_VERTICAL_RESERVE}
1196-
/>
1198+
{/* Size to the panel's real space, not the terminal (#1067).
1199+
panelAvailable() subtracts the in-flow sidebar + padding and
1200+
reserves the prompt row (see the memo above). */}
1201+
<WelcomePanel availableWidth={panelAvailable().width} availableHeight={panelAvailable().height} />
11971202
</box>
11981203
{/* altimate_change end */}
11991204
<scrollbox

‎packages/tui/test/component/welcome-panel-utils.test.ts‎

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,41 @@ import { expect, test } from "bun:test"
22
import {
33
FULL_MIN_HEIGHT,
44
FULL_MIN_WIDTH,
5+
HOME_VERTICAL_RESERVE,
56
MEDIUM_MIN_HEIGHT,
67
MEDIUM_MIN_WIDTH,
7-
PANEL_VERTICAL_RESERVE,
8+
SESSION_VERTICAL_RESERVE,
9+
homeAvailable,
10+
sessionAvailable,
811
welcomePanelVariant,
912
} from "../../src/component/welcome-panel-utils"
1013

1114
// Comfortably above the full floor on one axis, used to isolate the OTHER axis
1215
// so a single gate's removal is provable (each test below fails if its `<` check
13-
// is deleted from the source).
16+
// is deleted from the source, or flipped to `<=`).
1417
const TALL = FULL_MIN_HEIGHT + 10
1518
const WIDE = FULL_MIN_WIDTH + 20
1619

20+
// Map a terminal size to a variant THROUGH the exact functions the routes call
21+
// (homeAvailable / sessionAvailable), so these tests exercise the real call-site
22+
// arithmetic rather than a private copy of it. `sidebarVisible` is the route's
23+
// sidebarVisible() — the content column narrows whenever the sidebar is open.
24+
const home = (w: number, h: number) => {
25+
const a = homeAvailable(w, h)
26+
return welcomePanelVariant(a.width, a.height)
27+
}
28+
const session = (w: number, h: number, sidebarVisible: boolean) => {
29+
const a = sessionAvailable(w, h, sidebarVisible)
30+
return welcomePanelVariant(a.width, a.height)
31+
}
32+
33+
test("reserves stay pinned to the counted chrome", () => {
34+
// If someone changes the reserve to a wrong literal, this fails. The values are
35+
// derived sums of the documented per-route chrome (see welcome-panel-utils.ts).
36+
expect(HOME_VERTICAL_RESERVE).toBe(15)
37+
expect(SESSION_VERTICAL_RESERVE).toBe(9)
38+
})
39+
1740
test("full requires BOTH width and height to clear the full floor", () => {
1841
expect(welcomePanelVariant(WIDE, TALL)).toBe("full")
1942
expect(welcomePanelVariant(FULL_MIN_WIDTH, FULL_MIN_HEIGHT)).toBe("full") // exactly at the floor
@@ -42,23 +65,58 @@ test("compact→medium boundary is exact (at the floor is medium)", () => {
4265
})
4366

4467
test("everyday terminals get medium, not the oversized wordmark", () => {
45-
// Inputs are AVAILABLE size (terminal minus padding/sidebar on width, minus
46-
// PANEL_VERTICAL_RESERVE on height). A 106x31 terminal → ~(102, 23):
47-
expect(welcomePanelVariant(102, 23)).toBe("medium")
48-
// 80x24 terminal → ~(76, 16) — medium exactly at the height floor:
49-
expect(welcomePanelVariant(76, 16)).toBe("medium")
50-
// #1067 session case: a 130-col terminal with the 42-col sidebar leaves ~84
51-
// usable cols → medium (was wrongly full when it used the whole terminal width).
52-
expect(welcomePanelVariant(130 - 42 - 4, 50 - PANEL_VERTICAL_RESERVE)).toBe("medium")
68+
expect(home(106, 31)).toBe("medium")
69+
expect(home(80, 24)).toBe("medium")
70+
// #1067 session case: a 130-col terminal with the in-flow 42-col sidebar leaves
71+
// ~84 usable cols → medium (was wrongly full when it used the whole terminal).
72+
expect(session(130, 50, true)).toBe("medium")
73+
})
74+
75+
test("the classic 80x24 is medium on both routes (a real fit, not a fake margin)", () => {
76+
// 80x24 → home available (76 × 9), session available (76 × 15). Both clear the
77+
// medium floor (60 × 8) — the medium panel is ~8 rows, so this actually fits.
78+
expect(home(80, 24)).toBe("medium")
79+
expect(session(80, 24, false)).toBe("medium")
80+
})
81+
82+
test("full engages on a large window; ~one row below the floor stays medium", () => {
83+
// full needs available height ≥ FULL_MIN_HEIGHT(30); home reserves 15, so the
84+
// terminal must be ≥ 45 rows.
85+
expect(home(120, 45)).toBe("full")
86+
expect(home(120, 44)).toBe("medium")
87+
})
88+
89+
test("toggling the in-flow session sidebar flips the panel full → medium on a wide window (#1067)", () => {
90+
// The exact regression #1067 reports: on a wide window the sidebar is in-flow,
91+
// and opening it must shrink the panel out of `full` (no longer ≥110 usable cols).
92+
expect(session(150, 50, false)).toBe("full")
93+
expect(session(150, 50, true)).toBe("medium")
94+
})
95+
96+
test("the content column narrows whenever the sidebar is open, incl. the overlay (aligned with messages)", () => {
97+
// On a ≤120-col terminal the sidebar is a dimmed full-area overlay, but the
98+
// content column (messages + panel) still narrows uniformly so they stay
99+
// aligned and both restore when it closes — sizing to the narrowed column, not
100+
// the transient obscured width. 100 cols: open → 54 usable → compact; closed →
101+
// 96 → medium. (Matches session's contentWidth basis; deliberate, per review.)
102+
expect(session(100, 50, true)).toBe("compact")
103+
expect(session(100, 50, false)).toBe("medium")
104+
})
105+
106+
test("a short terminal drops to compact once the route's chrome is reserved (#1067 height)", () => {
107+
// 120x22: wide, but too few usable rows after the home chrome → compact, where
108+
// the raw terminal height (22) would have picked medium.
109+
expect(home(120, 22)).toBe("compact")
53110
})
54111

55-
test("a short terminal drops to compact once prompt/footer chrome is reserved (#1067 height)", () => {
56-
// 120x22: wide, but only ~14 usable rows after the ~8-row chrome → compact,
57-
// where the raw terminal height (22) would have picked medium.
58-
expect(welcomePanelVariant(120 - 4, 22 - PANEL_VERTICAL_RESERVE)).toBe("compact")
112+
test("negative available height (a tiny terminal) collapses to compact, never throws", () => {
113+
// 80x5 → home available height 5 - 15 = -10; must resolve, not crash.
114+
expect(home(80, 5)).toBe("compact")
115+
expect(session(80, 5, false)).toBe("compact")
59116
})
60117

61118
test("degenerate sizes collapse to compact", () => {
62119
expect(welcomePanelVariant(0, 0)).toBe("compact")
63120
expect(welcomePanelVariant(1, 1)).toBe("compact")
121+
expect(welcomePanelVariant(-5, -5)).toBe("compact")
64122
})

0 commit comments

Comments
 (0)