mini: add hideable footer details (#38192)
This commit is contained in:
parent
1ea9137c01
commit
2ed8fe5960
8 changed files with 130 additions and 16 deletions
|
|
@ -133,6 +133,9 @@ export const Info = Schema.Struct({
|
||||||
turn_summary: Schema.optional(Schema.Literals(["show", "hide"])).annotate({
|
turn_summary: Schema.optional(Schema.Literals(["show", "hide"])).annotate({
|
||||||
description: "Show or hide the agent, model, and duration summary in scrollback",
|
description: "Show or hide the agent, model, and duration summary in scrollback",
|
||||||
}),
|
}),
|
||||||
|
footer: Schema.optional(Schema.Literals(["show", "hide"])).annotate({
|
||||||
|
description: "Show or hide persistent activity, model, usage, and context details in the footer",
|
||||||
|
}),
|
||||||
mono: Schema.optional(Schema.Boolean).annotate({
|
mono: Schema.optional(Schema.Boolean).annotate({
|
||||||
description: "Use monochrome ASCII output",
|
description: "Use monochrome ASCII output",
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ type CommandEntry =
|
||||||
| (PanelEntry & { action: "skill" })
|
| (PanelEntry & { action: "skill" })
|
||||||
| (PanelEntry & { action: "queued" })
|
| (PanelEntry & { action: "queued" })
|
||||||
| (PanelEntry & { action: "subagent" })
|
| (PanelEntry & { action: "subagent" })
|
||||||
|
| (PanelEntry & { action: "status" })
|
||||||
| (PanelEntry & { action: "variant.cycle" })
|
| (PanelEntry & { action: "variant.cycle" })
|
||||||
| (PanelEntry & { action: "variant.list" })
|
| (PanelEntry & { action: "variant.list" })
|
||||||
| (PanelEntry & { action: "settings" })
|
| (PanelEntry & { action: "settings" })
|
||||||
|
|
@ -363,6 +364,7 @@ export function RunCommandMenuBody(props: {
|
||||||
onQueued: () => void
|
onQueued: () => void
|
||||||
onVariant: () => void
|
onVariant: () => void
|
||||||
onVariantCycle: () => void
|
onVariantCycle: () => void
|
||||||
|
onStatus: () => void
|
||||||
onSettings: () => void
|
onSettings: () => void
|
||||||
onCommand: (name: string) => void
|
onCommand: (name: string) => void
|
||||||
onNew: () => void
|
onNew: () => void
|
||||||
|
|
@ -381,6 +383,12 @@ export function RunCommandMenuBody(props: {
|
||||||
footer: "/editor",
|
footer: "/editor",
|
||||||
keywords: "editor compose draft external editor",
|
keywords: "editor compose draft external editor",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: "status",
|
||||||
|
category: "Session",
|
||||||
|
display: "Show status",
|
||||||
|
keywords: "status activity model context usage footer",
|
||||||
|
},
|
||||||
...(props.subagents().length > 0
|
...(props.subagents().length > 0
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
|
@ -526,6 +534,11 @@ export function RunCommandMenuBody(props: {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.action === "status") {
|
||||||
|
props.onStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (item.action === "settings") {
|
if (item.action === "settings") {
|
||||||
props.onSettings()
|
props.onSettings()
|
||||||
return
|
return
|
||||||
|
|
@ -613,6 +626,13 @@ export function RunSettingsBody(props: {
|
||||||
keywords: `turn summary agent model duration ${props.settings().turn_summary}`,
|
keywords: `turn summary agent model duration ${props.settings().turn_summary}`,
|
||||||
key: "turn_summary",
|
key: "turn_summary",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
category: "Terminal",
|
||||||
|
display: "Footer details",
|
||||||
|
footer: saving() === "footer" ? "saving" : props.settings().footer,
|
||||||
|
keywords: `footer status activity model context usage ${props.settings().footer}`,
|
||||||
|
key: "footer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
category: "Terminal",
|
category: "Terminal",
|
||||||
display: "Monochrome UI",
|
display: "Monochrome UI",
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ import type { RunTheme } from "./theme"
|
||||||
|
|
||||||
registerOpencodeSpinner()
|
registerOpencodeSpinner()
|
||||||
|
|
||||||
|
const FOOTER_DETAIL_DURATION = 3000
|
||||||
|
|
||||||
const EMPTY_BORDER = {
|
const EMPTY_BORDER = {
|
||||||
topLeft: "",
|
topLeft: "",
|
||||||
bottomLeft: "",
|
bottomLeft: "",
|
||||||
|
|
@ -189,6 +191,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
const armed = createMemo(() => props.state().interrupt > 0)
|
const armed = createMemo(() => props.state().interrupt > 0)
|
||||||
const exiting = createMemo(() => props.state().exit > 0)
|
const exiting = createMemo(() => props.state().exit > 0)
|
||||||
const usage = createMemo(() => props.state().usage)
|
const usage = createMemo(() => props.state().usage)
|
||||||
|
const footerDetails = createMemo(() => props.miniSettings().footer === "show")
|
||||||
const interruptLabel = createMemo(() => {
|
const interruptLabel = createMemo(() => {
|
||||||
if (!interrupt()) {
|
if (!interrupt()) {
|
||||||
return
|
return
|
||||||
|
|
@ -217,6 +220,33 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
color: createColors(options),
|
color: createColors(options),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const footerStatus = createMemo(() => {
|
||||||
|
const current = model() ?? props.state().model.trim()
|
||||||
|
const variant = props.currentVariant()
|
||||||
|
const details = [busy() ? "running" : "idle"]
|
||||||
|
if (current) details.push(variant ? `${current} ${variant}` : current)
|
||||||
|
if (usage()) details.push(props.mono ? usage().replaceAll(" · ", " - ") : usage())
|
||||||
|
if (queuedPrompts().length > 0) details.push(`${queuedPrompts().length} pending`)
|
||||||
|
if (activeTabs().length > 0) details.push(`${activeTabs().length} subagent${activeTabs().length === 1 ? "" : "s"}`)
|
||||||
|
return details.join(props.mono ? " - " : " · ")
|
||||||
|
})
|
||||||
|
const [footerNotice, setFooterNotice] = createSignal("")
|
||||||
|
let footerNoticeTimeout: ReturnType<typeof setTimeout> | undefined
|
||||||
|
let previousFooterStatus: string | undefined
|
||||||
|
const showFooterStatus = () => {
|
||||||
|
if (footerNoticeTimeout) clearTimeout(footerNoticeTimeout)
|
||||||
|
setFooterNotice(footerStatus())
|
||||||
|
footerNoticeTimeout = setTimeout(() => {
|
||||||
|
footerNoticeTimeout = undefined
|
||||||
|
setFooterNotice("")
|
||||||
|
}, FOOTER_DETAIL_DURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const current = footerStatus()
|
||||||
|
if (previousFooterStatus !== undefined && previousFooterStatus !== current && !footerDetails()) showFooterStatus()
|
||||||
|
previousFooterStatus = current
|
||||||
|
})
|
||||||
const permission = createMemo<Extract<FooterView, { type: "permission" }> | undefined>(() => {
|
const permission = createMemo<Extract<FooterView, { type: "permission" }> | undefined>(() => {
|
||||||
const view = active()
|
const view = active()
|
||||||
return view.type === "permission" ? view : undefined
|
return view.type === "permission" ? view : undefined
|
||||||
|
|
@ -375,9 +405,11 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
return `Press ${clearShortcut() || "ctrl+c"} again to exit`
|
return `Press ${clearShortcut() || "ctrl+c"} again to exit`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (busy()) {
|
if (busy() && armed()) return "again to interrupt"
|
||||||
return armed() ? "again to interrupt" : "interrupt"
|
|
||||||
}
|
if (footerNotice()) return footerNotice()
|
||||||
|
|
||||||
|
if (busy()) return "interrupt"
|
||||||
|
|
||||||
if (stateStatus().length > 0) {
|
if (stateStatus().length > 0) {
|
||||||
return stateStatus()
|
return stateStatus()
|
||||||
|
|
@ -386,7 +418,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
return shell() ? "Shell mode" : ""
|
return shell() ? "Shell mode" : ""
|
||||||
})
|
})
|
||||||
const activityMeta = createMemo(() => {
|
const activityMeta = createMemo(() => {
|
||||||
if (!responsive().statusline.showActivityMeta || usage().length === 0) {
|
if (!footerDetails() || !responsive().statusline.showActivityMeta || usage().length === 0) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -394,7 +426,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
})
|
})
|
||||||
const modelStatus = createMemo(() => {
|
const modelStatus = createMemo(() => {
|
||||||
const current = model()
|
const current = model()
|
||||||
if (!prompt() || !responsive().statusline.showModel || !current) return
|
if (!footerDetails() || !prompt() || !responsive().statusline.showModel || !current) return
|
||||||
return {
|
return {
|
||||||
model: current,
|
model: current,
|
||||||
variant: responsive().statusline.showModelVariant ? props.currentVariant() : undefined,
|
variant: responsive().statusline.showModelVariant ? props.currentVariant() : undefined,
|
||||||
|
|
@ -409,7 +441,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
return theme().highlight
|
return theme().highlight
|
||||||
}
|
}
|
||||||
|
|
||||||
if (busy() || stateStatus().length > 0) {
|
if (busy() || footerNotice().length > 0 || stateStatus().length > 0) {
|
||||||
return theme().text
|
return theme().text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,7 +451,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
const hasActivityMeta = createMemo(() => activityMeta().length > 0)
|
const hasActivityMeta = createMemo(() => activityMeta().length > 0)
|
||||||
const hasModelStatus = createMemo(() => Boolean(modelStatus()))
|
const hasModelStatus = createMemo(() => Boolean(modelStatus()))
|
||||||
const contextHints = createMemo(() => {
|
const contextHints = createMemo(() => {
|
||||||
if (!prompt() || shell() || !responsive().statusline.showContextHints) {
|
if (!footerDetails() || !prompt() || shell() || !responsive().statusline.showContextHints) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -459,6 +491,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
props.onRequestExit?.(undefined)
|
props.onRequestExit?.(undefined)
|
||||||
|
if (footerNoticeTimeout) clearTimeout(footerNoticeTimeout)
|
||||||
})
|
})
|
||||||
|
|
||||||
Keymap.createLayer(() => ({
|
Keymap.createLayer(() => ({
|
||||||
|
|
@ -699,6 +732,10 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
props.onCycle()
|
props.onCycle()
|
||||||
closePanel()
|
closePanel()
|
||||||
}}
|
}}
|
||||||
|
onStatus={() => {
|
||||||
|
showFooterStatus()
|
||||||
|
closePanel()
|
||||||
|
}}
|
||||||
onCommand={(name) => {
|
onCommand={(name) => {
|
||||||
composer.submitText(`/${name}`)
|
composer.submitText(`/${name}`)
|
||||||
closePanel()
|
closePanel()
|
||||||
|
|
@ -857,7 +894,7 @@ export function RunFooterView(props: RunFooterViewProps) {
|
||||||
paddingRight={1}
|
paddingRight={1}
|
||||||
backgroundColor="transparent"
|
backgroundColor="transparent"
|
||||||
>
|
>
|
||||||
<Show when={busy() && !exiting()}>
|
<Show when={footerDetails() && busy() && !exiting()}>
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
<spinner color={spin().color} frames={spin().frames} interval={40} />
|
<spinner color={spin().color} frames={spin().frames} interval={40} />
|
||||||
</box>
|
</box>
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ export function resolveMiniSettings(config?: { mini?: Partial<MiniSettings> }):
|
||||||
thinking: config?.mini?.thinking ?? "hide",
|
thinking: config?.mini?.thinking ?? "hide",
|
||||||
shell_output: config?.mini?.shell_output ?? "hide",
|
shell_output: config?.mini?.shell_output ?? "hide",
|
||||||
turn_summary: config?.mini?.turn_summary ?? "show",
|
turn_summary: config?.mini?.turn_summary ?? "show",
|
||||||
|
footer: config?.mini?.footer ?? "show",
|
||||||
mono: config?.mini?.mono ?? false,
|
mono: config?.mini?.mono ?? false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -398,6 +398,7 @@ export type MiniSettings = {
|
||||||
thinking: "show" | "hide"
|
thinking: "show" | "hide"
|
||||||
shell_output: "show" | "hide"
|
shell_output: "show" | "hide"
|
||||||
turn_summary: "show" | "hide"
|
turn_summary: "show" | "hide"
|
||||||
|
footer: "show" | "hide"
|
||||||
mono: boolean
|
mono: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ test("down opens subagents from an empty prompt", async () => {
|
||||||
subagent={subagents}
|
subagent={subagents}
|
||||||
theme={() => RUN_THEME_FALLBACK}
|
theme={() => RUN_THEME_FALLBACK}
|
||||||
tuiConfig={config}
|
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}
|
mono={false}
|
||||||
onSubmit={() => true}
|
onSubmit={() => true}
|
||||||
onPermissionReply={() => {}}
|
onPermissionReply={() => {}}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ function footerState(input: Partial<FooterState> = {}) {
|
||||||
interrupt: 0,
|
interrupt: 0,
|
||||||
exit: 0,
|
exit: 0,
|
||||||
...input,
|
...input,
|
||||||
})[0]
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderFooter(
|
async function renderFooter(
|
||||||
|
|
@ -129,10 +129,10 @@ async function renderFooter(
|
||||||
const [subagents] = createSignal<FooterSubagentState>(
|
const [subagents] = createSignal<FooterSubagentState>(
|
||||||
input.subagents ?? { tabs: [], details: {}, permissions: [], forms: [] },
|
input.subagents ?? { tabs: [], details: {}, permissions: [], forms: [] },
|
||||||
)
|
)
|
||||||
const state = footerState(input.state)
|
const [state, setState] = footerState(input.state)
|
||||||
const config = input.tuiConfig ?? tuiConfig
|
const config = input.tuiConfig ?? tuiConfig
|
||||||
const [miniSettings] = createSignal<MiniSettings>(
|
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() {
|
function Harness() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -186,6 +186,7 @@ async function renderFooter(
|
||||||
return {
|
return {
|
||||||
...app,
|
...app,
|
||||||
setView,
|
setView,
|
||||||
|
setState,
|
||||||
cleanup() {
|
cleanup() {
|
||||||
app.renderer.currentFocusedRenderable?.blur()
|
app.renderer.currentFocusedRenderable?.blur()
|
||||||
app.renderer.currentFocusedEditor?.blur()
|
app.renderer.currentFocusedEditor?.blur()
|
||||||
|
|
@ -362,6 +363,7 @@ test("direct command panel renders grouped command palette", async () => {
|
||||||
])
|
])
|
||||||
const [subagents] = createSignal([])
|
const [subagents] = createSignal([])
|
||||||
const [variants] = createSignal(["high", "minimal"])
|
const [variants] = createSignal(["high", "minimal"])
|
||||||
|
let status = 0
|
||||||
|
|
||||||
const app = await testRender(
|
const app = await testRender(
|
||||||
() => (
|
() => (
|
||||||
|
|
@ -381,6 +383,7 @@ test("direct command panel renders grouped command palette", async () => {
|
||||||
onQueued={() => {}}
|
onQueued={() => {}}
|
||||||
onVariant={() => {}}
|
onVariant={() => {}}
|
||||||
onVariantCycle={() => {}}
|
onVariantCycle={() => {}}
|
||||||
|
onStatus={() => status++}
|
||||||
onSettings={() => {}}
|
onSettings={() => {}}
|
||||||
onCommand={() => {}}
|
onCommand={() => {}}
|
||||||
onNew={() => {}}
|
onNew={() => {}}
|
||||||
|
|
@ -406,6 +409,7 @@ test("direct command panel renders grouped command palette", async () => {
|
||||||
expect(frame).toContain("Prompt")
|
expect(frame).toContain("Prompt")
|
||||||
expect(frame).toContain("Open editor")
|
expect(frame).toContain("Open editor")
|
||||||
expect(frame).toContain("/editor")
|
expect(frame).toContain("/editor")
|
||||||
|
expect(frame).toContain("Show status")
|
||||||
expect(frame).toContain("Switch model")
|
expect(frame).toContain("Switch model")
|
||||||
expect(frame).toContain("Skills")
|
expect(frame).toContain("Skills")
|
||||||
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("Cycle reasoning effort for future turns")
|
||||||
expect(frame).not.toContain("Review code")
|
expect(frame).not.toContain("Review code")
|
||||||
expect(frame).not.toContain("Commands 8")
|
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 {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
@ -427,6 +437,7 @@ test("direct settings panel changes Mini transcript preferences", async () => {
|
||||||
thinking: "hide",
|
thinking: "hide",
|
||||||
shell_output: "hide",
|
shell_output: "hide",
|
||||||
turn_summary: "show",
|
turn_summary: "show",
|
||||||
|
footer: "show",
|
||||||
mono: false,
|
mono: false,
|
||||||
})
|
})
|
||||||
const app = await testRender(
|
const app = await testRender(
|
||||||
|
|
@ -454,6 +465,7 @@ test("direct settings panel changes Mini transcript preferences", async () => {
|
||||||
expect(frame).toContain("Thinking")
|
expect(frame).toContain("Thinking")
|
||||||
expect(frame).toContain("Shell")
|
expect(frame).toContain("Shell")
|
||||||
expect(frame).toContain("Turn summary")
|
expect(frame).toContain("Turn summary")
|
||||||
|
expect(frame).toContain("Footer details")
|
||||||
expect(frame).toContain("Monochrome UI")
|
expect(frame).toContain("Monochrome UI")
|
||||||
expect(frame).toContain("left/right change")
|
expect(frame).toContain("left/right change")
|
||||||
expect(frame).not.toMatch(/[^\x00-\x7F]/)
|
expect(frame).not.toMatch(/[^\x00-\x7F]/)
|
||||||
|
|
@ -461,15 +473,16 @@ test("direct settings panel changes Mini transcript preferences", async () => {
|
||||||
app.mockInput.pressKey("ARROW_RIGHT")
|
app.mockInput.pressKey("ARROW_RIGHT")
|
||||||
await app.renderOnce()
|
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_DOWN")
|
app.mockInput.pressKey("ARROW_DOWN")
|
||||||
app.mockInput.pressKey("ARROW_RIGHT")
|
app.mockInput.pressKey("ARROW_RIGHT")
|
||||||
await app.renderOnce()
|
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_DOWN")
|
||||||
app.mockInput.pressKey("ARROW_RIGHT")
|
app.mockInput.pressKey("ARROW_RIGHT")
|
||||||
await app.renderOnce()
|
await app.renderOnce()
|
||||||
|
|
@ -589,6 +602,7 @@ test("direct command panel shows subagent entry when available", async () => {
|
||||||
onQueued={() => {}}
|
onQueued={() => {}}
|
||||||
onVariant={() => {}}
|
onVariant={() => {}}
|
||||||
onVariantCycle={() => {}}
|
onVariantCycle={() => {}}
|
||||||
|
onStatus={() => {}}
|
||||||
onSettings={() => {}}
|
onSettings={() => {}}
|
||||||
onCommand={() => {}}
|
onCommand={() => {}}
|
||||||
onNew={() => {}}
|
onNew={() => {}}
|
||||||
|
|
@ -638,6 +652,7 @@ test("direct command panel keeps completed subagents available", async () => {
|
||||||
onQueued={() => {}}
|
onQueued={() => {}}
|
||||||
onVariant={() => {}}
|
onVariant={() => {}}
|
||||||
onVariantCycle={() => {}}
|
onVariantCycle={() => {}}
|
||||||
|
onStatus={() => {}}
|
||||||
onSettings={() => {}}
|
onSettings={() => {}}
|
||||||
onCommand={() => {}}
|
onCommand={() => {}}
|
||||||
onNew={() => {}}
|
onNew={() => {}}
|
||||||
|
|
@ -1127,7 +1142,7 @@ test("direct footer shows authoritative pending work while running", async () =>
|
||||||
]}
|
]}
|
||||||
theme={() => RUN_THEME_FALLBACK}
|
theme={() => RUN_THEME_FALLBACK}
|
||||||
tuiConfig={tuiConfig}
|
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}
|
mono={false}
|
||||||
onSubmit={() => true}
|
onSubmit={() => true}
|
||||||
onPermissionReply={() => {}}
|
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 () => {
|
test("direct footer does not label normal mode as build", async () => {
|
||||||
const app = await renderFooter()
|
const app = await renderFooter()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,14 +107,18 @@ describe("run runtime boot", () => {
|
||||||
thinking: "hide",
|
thinking: "hide",
|
||||||
shell_output: "hide",
|
shell_output: "hide",
|
||||||
turn_summary: "show",
|
turn_summary: "show",
|
||||||
|
footer: "show",
|
||||||
mono: false,
|
mono: false,
|
||||||
})
|
})
|
||||||
expect(
|
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({
|
).toEqual({
|
||||||
thinking: "show",
|
thinking: "show",
|
||||||
shell_output: "show",
|
shell_output: "show",
|
||||||
turn_summary: "hide",
|
turn_summary: "hide",
|
||||||
|
footer: "hide",
|
||||||
mono: true,
|
mono: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue