fix(tui): clear stale composer placeholder

This commit is contained in:
Xubin Ren
2026-08-17 20:56:10 +08:00
parent a583b1ffda
commit 411f9f0e90
2 changed files with 39 additions and 2 deletions
+24
View File
@@ -137,6 +137,30 @@ describe("NanobotTui layout", () => {
expect(sent).toEqual(["你好"])
})
test("clears the placeholder on the first typed character", async () => {
setup = await createRenderer({ width: 72, height: 20, screenMode: "alternate-screen" })
const app = mount(setup)
const composer = (app as unknown as { composer: TextareaRenderable }).composer
await setup.renderOnce()
expect(setup.captureCharFrame()).toContain("Ask nanobot anything")
setup.mockInput.typeText("bu")
await setup.flush()
const frame = setup.captureCharFrame()
expect(composer.plainText).toBe("bu")
expect(composer.placeholder).toBeNull()
expect(frame).toContain("bu")
expect(frame).not.toContain("Ask nanobot anything")
expect(frame).not.toContain("buAsk nanobot anything")
setup.mockInput.pressBackspace()
setup.mockInput.pressBackspace()
await setup.flush()
expect(composer.placeholder).toBe("Ask nanobot anything")
expect(setup.captureCharFrame()).toContain("Ask nanobot anything")
})
test("recalls submitted prompts without stealing multiline cursor movement", async () => {
const sent: string[] = []
setup = await createRenderer({ width: 72, height: 20, screenMode: "alternate-screen" })
+15 -2
View File
@@ -82,6 +82,8 @@ const LIGHT: Palette = {
cool: "#0F766E",
}
const COMPOSER_PLACEHOLDER = "Ask nanobot anything"
function syntaxStyle(palette: Palette): SyntaxStyle {
const color = (value: string) => {
const parsed = RGBA.fromHex(value)
@@ -231,7 +233,7 @@ export class NanobotTui {
minHeight: 1,
maxHeight: 8,
wrapMode: "word",
placeholder: "Ask nanobot anything",
placeholder: COMPOSER_PLACEHOLDER,
placeholderColor: this.palette.faint,
textColor: this.palette.text,
focusedTextColor: this.palette.text,
@@ -243,7 +245,10 @@ export class NanobotTui {
{ name: "return", action: "submit" },
{ name: "return", meta: true, action: "newline" },
],
onContentChange: () => this.resizeComposer(),
onContentChange: () => {
this.syncComposerPlaceholder()
this.resizeComposer()
},
// IMEs may commit their final composed glyph after Enter. Matching the
// OpenCode/OpenTUI integration, defer twice before reading plainText.
onSubmit: () => this.deferSubmit(),
@@ -673,6 +678,14 @@ export class NanobotTui {
this.composerFrame.maxHeight = maxHeight + 2
}
private syncComposerPlaceholder(): void {
// 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 placeholder = this.composer.plainText ? null : COMPOSER_PLACEHOLDER
if (this.composer.placeholder !== placeholder) this.composer.placeholder = placeholder
}
private async copySelection(text: string): Promise<void> {
if (!text) return
try {