diff --git a/src/web/public/keyboard-accessory.js b/src/web/public/keyboard-accessory.js index 80c9f25d..8a629dfe 100644 --- a/src/web/public/keyboard-accessory.js +++ b/src/web/public/keyboard-accessory.js @@ -1276,6 +1276,7 @@ const KeyboardAccessoryBar = { const send = () => { const text = textarea.value; if (!text || !this._sendComposedPrompt(sessionId, text)) return; + app._echoPassthroughSessions?.delete(sessionId); this._composerDrafts.delete(sessionId); this._syncComposerDraftIndicator(); close({ preserveDraft: false }); diff --git a/test/mobile-prompt-composer.test.ts b/test/mobile-prompt-composer.test.ts index 8a827c36..5b16d120 100644 --- a/test/mobile-prompt-composer.test.ts +++ b/test/mobile-prompt-composer.test.ts @@ -38,6 +38,7 @@ function loadComposer(sessionId = 'session-1') { _localEchoOverlay: localEcho, _flushedOffsets: new Map(), _flushedTexts: new Map(), + _echoPassthroughSessions: new Set(), _predictiveEcho: { clearPredictions: vi.fn() }, _sendInputAsync: vi.fn(), _uploadAndInsertImages: vi.fn(async () => ['/tmp/image-one.png']), @@ -248,6 +249,19 @@ describe('mobile prompt composer', () => { expect(app._sendInputAsync).toHaveBeenLastCalledWith('session-1', '\r', { useMux: true }); }); + it('releases echo passthrough after a composed prompt is queued', () => { + const { app, bar, document } = loadComposer(); + app._echoPassthroughSessions.add('session-1'); + app._echoPassthroughSessions.add('session-2'); + bar.composePrompt(); + textarea(document).value = 'send from composer'; + + (document.querySelector('.paste-send') as HTMLButtonElement).click(); + + expect(app._echoPassthroughSessions.has('session-1')).toBe(false); + expect(app._echoPassthroughSessions.has('session-2')).toBe(true); + }); + it('preserves the draft and focuses xterm when Use terminal keyboard is chosen', () => { const { app, bar, document, localEcho, runTimers } = loadComposer(); const composeButton = mountComposeButton(bar, document);