diff --git a/packages/cli/test/config.test.ts b/packages/cli/test/config.test.ts index 6af4b66d26..a0e32c4aa8 100644 --- a/packages/cli/test/config.test.ts +++ b/packages/cli/test/config.test.ts @@ -131,7 +131,7 @@ test("updates a config draft while preserving JSONC comments", async () => { const service = yield* Config.Service return yield* service.update((draft) => { draft.prompt = { paste: "compact" } - draft.mini = { thinking: "hide", shell_output: "hide", turn_summary: "hide", mono: true } + draft.mini = { thinking: "hide", shell_output: "hide", turn_summary: "hide", splash: "hide", mono: true } }) }), ) @@ -139,7 +139,7 @@ test("updates a config draft while preserving JSONC comments", async () => { expect(config).toEqual({ animations: true, prompt: { paste: "compact" }, - mini: { thinking: "hide", shell_output: "hide", turn_summary: "hide", mono: true }, + mini: { thinking: "hide", shell_output: "hide", turn_summary: "hide", splash: "hide", mono: true }, }) expect(await Bun.file(path.join(directory, "cli.json")).text()).toContain("// Keep this comment") } finally { diff --git a/packages/tui/src/config/index.tsx b/packages/tui/src/config/index.tsx index 5afb61ae18..6f5b78185e 100644 --- a/packages/tui/src/config/index.tsx +++ b/packages/tui/src/config/index.tsx @@ -136,6 +136,9 @@ export const Info = Schema.Struct({ footer: Schema.optional(Schema.Literals(["show", "hide"])).annotate({ description: "Show or hide persistent activity, model, usage, and context details in the footer", }), + splash: Schema.optional(Schema.Literals(["show", "hide"])).annotate({ + description: "Show or hide the entry and exit splash banners", + }), mono: Schema.optional(Schema.Boolean).annotate({ description: "Use monochrome ASCII output", }), diff --git a/packages/tui/src/mini/footer.command.tsx b/packages/tui/src/mini/footer.command.tsx index 48c11f951c..38be78a682 100644 --- a/packages/tui/src/mini/footer.command.tsx +++ b/packages/tui/src/mini/footer.command.tsx @@ -684,6 +684,13 @@ export function RunSettingsBody(props: { keywords: `footer status activity model context usage ${props.settings().footer}`, key: "footer", }, + { + category: "Terminal", + display: "Splash", + footer: saving() === "splash" ? "saving" : props.settings().splash, + keywords: `splash entry exit banner ${props.settings().splash}`, + key: "splash", + }, { category: "Terminal", display: "Monochrome UI", diff --git a/packages/tui/src/mini/footer.ts b/packages/tui/src/mini/footer.ts index a403542219..002cc62616 100644 --- a/packages/tui/src/mini/footer.ts +++ b/packages/tui/src/mini/footer.ts @@ -616,6 +616,10 @@ export class RunFooter implements FooterApi { return this.theme() } + public currentMiniSettings(): MiniSettings { + return this.miniSettings() + } + private destroyTheme(theme: RunTheme): void { const index = this.themes.indexOf(theme) if (index === -1) { diff --git a/packages/tui/src/mini/runtime.boot.ts b/packages/tui/src/mini/runtime.boot.ts index 2f5e807d5e..3a65ce19c0 100644 --- a/packages/tui/src/mini/runtime.boot.ts +++ b/packages/tui/src/mini/runtime.boot.ts @@ -90,6 +90,7 @@ export function resolveMiniSettings(config?: { mini?: Partial }): shell_output: config?.mini?.shell_output ?? "hide", turn_summary: config?.mini?.turn_summary ?? "show", footer: config?.mini?.footer ?? "show", + splash: config?.mini?.splash ?? "show", mono: config?.mini?.mono ?? false, } } diff --git a/packages/tui/src/mini/runtime.lifecycle.ts b/packages/tui/src/mini/runtime.lifecycle.ts index 4d715add83..315335da82 100644 --- a/packages/tui/src/mini/runtime.lifecycle.ts +++ b/packages/tui/src/mini/runtime.lifecycle.ts @@ -196,13 +196,15 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise {}) @@ -224,7 +226,9 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise {}) - const show = renderer.isDestroyed ? false : next.showExit - if (!renderer.isDestroyed && show) { + if (!renderer.isDestroyed && next.showExit && footer.currentMiniSettings().splash === "show") { const sessionID = next.sessionID || input.getSessionID?.() || input.sessionID const splash = splashInfo(next.sessionTitle ?? input.sessionTitle, next.history ?? input.history) wroteExit = queueSplash( diff --git a/packages/tui/src/mini/types.ts b/packages/tui/src/mini/types.ts index ea300c3222..2ee854010b 100644 --- a/packages/tui/src/mini/types.ts +++ b/packages/tui/src/mini/types.ts @@ -393,6 +393,7 @@ export type MiniSettings = { shell_output: "show" | "hide" turn_summary: "show" | "hide" footer: "show" | "hide" + splash: "show" | "hide" mono: boolean } diff --git a/packages/tui/test/mini/footer-keymap.test.tsx b/packages/tui/test/mini/footer-keymap.test.tsx index 65562a4f02..cca3526576 100644 --- a/packages/tui/test/mini/footer-keymap.test.tsx +++ b/packages/tui/test/mini/footer-keymap.test.tsx @@ -56,7 +56,14 @@ test("down opens subagents from an empty prompt", async () => { view={view} subagent={subagents} theme={() => RUN_THEME_FALLBACK} - miniSettings={() => ({ thinking: "hide", shell_output: "hide", turn_summary: "show", footer: "show", mono: false })} + miniSettings={() => ({ + thinking: "hide", + shell_output: "hide", + turn_summary: "show", + footer: "show", + splash: "show", + mono: false, + })} mono={false} onSubmit={() => true} onPermissionReply={() => {}} diff --git a/packages/tui/test/mini/footer.view.test.tsx b/packages/tui/test/mini/footer.view.test.tsx index d200fe80ad..6fb78b1bea 100644 --- a/packages/tui/test/mini/footer.view.test.tsx +++ b/packages/tui/test/mini/footer.view.test.tsx @@ -136,7 +136,14 @@ async function renderFooter( const [state, setState] = footerState(input.state) const config = input.tuiConfig ?? tuiConfig const [miniSettings] = createSignal( - input.miniSettings ?? { thinking: "hide", shell_output: "hide", turn_summary: "show", footer: "show", mono: false }, + input.miniSettings ?? { + thinking: "hide", + shell_output: "hide", + turn_summary: "show", + footer: "show", + splash: "show", + mono: false, + }, ) function Harness() { return ( @@ -471,6 +478,7 @@ test("direct settings panel changes Mini transcript preferences", async () => { shell_output: "hide", turn_summary: "show", footer: "show", + splash: "show", mono: false, }) const app = await testRender( @@ -499,6 +507,7 @@ test("direct settings panel changes Mini transcript preferences", async () => { expect(frame).toContain("Shell") expect(frame).toContain("Turn summary") expect(frame).toContain("Footer details") + expect(frame).toContain("Splash") expect(frame).toContain("Monochrome UI") expect(frame).toContain("left/right change") expect(frame).not.toMatch(/[^\x00-\x7F]/) @@ -506,16 +515,35 @@ test("direct settings panel changes Mini transcript preferences", async () => { app.mockInput.pressKey("ARROW_RIGHT") await app.renderOnce() - expect(settings()).toEqual({ thinking: "show", shell_output: "hide", turn_summary: "show", footer: "show", mono: false }) + expect(settings()).toEqual({ + thinking: "show", + shell_output: "hide", + turn_summary: "show", + footer: "show", + splash: "show", + mono: false, + }) app.mockInput.pressKey("ARROW_DOWN") app.mockInput.pressKey("ARROW_DOWN") app.mockInput.pressKey("ARROW_RIGHT") await app.renderOnce() - expect(settings()).toEqual({ thinking: "show", shell_output: "hide", turn_summary: "hide", footer: "show", mono: false }) + expect(settings()).toEqual({ + thinking: "show", + shell_output: "hide", + turn_summary: "hide", + footer: "show", + splash: "show", + mono: false, + }) app.mockInput.pressKey("ARROW_DOWN") + app.mockInput.pressKey("ARROW_DOWN") + app.mockInput.pressKey("ARROW_RIGHT") + await app.renderOnce() + expect(settings().splash).toBe("hide") + app.mockInput.pressKey("ARROW_DOWN") app.mockInput.pressKey("ARROW_RIGHT") await app.renderOnce() @@ -1169,7 +1197,14 @@ test("direct footer shows authoritative pending work while running", async () => }, ]} theme={() => RUN_THEME_FALLBACK} - miniSettings={() => ({ thinking: "hide", shell_output: "hide", turn_summary: "show", footer: "show", mono: false })} + miniSettings={() => ({ + thinking: "hide", + shell_output: "hide", + turn_summary: "show", + footer: "show", + splash: "show", + mono: false, + })} mono={false} onSubmit={() => true} onPermissionReply={() => {}} @@ -1357,6 +1392,7 @@ test("direct footer hides routine activity and shows explicit notices", async () shell_output: "hide", turn_summary: "show", footer: "hide", + splash: "show", mono: true, }, mono: true, diff --git a/packages/tui/test/mini/runtime.boot.test.ts b/packages/tui/test/mini/runtime.boot.test.ts index 431b5bdc01..734d2a4923 100644 --- a/packages/tui/test/mini/runtime.boot.test.ts +++ b/packages/tui/test/mini/runtime.boot.test.ts @@ -106,17 +106,26 @@ describe("run runtime boot", () => { shell_output: "hide", turn_summary: "show", footer: "show", + splash: "show", mono: false, }) expect( resolveMiniSettings({ - mini: { thinking: "show", shell_output: "show", turn_summary: "hide", footer: "hide", mono: true }, + mini: { + thinking: "show", + shell_output: "show", + turn_summary: "hide", + footer: "hide", + splash: "hide", + mono: true, + }, }), ).toEqual({ thinking: "show", shell_output: "show", turn_summary: "hide", footer: "hide", + splash: "hide", mono: true, }) })