style(tui): simplify runtime header controls

This commit is contained in:
chengyongru
2026-08-31 17:03:24 +08:00
committed by chengyongru
parent e69159cdae
commit 6d6d58d329
3 changed files with 20 additions and 70 deletions
+19 -28
View File
@@ -205,7 +205,7 @@ describe("NanobotTui layout", () => {
expect(occurrences(frame, "Ask nanobot anything")).toBe(1)
expect(occurrences(frame, "Ready")).toBe(0)
expect(occurrences(frame, "Getting ready…")).toBe(1)
expect(occurrences(frame, "nanobot test/model")).toBe(1)
expect(occurrences(frame, "default ▾")).toBe(1)
}
app.accept({ event: "attached", chat_id: "chat" })
@@ -985,7 +985,6 @@ describe("NanobotTui layout", () => {
const ui = app as unknown as {
composer: TextareaRenderable
sessionMenu: { visible: boolean }
titleText: { plainText: string }
runtimeControls: { modelText: { plainText: string } }
}
@@ -999,8 +998,7 @@ describe("NanobotTui layout", () => {
ui.composer.submit()
await waitUntil(() => attached.length === 1)
expect(attached).toEqual(["other"])
expect(ui.titleText.plainText).toContain("Release checklist")
expect(ui.runtimeControls.modelText.plainText).toContain("Deep Research")
expect(ui.runtimeControls.modelText.plainText).toBe("Deep Research ▾")
expect(ui.runtimeControls.modelText.plainText).not.toContain("test/model")
app.accept({ event: "attached", chat_id: "other" })
@@ -1009,8 +1007,7 @@ describe("NanobotTui layout", () => {
ui.composer.submit()
await waitUntil(() => newChats.length === 1)
expect(newChats).toEqual(["new"])
expect(ui.titleText.plainText).toContain("New chat")
expect(ui.runtimeControls.modelText.plainText).toContain("test/model")
expect(ui.runtimeControls.modelText.plainText).toBe("default ▾")
} finally {
globalThis.fetch = original
}
@@ -1182,7 +1179,8 @@ describe("NanobotTui layout", () => {
model_preset: "Codex",
})
await setup.flush()
expect(ui.runtimeControls.modelText.plainText).toContain("Codex openai/gpt-5.6")
expect(ui.runtimeControls.modelText.plainText).toBe("Codex ")
expect(ui.runtimeControls.modelText.plainText).not.toContain("openai/gpt-5.6")
app.accept({
event: "runtime_model_updated",
@@ -1190,7 +1188,7 @@ describe("NanobotTui layout", () => {
model_preset: "DeepSeek",
})
await setup.flush()
expect(ui.runtimeControls.modelText.plainText).toContain("Codex openai/gpt-5.6")
expect(ui.runtimeControls.modelText.plainText).toBe("Codex ")
expect(ui.runtimeControls.modelText.plainText).not.toContain("DeepSeek")
})
@@ -1212,8 +1210,8 @@ describe("NanobotTui layout", () => {
})
await setup.flush()
expect(ui.runtimeControls.modelText.plainText).toContain("deepseek/deepseek-chat")
expect(ui.runtimeControls.modelText.plainText).not.toContain("Codex")
expect(ui.runtimeControls.modelText.plainText).toBe("default ▾")
expect(ui.runtimeControls.modelText.plainText).not.toContain("deepseek/deepseek-chat")
})
test("refreshes the canonical preset after the model command completes", async () => {
@@ -1309,7 +1307,6 @@ describe("NanobotTui layout", () => {
menuRoot: { getChildren(): unknown[] }
}
composer: TextareaRenderable
titleText: TextRenderable
status: TextRenderable
meta: TextRenderable
}
@@ -1324,7 +1321,6 @@ describe("NanobotTui layout", () => {
expect(ui.runtimeControls.modelText.selectable).toBe(false)
expect(ui.runtimeControls.accessText.selectable).toBe(false)
expect(ui.runtimeControls.contextText.selectable).toBe(false)
expect(ui.titleText.selectable).toBe(false)
expect(ui.status.selectable).toBe(false)
expect(ui.meta.selectable).toBe(false)
app.accept({ event: "goal_status", chat_id: "chat", status: "running" })
@@ -1394,7 +1390,7 @@ describe("NanobotTui layout", () => {
}
})
test("opens and switches sessions from the clickable title", async () => {
test("switches sessions only through the sessions command", async () => {
const original = globalThis.fetch
globalThis.fetch = ((input: string | URL | Request) => {
const url = String(input)
@@ -1420,14 +1416,18 @@ describe("NanobotTui layout", () => {
const ui = app as unknown as {
composer: TextareaRenderable
sessionMenu: { visible: boolean; root: { getChildren(): unknown[] } }
titleText: TextRenderable
status: TextRenderable
title: { getChildren(): unknown[] }
}
try {
await waitUntil(() => (app as unknown as { ready: boolean }).ready)
await setup.renderOnce()
await setup.mockMouse.click(ui.titleText.x + 2, ui.titleText.y)
const titleItems = ui.title.getChildren() as TextRenderable[]
expect(titleItems.some((item) => item.id === "nanobot-tui-title-text")).toBe(false)
expect(ui.sessionMenu.visible).toBe(false)
ui.composer.setText("/sessions")
ui.composer.submit()
await waitUntil(() => ui.sessionMenu.visible)
await setup.flush()
expect(ui.composer.placeholder).toBe("Search sessions")
@@ -1441,15 +1441,6 @@ describe("NanobotTui layout", () => {
expect(attached).toEqual(["other"])
expect(ui.sessionMenu.visible).toBe(false)
expect(ui.composer.focused).toBe(true)
expect(ui.titleText.plainText).toContain("Release checklist")
app.accept({ event: "attached", chat_id: "other" })
await setup.mockMouse.click(ui.titleText.x + 2, ui.titleText.y)
await waitUntil(() => ui.sessionMenu.visible)
ui.composer.blur()
await setup.mockMouse.click(ui.status.x, ui.status.y)
expect(ui.sessionMenu.visible).toBe(false)
expect(ui.composer.focused).toBe(true)
} finally {
globalThis.fetch = original
}
@@ -1715,7 +1706,7 @@ describe("NanobotTui layout", () => {
await setup.flush()
const frame = setup.captureCharFrame()
expect(frame).toContain("Release checklist")
expect(occurrences(frame, "Current chat")).toBe(1)
expect(occurrences(frame, "Current chat")).toBe(0)
} finally {
globalThis.fetch = original
}
@@ -1959,7 +1950,7 @@ describe("NanobotTui layout", () => {
} else if (width >= 28 && height >= 9) {
expect(occurrences(frame, "Enter now · Tab next")).toBe(1)
}
expect(occurrences(frame, "nanobot test/model")).toBe(height >= 14 ? 1 : 0)
expect(occurrences(frame, "default ▾")).toBe(height >= 14 ? 1 : 0)
}
})
@@ -3122,7 +3113,7 @@ describe("NanobotTui with a Herdr pane title reporter", () => {
await setup.flush()
const activeFrame = setup.captureCharFrame()
expect(activeFrame).toContain(">_ nanobot")
expect(activeFrame).toContain("test/model")
expect(activeFrame).toContain("default ▾")
expect(occurrences(activeFrame, " Ship the Herdr integration")).toBe(1)
expect(occurrences(activeFrame, "app.ts")).toBe(1)
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
-38
View File
@@ -442,7 +442,6 @@ export class NanobotTui {
private readonly client: ChatClient
private readonly shell: BoxRenderable
private readonly title: BoxRenderable
private readonly titleText: TextRenderable
private readonly composerFrame: BoxRenderable
private readonly composer: TextareaRenderable
private composerSyntax: SyntaxStyle
@@ -649,28 +648,6 @@ export class NanobotTui {
alignItems: "center",
backgroundColor: RGBA.defaultBackground(),
})
this.titleText = new TextRenderable(renderer, {
id: "nanobot-tui-title-text",
content: "nanobot",
height: 1,
flexShrink: 0,
truncate: true,
fg: this.palette.muted,
selectable: false,
onMouseOver: () => { this.titleText.fg = this.palette.accent },
onMouseOut: () => this.renderTitleColor(),
onMouseDown: (event) => {
if (event.button !== 0) return
event.preventDefault()
event.stopPropagation()
this.renderer.clearSelection()
if (this.sessionLoading || this.sessionMenu.visible) {
this.closeSessions()
return
}
void this.openSessions()
},
})
this.runtimeControls = new RuntimeControls(
renderer,
runtimeControlsTheme(this.palette),
@@ -703,7 +680,6 @@ export class NanobotTui {
},
},
)
this.title.add(this.titleText)
this.title.add(this.runtimeControls.modelText)
this.title.add(this.runtimeControls.accessText)
this.title.add(this.runtimeControls.contextText)
@@ -1877,7 +1853,6 @@ export class NanobotTui {
this.composer.syntaxStyle = this.composerSyntax
this.syncComposerImageHighlights(this.composer.plainText)
void this.renderer.idle().catch(() => {}).finally(() => previousComposerSyntax.destroy())
this.renderTitleColor()
this.status.fg = this.palette.muted
this.meta.fg = this.palette.faint
this.updateMeta()
@@ -1957,9 +1932,6 @@ export class NanobotTui {
}
private updateTitle(): void {
const identity = this.sessionTitle.trim() || "nanobot"
this.titleText.maxWidth = Math.max(8, Math.floor(this.renderer.width * 0.38))
this.titleText.content = identity
const context = this.contextTokens === null
? ""
: ` ~${formatTokenCount(this.contextTokens)}${this.contextWindowTokens
@@ -1969,12 +1941,6 @@ export class NanobotTui {
this.runtimeControls.updateContext(context)
}
private renderTitleColor(): void {
this.titleText.fg = this.sessionLoading || this.sessionMenu.visible
? this.palette.accent
: this.palette.muted
}
private resizeComposer(): void {
const verticalPadding = this.renderer.height >= 12 ? 1 : 0
const maxContentHeight = Math.max(1, Math.min(12, Math.floor(this.renderer.height / 3)))
@@ -2385,7 +2351,6 @@ export class NanobotTui {
this.contextPanel.hide()
this.clearComposer()
this.sessionLoading = true
this.renderTitleColor()
const loadId = ++this.sessionLoadId
this.status.content = "Loading sessions…"
try {
@@ -2412,7 +2377,6 @@ export class NanobotTui {
this.defaultModelPreset,
)
this.startSessionRefresh()
this.renderTitleColor()
this.sessionMenu.update(this.composer.plainText, limit)
this.syncComposerPlaceholder()
this.updateMeta()
@@ -2420,7 +2384,6 @@ export class NanobotTui {
} catch (error) {
if (loadId !== this.sessionLoadId) return
this.sessionLoading = false
this.renderTitleColor()
this.status.content = error instanceof Error ? error.message : String(error)
}
}
@@ -2579,7 +2542,6 @@ export class NanobotTui {
this.sessionLoadId += 1
this.sessionLoading = false
this.hideSessionMenu()
this.renderTitleColor()
this.clearComposer()
this.syncComposerPlaceholder()
this.composer.focus()
+1 -4
View File
@@ -309,10 +309,7 @@ export class RuntimeControls {
}
private render(): void {
const runtime = this.modelPreset !== "default"
? [this.modelPreset, this.model].filter(Boolean).join(" ")
: this.model
this.modelText.content = ` ${runtime}`
this.modelText.content = `${this.modelPreset}`
const access = this.scope.access_mode === "full" ? "full access" : "workspace access"
this.accessText.content = ` ${access}`
this.renderColors()