From a220c28a144424499586dcad4047c5d4a37ced76 Mon Sep 17 00:00:00 2001 From: DodgyBadger <4453217+DodgyBadger@users.noreply.github.com> Date: Sun, 20 Sep 2026 07:18:49 +0000 Subject: [PATCH] fix(input): count code points when clearing prompts --- src/web/public/keyboard-accessory.js | 6 +++--- src/web/public/terminal-ui.js | 5 +++-- test/mobile-prompt-composer.test.ts | 11 +++++++++++ test/path-picker-ui.test.ts | 6 +++--- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/web/public/keyboard-accessory.js b/src/web/public/keyboard-accessory.js index 3c7263b2..9b95e79d 100644 --- a/src/web/public/keyboard-accessory.js +++ b/src/web/public/keyboard-accessory.js @@ -1156,13 +1156,13 @@ const KeyboardAccessoryBar = { const pending = app._localEchoOverlay.pendingText || ''; const overlayFlushed = app._localEchoOverlay.getFlushed?.() || {}; const flushedText = overlayFlushed.text || app._flushedTexts?.get(sessionId) || ''; - const flushedCount = overlayFlushed.count || app._flushedOffsets?.get(sessionId) || 0; + const flushedLength = Array.from(flushedText).length; app._localEchoOverlay.clear(); app._localEchoOverlay.suppressBufferDetection?.(); app._flushedOffsets?.delete(sessionId); app._flushedTexts?.delete(sessionId); - if (flushedCount > 0) { - app._sendInputAsync(sessionId, '\x7f'.repeat(flushedCount), { useMux: true }); + if (flushedLength > 0) { + app._sendInputAsync(sessionId, '\x7f'.repeat(flushedLength), { useMux: true }); } return flushedText + pending; }, diff --git a/src/web/public/terminal-ui.js b/src/web/public/terminal-ui.js index e32b5832..93999191 100644 --- a/src/web/public/terminal-ui.js +++ b/src/web/public/terminal-ui.js @@ -4069,8 +4069,9 @@ Object.assign(CodemanApp.prototype, { this._localEchoOverlay.suppressBufferDetection(); this._flushedOffsets?.delete(this.activeSessionId); this._flushedTexts?.delete(this.activeSessionId); - if (flushed.count > 0) { - this.sendInput('\x7f'.repeat(flushed.count)).catch(() => {}); + const flushedLength = Array.from(flushed.text || '').length; + if (flushedLength > 0) { + this.sendInput('\x7f'.repeat(flushedLength)).catch(() => {}); } } else { // In non-local-echo mode the TUI already owns the editable buffer. Ctrl+U diff --git a/test/mobile-prompt-composer.test.ts b/test/mobile-prompt-composer.test.ts index 45cf8cd4..0dc98aeb 100644 --- a/test/mobile-prompt-composer.test.ts +++ b/test/mobile-prompt-composer.test.ts @@ -152,6 +152,17 @@ describe('mobile prompt composer', () => { expect(app._flushedTexts.has('session-1')).toBe(false); }); + it('uses Unicode code points when erasing flushed text', () => { + const { app, bar, document } = loadComposer(); + app._flushedOffsets.set('session-1', 3); + app._flushedTexts.set('session-1', 'a😀'); + + bar.composePrompt(); + + expect(textarea(document).value).toBe('a😀'); + expect(app._sendInputAsync).toHaveBeenCalledWith('session-1', '\x7f'.repeat(2), { useMux: true }); + }); + it('closes on tab switch and keeps drafts isolated by session', () => { const { app, bar, document } = loadComposer(); const composeButton = mountComposeButton(bar, document); diff --git a/test/path-picker-ui.test.ts b/test/path-picker-ui.test.ts index d000cb15..8dcb3c4a 100644 --- a/test/path-picker-ui.test.ts +++ b/test/path-picker-ui.test.ts @@ -200,12 +200,12 @@ describe('mobile filesystem picker actions', () => { _pendingInput: 'pending text', _localEchoEnabled: true, _localEchoOverlay: { - getFlushed: () => ({ count: 4, text: 'sent' }), + getFlushed: () => ({ count: 4, text: 'a😀b' }), clear, suppressBufferDetection, }, _flushedOffsets: new Map([['session-1', 4]]), - _flushedTexts: new Map([['session-1', 'sent']]), + _flushedTexts: new Map([['session-1', 'a😀b']]), sendInput, showToast, terminal: { focus }, @@ -216,7 +216,7 @@ describe('mobile filesystem picker actions', () => { expect(app._pendingInput).toBe(''); expect(clear).toHaveBeenCalledOnce(); expect(suppressBufferDetection).toHaveBeenCalledOnce(); - expect(sendInput).toHaveBeenCalledWith('\x7f'.repeat(4)); + expect(sendInput).toHaveBeenCalledWith('\x7f'.repeat(3)); expect(sendInput).not.toHaveBeenCalledWith('/clear'); expect(app._flushedOffsets.size).toBe(0); expect(app._flushedTexts.size).toBe(0);