refactor(tui): simplify mini footer status line (#38112)

This commit is contained in:
Simon Klee 2026-07-21 16:08:19 +02:00 committed by GitHub
commit dd50d457b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 111 additions and 102 deletions

View file

@ -81,6 +81,9 @@ describe("run catalog shared", () => {
cost: {
input: 0,
},
limit: {
context: 128_000,
},
status: "active",
variants: {
high: {},

View file

@ -259,15 +259,9 @@ function footerComposerFrame(root: BoxRenderable | RootRenderable) {
function footerStatusline(root: BoxRenderable | RootRenderable) {
const status = (RUN_THEME_FALLBACK.footer.status as RGBA).toInts()
const accent = (RUN_THEME_FALLBACK.footer.statusAccent as RGBA).toInts()
const boxes = root.getChildren().filter((item): item is BoxRenderable => item instanceof BoxRenderable)
for (const box of boxes) {
const first = box.getChildren().find((item): item is BoxRenderable => item instanceof BoxRenderable)
if (
box.backgroundColor?.toInts().every((value, index) => value === status[index]) &&
first?.backgroundColor?.toInts().every((value, index) => value === accent[index])
)
return box
if (box.backgroundColor?.toInts().every((value, index) => value === status[index])) return box
boxes.push(...box.getChildren().filter((item): item is BoxRenderable => item instanceof BoxRenderable))
}
throw new Error("Footer statusline not found")
@ -1076,30 +1070,25 @@ test("direct footer shows authoritative pending work while running", async () =>
const frame = app.captureCharFrame()
const transparent = RGBA.fromValues(0, 0, 0, 0).toInts()
const tinted = (RUN_THEME_FALLBACK.footer.status as RGBA).toInts()
const accent = (RUN_THEME_FALLBACK.footer.statusAccent as RGBA).toInts()
const statusline = footerStatusline(app.renderer.root)
const statusItems = statusline.getChildren().filter((item): item is BoxRenderable => item instanceof BoxRenderable)
const mode = statusItems[0]
const main = statusItems[1]
const main = statusItems[0]
const spinner = main.getChildren()[0]
const model = statusItems[2]
const queued = statusItems[3]
const background = statusItems[1]
const queued = statusItems[2]
const hint = statusItems.at(-1)!
expect(spinner).toBeDefined()
expect(frame).toContain("a-model-name-long-enough-to-force-responsive-truncation")
expect(frame).toContain("1 pending")
expect(frame).toContain("ctrl+b background")
expect(frame).toContain("ctrl+x q 1 pending")
expect(frame).toContain("↓ subagents")
expect(frame).toContain("ctrl+p cmd")
expect(frame).toContain("a-model-name-long-enough-to-force-responsive-truncation")
expect(frame).toContain("subagents · ctrl+p cmd")
expect(frame).not.toContain("1 agent")
expect(statusline.backgroundColor.toInts()).toEqual(tinted)
expect(mode.backgroundColor.toInts()).toEqual(accent)
expect(main.backgroundColor.toInts()).toEqual(transparent)
expect(model.backgroundColor.toInts()).toEqual(transparent)
expect(background.backgroundColor.toInts()).toEqual(transparent)
expect(queued.backgroundColor.toInts()).toEqual(transparent)
expect(hint.backgroundColor.toInts()).toEqual(transparent)
} finally {
@ -1127,9 +1116,7 @@ test("direct footer always offers backgrounding for a foreground subagent", asyn
await app.renderOnce()
const frame = app.captureCharFrame()
expect(frame).toContain("GPT-5")
expect(frame).toContain("xhigh · ctrl+b background · ↓ subagents · ctrl+p cmd")
expect(frame).toContain("ctrl+b background")
expect(frame).toContain("ctrl+b background · ↓ subagents · ctrl+p cmd")
expect(frame).not.toContain("queued")
} finally {
app.cleanup()
@ -1154,8 +1141,7 @@ test("direct footer hides the subagent hint when only completed subagents remain
await app.renderOnce()
const frame = app.captureCharFrame()
expect(frame).toContain("GPT-5")
expect(frame).toContain("xhigh · ctrl+p cmd")
expect(frame).toContain("ctrl+p cmd")
expect(frame).not.toContain("↓ subagents")
} finally {
app.cleanup()
@ -1194,7 +1180,7 @@ test("direct footer shows full usage metadata when room is available", async ()
}
})
test("direct footer mode label keeps left padding without a status pill", async () => {
test("direct footer does not label normal mode as build", async () => {
const app = await renderFooter()
try {
@ -1202,10 +1188,10 @@ test("direct footer mode label keeps left padding without a status pill", async
const statusline = app
.captureCharFrame()
.split("\n")
.find((line) => line.includes("BUILD") && line.includes("cmd"))
.find((line) => line.includes("cmd"))
expect(statusline).toBeDefined()
expect(statusline?.startsWith(" BUILD ")).toBe(true)
expect(statusline).not.toContain("BUILD")
} finally {
app.cleanup()
}

View file

@ -9,7 +9,6 @@ describe("run footer width", () => {
expect(narrow.statusline.showCommandHint).toBe(true)
expect(narrow.statusline.showContextHints).toBe(false)
expect(narrow.statusline.contextHintLimit).toBe(0)
expect(narrow.statusline.showModel).toBe(false)
const command = footerWidthPolicy(65)
expect(command.statusline.showCommandHint).toBe(false)
@ -22,14 +21,11 @@ describe("run footer width", () => {
expect(compact.statusline.showActivityMeta).toBe(true)
expect(compact.statusline.showContextHints).toBe(true)
expect(compact.statusline.contextHintLimit).toBe(1)
expect(compact.statusline.showModel).toBe(false)
const model = footerWidthPolicy(120)
expect(model.statusline.contextHintLimit).toBe(2)
expect(model.statusline.showModel).toBe(true)
const context = footerWidthPolicy(120)
expect(context.statusline.contextHintLimit).toBe(2)
const spacious = footerWidthPolicy(150)
expect(spacious.statusline.contextHintLimit).toBeUndefined()
expect(spacious.statusline.showModel).toBe(true)
})
})

View file

@ -128,6 +128,9 @@ describe("run runtime boot", () => {
cost: {
input: 0,
},
limit: {
context: 128_000,
},
status: "active",
variants: {
high: {},

View file

@ -200,6 +200,50 @@ afterEach(() => {
})
describe("V2 mini transport", () => {
test("formats footer usage with compact tokens and context percentage", async () => {
const events = feed()
events.push(connected())
const ui = footer()
const transport = await createSessionTransport({
sdk: sdk({ streams: [events] }),
sessionID: "ses_1",
thinking: false,
footer: ui.api,
contextLimit: (model) =>
model.providerID === "test" && model.modelID === "model" ? 160_000 : undefined,
})
events.push({
id: "evt_step_started",
created: 1,
type: "session.step.started",
durable: durable("ses_1", 1),
data: {
sessionID: "ses_1",
assistantMessageID: "msg_assistant",
agent: "build",
model: { providerID: "test", id: "model" },
},
})
events.push({
id: "evt_step_ended",
created: 2,
type: "session.step.ended",
durable: durable("ses_1", 2),
data: {
sessionID: "ses_1",
assistantMessageID: "msg_assistant",
finish: "stop",
cost: 0,
tokens: { input: 7_000, output: 500, reasoning: 8, cache: { read: 0, write: 0 } },
},
})
while (!ui.events.some((event) => event.type === "stream.patch" && event.patch.usage)) await Bun.sleep(0)
expect(ui.events).toContainEqual({ type: "stream.patch", patch: { usage: "7.5K (5%)" } })
await transport.close()
})
test("recursively hydrates blockers for direct and transitive descendants", async () => {
const events = feed()
events.push(connected())