refactor(tui): clarify active composer actions (#5538)

* fix(tui): queue busy submissions on Enter

* fix(tui): remove Tab queue shortcut

* refactor(tui): clarify active composer actions

* refactor(tui): simplify active composer hint
This commit is contained in:
chengyongru
2026-08-26 00:46:31 +08:00
committed by GitHub
parent c83ed041ea
commit 3ee3791626
6 changed files with 37 additions and 20 deletions
+16 -5
View File
@@ -322,6 +322,7 @@ describe("NanobotTui layout", () => {
composer: TextareaRenderable
mentionCandidates: Array<Record<string, unknown>>
queuePreview: { root: { visible: boolean } }
status: { plainText: string }
}
await waitUntil(() => ui.ready)
ui.mentionCandidates = [{
@@ -334,10 +335,17 @@ describe("NanobotTui layout", () => {
ui.composer.setText("first")
ui.composer.submit()
await waitUntil(() => sent.length === 1)
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
ui.composer.setText("one more detail")
await setup.flush()
expect(ui.composer.placeholder).toBeNull()
ui.composer.setText("ask @github next")
ui.composer.submit()
await waitUntil(() => sent.length === 2)
expect(ui.status.plainText).not.toContain("Steering")
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
expect(sentOptions[1]).toEqual({
cliApps: [{ name: "github" }],
mcpPresets: [],
@@ -1643,9 +1651,12 @@ describe("NanobotTui layout", () => {
expect(setup.renderer.width).toBe(width)
expect(setup.renderer.height).toBe(height)
expect(frame).not.toContain("undefined")
expect(occurrences(frame, "Steer this turn…")).toBeLessThanOrEqual(1)
if (width >= 30 && height >= 9) {
expect(occurrences(frame, "Steer this turn…")).toBe(1)
expect(frame).not.toContain("Steer this turn…")
expect(frame).not.toContain("Ask a follow-up…")
if (width >= 40 && height >= 9) {
expect(occurrences(frame, "Enter send now · Tab send next")).toBe(1)
} 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)
}
@@ -2176,7 +2187,7 @@ describe("NanobotTui layout", () => {
}
const status = ui.status
expect(status.plainText).toMatch(/^Thinking\s+0s/u)
expect(ui.composer.placeholder).toBe("Steer this turn…")
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
expect(ui.composerFrame.height).toBe(3)
const shimmerColors = new Set(
status.content.chunks
@@ -2703,7 +2714,7 @@ describe("NanobotTui in a Herdr pane", () => {
const activeFrame = setup.captureCharFrame()
expect(occurrences(activeFrame, " Ship the Herdr integration")).toBe(1)
expect(occurrences(activeFrame, "app.ts")).toBe(1)
expect(ui.composer.placeholder).toBe("Steer this turn…")
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
expect(ui.composerFrame.height).toBe(3)
app.accept({
event: "turn_end",
+8 -3
View File
@@ -178,7 +178,8 @@ const LIGHT: Palette = {
}
const COMPOSER_PLACEHOLDER = "Ask nanobot anything"
const ACTIVE_COMPOSER_PLACEHOLDER = "Steer this turn…"
const ACTIVE_COMPOSER_PLACEHOLDER = "Enter send now · Tab send next"
const COMPACT_ACTIVE_COMPOSER_PLACEHOLDER = "Enter now · Tab next"
const SHIMMER_PAUSE = 16
const SHIMMER_BAND = 4
const SHIMMER_INTERVAL_MS = 80
@@ -959,7 +960,7 @@ export class NanobotTui {
this.hostBlocked = false
this.setCurrentTask(prompt.content)
if (steering) {
this.status.content = `Steering current turn${this.promptQueue.length ? ` · ${this.promptQueue.length} queued` : ""}`
this.renderActiveStatus()
this.updateMeta()
return true
}
@@ -1796,6 +1797,7 @@ export class NanobotTui {
private handleResize = (): void => {
this.resizeComposer()
this.syncComposerPlaceholder()
this.contextPanel.resize(this.renderer.height)
this.diffViewer.resize(this.renderer.width)
if (!this.host.hosted) this.title.visible = this.renderer.height >= 14
@@ -1974,13 +1976,16 @@ export class NanobotTui {
// OpenTUI normally suppresses placeholder glyphs while the editor is not
// empty. Explicitly removing them also invalidates their old cells, which
// prevents stale placeholder text in differential/embedded terminals.
const activePlaceholder = this.renderer.width >= 40
? ACTIVE_COMPOSER_PLACEHOLDER
: COMPACT_ACTIVE_COMPOSER_PLACEHOLDER
const placeholder = this.composer.plainText
? null
: this.sessionMenu.visible
? "Search sessions"
: this.branchMenu.visible
? "Search branch points"
: this.activeTurn ? ACTIVE_COMPOSER_PLACEHOLDER : COMPOSER_PLACEHOLDER
: this.activeTurn ? activePlaceholder : COMPOSER_PLACEHOLDER
if (this.composer.placeholder !== placeholder) this.composer.placeholder = placeholder
}