diff --git a/src/tui-opentui/command-surfaces.ts b/src/tui-opentui/command-surfaces.ts index dbe73fea8..72e77ccfa 100644 --- a/src/tui-opentui/command-surfaces.ts +++ b/src/tui-opentui/command-surfaces.ts @@ -300,7 +300,8 @@ function settingsCycleRows( describe: { what: "how the transcript is trimmed once the context fills.", impact: - "summarize spends a model call and keeps the thread; drop is instant and loses the middle of the session.", + "summarize (default) costs a call; drop is free but strips output too.", + tone: "consequence", }, cycle: (dir) => settings.setCompactionMode( diff --git a/src/tui-opentui/landing.test.ts b/src/tui-opentui/landing.test.ts index 1938da0b5..f077c7107 100644 --- a/src/tui-opentui/landing.test.ts +++ b/src/tui-opentui/landing.test.ts @@ -20,6 +20,7 @@ import { makeOperatorQuestion, openOperatorOverlay } from "./overlays" import { LANDING_HINTS, LANDING_SUGGESTIONS, + LANDING_VERSION, landingBelowContent, landingBelowRows, landingSuggestionFor, @@ -28,6 +29,7 @@ import { wrapLanding, } from "./landing" import { LOCKUP_WORDMARK } from "./lockup" +import pkg from "../../package.json" with { type: "json" } import { MARK_LARGE, MARK_MID, MARK_SMALL } from "./mark-shape" import { UI } from "./theme" @@ -151,6 +153,9 @@ describe("landing screen", () => { expect(row).toContain(hint.key) expect(row!.indexOf(hint.key)).toBeGreaterThan(0) } + // The version sits with the hints, and cannot drift from package.json. + expect(LANDING_VERSION).toBe(`v${pkg.version}`) + expect(h.captureCharFrame()).toContain(LANDING_VERSION) const noticeRow = painted.findIndex((row) => row.includes("telemetry")) expect(noticeRow).toBeGreaterThan(bottom) for (const item of LANDING_SUGGESTIONS) { diff --git a/src/tui-opentui/landing.ts b/src/tui-opentui/landing.ts index 26a87c670..adb5fdc53 100644 --- a/src/tui-opentui/landing.ts +++ b/src/tui-opentui/landing.ts @@ -30,6 +30,7 @@ import { type TextChunk, } from "@opentui/core" import { BoxRenderable, TextRenderable } from "@opentui/core" +import pkg from "../../package.json" with { type: "json" } import { MARK_LARGE, MARK_MID, MARK_SMALL, type MarkGrid } from "./mark-shape.js" import { renderMark } from "./mark-anim.js" @@ -49,6 +50,9 @@ const MARK_GAP_ROWS = 1 /** Columns of air between the mark's right edge and the hint block. */ export const LANDING_HERO_GAP = 3 +/** The running build, read from `package.json` so it cannot drift from what shipped. */ +export const LANDING_VERSION = `v${pkg.version}` + /** * The two doors off the landing screen. Every other key lives behind one of * them, so this list never grows. @@ -62,9 +66,9 @@ export const LANDING_HINTS: readonly { ] /** Columns the hint block needs, its longest line deciding. */ -export const LANDING_HINT_WIDTH = LANDING_HINTS.reduce( - (widest, hint) => Math.max(widest, hint.key.length + 1 + hint.rest.length), - 0, +export const LANDING_HINT_WIDTH = Math.max( + LANDING_HINTS.reduce((widest, hint) => Math.max(widest, hint.key.length + 1 + hint.rest.length), 0), + LANDING_VERSION.length, ) /** Largest first: the landing takes the best-reading mark its zone can seat. */ @@ -343,6 +347,14 @@ function createHintBlock(ctx: CliRenderer): BoxRenderable { }), ) }) + block.add( + new TextRenderable(ctx, { + id: "shell-landing-version", + height: 1, + content: LANDING_VERSION, + fg: UI.textFaint, + }), + ) return block } @@ -352,7 +364,7 @@ function createHintBlock(ctx: CliRenderer): BoxRenderable { */ export function fitLandingMark(above: LandingAbove, grid: MarkGrid | null): void { above.grid = grid - const rows = grid?.rows ?? LANDING_HINTS.length + const rows = grid?.rows ?? LANDING_HINTS.length + 1 above.hero.height = rows above.markColumn.visible = grid !== null above.markColumn.width = grid?.cols ?? 0