From 6d4df7520876c6cb12079be8fa6327d486bf9139 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 7 Aug 2026 00:09:00 -0700 Subject: [PATCH 1/3] Show the running version on the landing screen An operator has no way to tell which build is running once the terminal is open, which matters when a release changes default behaviour. The version is read from package.json and painted beside the two existing hints so it cannot drift from what shipped. --- src/tui-opentui/landing.test.ts | 5 +++++ src/tui-opentui/landing.ts | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) 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 From c50ced80b4e38a5acdad88d7f2f47eb34fa27002 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 7 Aug 2026 00:19:34 -0700 Subject: [PATCH 2/3] Describe what compaction options do to the conversation Summarize and drop named the mechanism but not the tradeoff: an operator had no way to know summarize spends a model call while drop is free, or that drop also strips tool output from every turn it keeps, not only the ones it discards. The setting row now states the cost difference, that behaviour, and which mode is the default. --- src/tui-opentui/command-surfaces.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tui-opentui/command-surfaces.ts b/src/tui-opentui/command-surfaces.ts index dbe73fea8..2615bc065 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) spends a call; drop is free but also strips tool output.", + tone: "consequence", }, cycle: (dir) => settings.setCompactionMode( From c02f6eb70182f5edfc5a6edaca9f25d6a5e623ec Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 7 Aug 2026 00:36:07 -0700 Subject: [PATCH 3/3] Fit the compaction description on one line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At 80 columns the description zone gives impact exactly one wrapped line with no truncation marker, so the previous sentence silently cut off before "output" — the exact fact the copy exists to state. Shortened it so the cost difference and the strip-in-kept-turns behaviour both survive at the harness's default width. --- src/tui-opentui/command-surfaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tui-opentui/command-surfaces.ts b/src/tui-opentui/command-surfaces.ts index 2615bc065..72e77ccfa 100644 --- a/src/tui-opentui/command-surfaces.ts +++ b/src/tui-opentui/command-surfaces.ts @@ -300,7 +300,7 @@ function settingsCycleRows( describe: { what: "how the transcript is trimmed once the context fills.", impact: - "summarize (default) spends a call; drop is free but also strips tool output.", + "summarize (default) costs a call; drop is free but strips output too.", tone: "consequence", }, cycle: (dir) =>