mini: add hideable footer details (#38192)

This commit is contained in:
Simon Klee 2026-07-21 23:28:40 +02:00 committed by GitHub
commit 2ed8fe5960
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 130 additions and 16 deletions

View file

@ -55,7 +55,7 @@ test("down opens subagents from an empty prompt", async () => {
subagent={subagents}
theme={() => RUN_THEME_FALLBACK}
tuiConfig={config}
miniSettings={() => ({ thinking: "hide", shell_output: "hide", turn_summary: "show", mono: false })}
miniSettings={() => ({ thinking: "hide", shell_output: "hide", turn_summary: "show", footer: "show", mono: false })}
mono={false}
onSubmit={() => true}
onPermissionReply={() => {}}

View file

@ -101,7 +101,7 @@ function footerState(input: Partial<FooterState> = {}) {
interrupt: 0,
exit: 0,
...input,
})[0]
})
}
async function renderFooter(
@ -129,10 +129,10 @@ async function renderFooter(
const [subagents] = createSignal<FooterSubagentState>(
input.subagents ?? { tabs: [], details: {}, permissions: [], forms: [] },
)
const state = footerState(input.state)
const [state, setState] = footerState(input.state)
const config = input.tuiConfig ?? tuiConfig
const [miniSettings] = createSignal<MiniSettings>(
input.miniSettings ?? { thinking: "hide", shell_output: "hide", turn_summary: "show", mono: false },
input.miniSettings ?? { thinking: "hide", shell_output: "hide", turn_summary: "show", footer: "show", mono: false },
)
function Harness() {
return (
@ -186,6 +186,7 @@ async function renderFooter(
return {
...app,
setView,
setState,
cleanup() {
app.renderer.currentFocusedRenderable?.blur()
app.renderer.currentFocusedEditor?.blur()
@ -362,6 +363,7 @@ test("direct command panel renders grouped command palette", async () => {
])
const [subagents] = createSignal([])
const [variants] = createSignal(["high", "minimal"])
let status = 0
const app = await testRender(
() => (
@ -381,6 +383,7 @@ test("direct command panel renders grouped command palette", async () => {
onQueued={() => {}}
onVariant={() => {}}
onVariantCycle={() => {}}
onStatus={() => status++}
onSettings={() => {}}
onCommand={() => {}}
onNew={() => {}}
@ -406,6 +409,7 @@ test("direct command panel renders grouped command palette", async () => {
expect(frame).toContain("Prompt")
expect(frame).toContain("Open editor")
expect(frame).toContain("/editor")
expect(frame).toContain("Show status")
expect(frame).toContain("Switch model")
expect(frame).toContain("Skills")
expect(frame).toContain("/skills")
@ -417,6 +421,12 @@ test("direct command panel renders grouped command palette", async () => {
expect(frame).not.toContain("Cycle reasoning effort for future turns")
expect(frame).not.toContain("Review code")
expect(frame).not.toContain("Commands 8")
await app.mockInput.typeText("status")
await app.renderOnce()
expect(app.captureCharFrame()).toContain("Show status")
app.mockInput.pressEnter()
expect(status).toBe(1)
} finally {
app.renderer.destroy()
}
@ -427,6 +437,7 @@ test("direct settings panel changes Mini transcript preferences", async () => {
thinking: "hide",
shell_output: "hide",
turn_summary: "show",
footer: "show",
mono: false,
})
const app = await testRender(
@ -454,6 +465,7 @@ test("direct settings panel changes Mini transcript preferences", async () => {
expect(frame).toContain("Thinking")
expect(frame).toContain("Shell")
expect(frame).toContain("Turn summary")
expect(frame).toContain("Footer details")
expect(frame).toContain("Monochrome UI")
expect(frame).toContain("left/right change")
expect(frame).not.toMatch(/[^\x00-\x7F]/)
@ -461,15 +473,16 @@ 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", mono: false })
expect(settings()).toEqual({ thinking: "show", shell_output: "hide", turn_summary: "show", footer: "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", mono: false })
expect(settings()).toEqual({ thinking: "show", shell_output: "hide", turn_summary: "hide", footer: "show", mono: false })
app.mockInput.pressKey("ARROW_DOWN")
app.mockInput.pressKey("ARROW_DOWN")
app.mockInput.pressKey("ARROW_RIGHT")
await app.renderOnce()
@ -589,6 +602,7 @@ test("direct command panel shows subagent entry when available", async () => {
onQueued={() => {}}
onVariant={() => {}}
onVariantCycle={() => {}}
onStatus={() => {}}
onSettings={() => {}}
onCommand={() => {}}
onNew={() => {}}
@ -638,6 +652,7 @@ test("direct command panel keeps completed subagents available", async () => {
onQueued={() => {}}
onVariant={() => {}}
onVariantCycle={() => {}}
onStatus={() => {}}
onSettings={() => {}}
onCommand={() => {}}
onNew={() => {}}
@ -1127,7 +1142,7 @@ test("direct footer shows authoritative pending work while running", async () =>
]}
theme={() => RUN_THEME_FALLBACK}
tuiConfig={tuiConfig}
miniSettings={() => ({ thinking: "hide", shell_output: "hide", turn_summary: "show", mono: false })}
miniSettings={() => ({ thinking: "hide", shell_output: "hide", turn_summary: "show", footer: "show", mono: false })}
mono={false}
onSubmit={() => true}
onPermissionReply={() => {}}
@ -1301,6 +1316,39 @@ test("direct footer shows full usage metadata when room is available", async ()
}
})
test("direct footer can hide persistent details and briefly reveal changes", async () => {
const app = await renderFooter({
state: { usage: "159.6K (16%) · $4.23" },
miniSettings: {
thinking: "hide",
shell_output: "hide",
turn_summary: "show",
footer: "hide",
mono: true,
},
mono: true,
width: 160,
})
try {
await app.renderOnce()
const initial = app.captureCharFrame()
expect(initial).toContain("ctrl+p cmd")
expect(initial).not.toContain("gpt-5")
expect(initial).not.toContain("159.6K")
app.setState((state) => ({ ...state, phase: "running" }))
await app.renderOnce()
const changed = app.captureCharFrame()
const statusline = footerStatusline(app.renderer.root)
expect(changed).toContain("running - gpt-5 - 159.6K (16%) - $4.23")
expect(boxPath(statusline, "SpinnerRenderable")).toBeUndefined()
} finally {
app.cleanup()
}
})
test("direct footer does not label normal mode as build", async () => {
const app = await renderFooter()

View file

@ -107,14 +107,18 @@ describe("run runtime boot", () => {
thinking: "hide",
shell_output: "hide",
turn_summary: "show",
footer: "show",
mono: false,
})
expect(
resolveMiniSettings({ mini: { thinking: "show", shell_output: "show", turn_summary: "hide", mono: true } }),
resolveMiniSettings({
mini: { thinking: "show", shell_output: "show", turn_summary: "hide", footer: "hide", mono: true },
}),
).toEqual({
thinking: "show",
shell_output: "show",
turn_summary: "hide",
footer: "hide",
mono: true,
})
})