Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions packages/tui/src/component/welcome-panel-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,46 @@
// 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.
// with the panel (see the per-route reserves below), 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
// (`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"

/**
* 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
// Rows the panel must leave for the always-present chrome that shares its column.
// The two routes have different chrome, so they reserve different amounts; the
// shared thresholds below then transition ~3 rows of terminal height apart
// between the routes (session, with less chrome, shows a bigger variant sooner).
// Estimates — the prompt can grow with multi-line input; this is the at-rest cost.
//
// home (routes/home.tsx): top spacer (2) + prompt wrapper paddingTop (1) + prompt
// (~4) + footer (~3, feature-plugins/home/footer.tsx) ≈ 10.
export const HOME_VERTICAL_RESERVE = 10
// session (routes/session/index.tsx): two column gaps (2) + paddingBottom (1) +
// prompt (~4); no top spacer, no footer in that column ≈ 7.
export const SESSION_VERTICAL_RESERVE = 7

// Columns the home slot spends on its own left/right padding (2 + 2); the caller
// subtracts this to get the panel's usable width. (Session's contentWidth applies
// the same 4 as part of its own content-column math.)
export const PANEL_HORIZONTAL_PADDING = 4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The padding centralization is incomplete: PANEL_HORIZONTAL_PADDING is used at the home call site, but the session route's contentWidth still hardcodes the bare - 4 rather than referencing the constant (the new doc comment even acknowledges session "applies the same 4 as part of its own content-column math"). If this constant ever changes, the session width math won't follow and drifts out of sync; consider having session/index.tsx import PANEL_HORIZONTAL_PADDING so both routes share one source of truth.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tui/src/component/welcome-panel-utils.ts, line 37:

<comment>The padding centralization is incomplete: PANEL_HORIZONTAL_PADDING is used at the home call site, but the session route's contentWidth still hardcodes the bare `- 4` rather than referencing the constant (the new doc comment even acknowledges session "applies the same 4 as part of its own content-column math"). If this constant ever changes, the session width math won't follow and drifts out of sync; consider having session/index.tsx import PANEL_HORIZONTAL_PADDING so both routes share one source of truth.</comment>

<file context>
@@ -7,36 +7,46 @@
+// Columns the home slot spends on its own left/right padding (2 + 2); the caller
+// subtracts this to get the panel's usable width. (Session's contentWidth applies
+// the same 4 as part of its own content-column math.)
+export const PANEL_HORIZONTAL_PADDING = 4
 
-/** Minimum usable size for the medium panel; below either → compact (one line). */
</file context>


/** Minimum usable size for the medium panel; below either compact (one line). */
/** 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 MEDIUM_MIN_HEIGHT = 13
/** Minimum usable size for the full wordmark panel; below either -> medium. */
export const FULL_MIN_WIDTH = 110
export const FULL_MIN_HEIGHT = 36
export const FULL_MIN_HEIGHT = 34

