style(tui): simplify the composer surface

This commit is contained in:
Xubin Ren
2026-08-17 20:56:10 +08:00
parent f17d9eeff7
commit 9d7453a53e
2 changed files with 60 additions and 11 deletions
+39 -2
View File
@@ -766,7 +766,13 @@ describe("NanobotTui layout", () => {
const internals = app as unknown as { const internals = app as unknown as {
palette: { referenceBackground: string; text: string; border: string } palette: { referenceBackground: string; text: string; border: string }
shell: { backgroundColor: { intent: string } } shell: { backgroundColor: { intent: string } }
composer: { backgroundColor: { intent: string }; textColor: { toInts(): number[] } } composerFrame: {
backgroundColor: { intent: string; toInts(): number[] }
}
composer: {
backgroundColor: { intent: string; toInts(): number[] }
textColor: { toInts(): number[] }
}
transcript: { transcript: {
markdown: Set<{ syntaxStyle: object }> markdown: Set<{ syntaxStyle: object }>
frames: Set<{ borderColor: { toInts(): number[] } }> frames: Set<{ borderColor: { toInts(): number[] } }>
@@ -791,13 +797,44 @@ describe("NanobotTui layout", () => {
border: "#D4D4D8", border: "#D4D4D8",
}) })
expect(internals.shell.backgroundColor.intent).toBe("default") expect(internals.shell.backgroundColor.intent).toBe("default")
expect(internals.composer.backgroundColor.intent).toBe("default") expect(internals.composerFrame.backgroundColor.toInts().slice(0, 3)).toEqual([240, 240, 240])
expect(internals.composer.backgroundColor.toInts().slice(0, 3)).toEqual([240, 240, 240])
expect(internals.composer.textColor.toInts().slice(0, 3)).toEqual([24, 24, 27]) expect(internals.composer.textColor.toInts().slice(0, 3)).toEqual([24, 24, 27])
expect(sessionFrame?.borderColor.toInts().slice(0, 3)).toEqual([212, 212, 216]) expect(sessionFrame?.borderColor.toInts().slice(0, 3)).toEqual([212, 212, 216])
expect(userRow?.backgroundColor.toInts().slice(0, 3)).toEqual([240, 240, 240]) expect(userRow?.backgroundColor.toInts().slice(0, 3)).toEqual([240, 240, 240])
expect(markdown?.syntaxStyle).not.toBe(darkSyntax) expect(markdown?.syntaxStyle).not.toBe(darkSyntax)
}) })
test("uses a quiet surface instead of a boxed composer", async () => {
setup = await createRenderer({ width: 72, height: 20, screenMode: "alternate-screen" })
const app = NanobotTui.mount(
setup.renderer,
{ ...options, theme: "light" },
client(),
new MockTreeSitterClient({ autoResolveTimeout: 0 }),
)
const internals = app as unknown as {
ready: boolean
composerFrame: {
border: boolean | string[]
backgroundColor: { toInts(): number[] }
}
composer: { backgroundColor: { toInts(): number[] } }
}
expect(internals.composerFrame.border).toBeFalse()
expect(internals.composerFrame.backgroundColor.toInts().slice(0, 3)).toEqual([240, 240, 240])
expect(internals.composer.backgroundColor.toInts().slice(0, 3)).toEqual([240, 240, 240])
app.accept({ event: "attached", chat_id: "chat" })
await waitUntil(() => internals.ready)
await setup.renderOnce()
const composerLine = setup.captureCharFrame().split("\n")
.find((line) => line.includes("Ask nanobot anything")) || ""
expect(composerLine).not.toContain("│")
})
test("uses asymmetric roles instead of chat bubbles", async () => { test("uses asymmetric roles instead of chat bubbles", async () => {
setup = await createRenderer({ width: 72, height: 24, screenMode: "alternate-screen" }) setup = await createRenderer({ width: 72, height: 24, screenMode: "alternate-screen" })
const app = NanobotTui.mount( const app = NanobotTui.mount(
+21 -9
View File
@@ -411,17 +411,16 @@ export class NanobotTui {
}) })
this.title.add(this.titleText) this.title.add(this.titleText)
this.title.add(this.modelText) this.title.add(this.modelText)
const composerSurface = this.composerSurface()
this.composerFrame = new BoxRenderable(renderer, { this.composerFrame = new BoxRenderable(renderer, {
id: "nanobot-tui-composer-frame", id: "nanobot-tui-composer-frame",
width: "100%", width: "100%",
minHeight: 3, minHeight: 1,
flexShrink: 0, flexShrink: 0,
border: true, border: false,
borderStyle: "rounded",
borderColor: this.palette.border,
paddingLeft: 1, paddingLeft: 1,
paddingRight: 1, paddingRight: 1,
backgroundColor: RGBA.defaultBackground(), backgroundColor: composerSurface,
}) })
this.composer = new TextareaRenderable(renderer, { this.composer = new TextareaRenderable(renderer, {
id: "nanobot-tui-composer", id: "nanobot-tui-composer",
@@ -433,8 +432,8 @@ export class NanobotTui {
placeholderColor: this.palette.faint, placeholderColor: this.palette.faint,
textColor: this.palette.text, textColor: this.palette.text,
focusedTextColor: this.palette.text, focusedTextColor: this.palette.text,
backgroundColor: RGBA.defaultBackground(), backgroundColor: composerSurface,
focusedBackgroundColor: RGBA.defaultBackground(), focusedBackgroundColor: composerSurface,
cursorColor: this.palette.accent, cursorColor: this.palette.accent,
showCursor: true, showCursor: true,
keyBindings: [ keyBindings: [
@@ -1040,7 +1039,7 @@ export class NanobotTui {
this.sessionMenu.setTheme(commandMenuTheme(this.palette)) this.sessionMenu.setTheme(commandMenuTheme(this.palette))
this.contextPanel.setTheme(contextPanelTheme(this.palette)) this.contextPanel.setTheme(contextPanelTheme(this.palette))
this.diffViewer.setTheme(diffViewerTheme(this.palette, this.backgroundKnown)) this.diffViewer.setTheme(diffViewerTheme(this.palette, this.backgroundKnown))
this.composerFrame.borderColor = this.palette.border this.updateComposerAppearance()
this.composer.textColor = this.palette.text this.composer.textColor = this.palette.text
this.composer.focusedTextColor = this.palette.text this.composer.focusedTextColor = this.palette.text
this.composer.cursorColor = this.palette.accent this.composer.cursorColor = this.palette.accent
@@ -1113,7 +1112,20 @@ export class NanobotTui {
private resizeComposer(): void { private resizeComposer(): void {
const maxHeight = Math.max(1, Math.min(12, Math.floor(this.renderer.height / 3))) const maxHeight = Math.max(1, Math.min(12, Math.floor(this.renderer.height / 3)))
this.composer.maxHeight = maxHeight this.composer.maxHeight = maxHeight
this.composerFrame.maxHeight = maxHeight + 2 this.composerFrame.maxHeight = maxHeight
}
private composerSurface(): RGBA {
return this.backgroundKnown
? RGBA.fromHex(this.palette.userBackground)
: RGBA.defaultBackground()
}
private updateComposerAppearance(): void {
const surface = this.composerSurface()
this.composerFrame.backgroundColor = surface
this.composer.backgroundColor = surface
this.composer.focusedBackgroundColor = surface
} }
private syncComposerPlaceholder(): void { private syncComposerPlaceholder(): void {