/**
* 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
* minus padding/sidebar, height already minus the route's 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 {
Expand Down
31 changes: 17 additions & 14 deletions packages/tui/src/component/welcome-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Match, Show, Switch, 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"
Expand All @@ -16,27 +15,31 @@ const CONNECT_CTA = "Connect your AI model to start."
// blank its top rows.
//
// 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):
// box that ate ~40% of the screen. It now scales down by AVAILABLE size — the
// caller measures the terminal (useTerminalDimensions) and passes what the panel
// actually gets, following the repo's breakpoint idiom (a createMemo over the
// reactive dimensions, 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)
// medium — title + a condensed description (one line on a wide terminal, two at
// medium's narrow end), 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 }) {
// (session's contentWidth), and the route's vertical reserve (HOME/SESSION_
// 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 props are REQUIRED: a call site that forgot one would
// silently get the pre-fix raw-terminal behavior, so the type system guards it
// (there's no in-repo render test of the call sites).
export function WelcomePanel(props: { availableWidth: number; availableHeight: number }) {
const { theme } = useTheme()
const ready = useReady()
const dimensions = useTerminalDimensions()

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

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

Expand Down
10 changes: 5 additions & 5 deletions packages/tui/src/routes/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +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"
import { HOME_VERTICAL_RESERVE, PANEL_HORIZONTAL_PADDING } from "../component/welcome-panel-utils"
// altimate_change end

let once = false
Expand Down Expand Up @@ -110,11 +110,11 @@ export function Home() {
<box width="100%" flexShrink={0}>
<pluginRuntime.Slot name="home_logo" mode="replace">
{/* 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. */}
-PANEL_HORIZONTAL_PADDING for this column's paddingLeft/Right;
-HOME_VERTICAL_RESERVE for the top spacer + prompt + footer. */}
<WelcomePanel
availableWidth={dimensions().width - 4}
availableHeight={dimensions().height - PANEL_VERTICAL_RESERVE}
availableWidth={dimensions().width - PANEL_HORIZONTAL_PADDING}
availableHeight={dimensions().height - HOME_VERTICAL_RESERVE}
/>
</pluginRuntime.Slot>
</box>
Expand Down
6 changes: 3 additions & 3 deletions packages/tui/src/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +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 { SESSION_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"
Expand Down Expand Up @@ -1189,10 +1189,10 @@ export function Session() {
<box flexShrink={0}>
{/* 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. */}
-SESSION_VERTICAL_RESERVE leaves room for the prompt. */}
<WelcomePanel
availableWidth={contentWidth()}
availableHeight={dimensions().height - PANEL_VERTICAL_RESERVE}
availableHeight={dimensions().height - SESSION_VERTICAL_RESERVE}
/>
</box>
{/* altimate_change end */}
Expand Down
47 changes: 35 additions & 12 deletions packages/tui/test/component/welcome-panel-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ import { expect, test } from "bun:test"
import {
FULL_MIN_HEIGHT,
FULL_MIN_WIDTH,
HOME_VERTICAL_RESERVE,
MEDIUM_MIN_HEIGHT,
MEDIUM_MIN_WIDTH,
PANEL_VERTICAL_RESERVE,
PANEL_HORIZONTAL_PADDING,
SESSION_VERTICAL_RESERVE,
welcomePanelVariant,
} from "../../src/component/welcome-panel-utils"

// 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).
// is deleted from the source, or flipped to `<=`).
const TALL = FULL_MIN_HEIGHT + 10
const WIDE = FULL_MIN_WIDTH + 20

// Map a terminal size to the AVAILABLE size each route feeds the pure function.
const home = (w: number, h: number) => welcomePanelVariant(w - PANEL_HORIZONTAL_PADDING, h - HOME_VERTICAL_RESERVE)
const session = (w: number, h: number, sidebar: boolean) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The session test helper duplicates the exact width formula (including the magic sidebar width 42) that lives inline in routes/session/index.tsx:275. If the sidebar width or content-column math changes in the component, this mirror silently diverges and the #1067 pin no longer guards the real path. Consider exporting the sidebar width/formula from welcome-panel-utils (as was done for PANEL_HORIZONTAL_PADDING) and importing it in both the component and this test so they stay in sync.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tui/test/component/welcome-panel-utils.test.ts, line 21:

<comment>The session test helper duplicates the exact width formula (including the magic sidebar width 42) that lives inline in routes/session/index.tsx:275. If the sidebar width or content-column math changes in the component, this mirror silently diverges and the #1067 pin no longer guards the real path. Consider exporting the sidebar width/formula from welcome-panel-utils (as was done for PANEL_HORIZONTAL_PADDING) and importing it in both the component and this test so they stay in sync.</comment>

<file context>
@@ -2,18 +2,25 @@ import { expect, test } from "bun:test"
 
+// Map a terminal size to the AVAILABLE size each route feeds the pure function.
+const home = (w: number, h: number) => welcomePanelVariant(w - PANEL_HORIZONTAL_PADDING, h - HOME_VERTICAL_RESERVE)
+const session = (w: number, h: number, sidebar: boolean) =>
+  welcomePanelVariant(w - (sidebar ? 42 : 0) - PANEL_HORIZONTAL_PADDING, h - SESSION_VERTICAL_RESERVE)
+
</file context>

welcomePanelVariant(w - (sidebar ? 42 : 0) - PANEL_HORIZONTAL_PADDING, h - SESSION_VERTICAL_RESERVE)

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
Expand Down Expand Up @@ -42,20 +49,36 @@ test("compact→medium boundary is exact (at the floor is medium)", () => {
})

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")
expect(home(106, 31)).toBe("medium")
expect(home(80, 24)).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")
expect(session(130, 50, true)).toBe("medium")
})

test("the classic 80x24 is medium on both routes, with margin off the compact floor", () => {
// The review flagged 80x24 sitting on the exact medium floor; it now clears it
// on both routes (home reserves more chrome, so it's the tighter one).
expect(home(80, 24)).toBe("medium")
expect(session(80, 24, false)).toBe("medium")
})

test("full engages on a large window; ~one row below the floor stays medium", () => {
expect(home(120, 44)).toBe("full") // FULL_MIN_HEIGHT(34) + HOME_VERTICAL_RESERVE(10)
expect(home(120, 43)).toBe("medium")
})

test("toggling the session sidebar flips the panel full → medium on a wide window (#1067)", () => {
// The exact regression #1067 reports: on a wide window, opening the 42-col
// sidebar must shrink the panel out of `full` — it no longer has ~110 usable cols.
expect(session(150, 50, false)).toBe("full")
expect(session(150, 50, true)).toBe("medium")
})

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("a short terminal drops to compact once the route's chrome is reserved (#1067 height)", () => {
// 120x22: wide, but too few usable rows after the home chrome → compact, where
// the raw terminal height (22) would have picked medium.
expect(home(120, 22)).toBe("compact")
})

test("degenerate sizes collapse to compact", () => {
Expand Down
Loading