fix(mobile): retain oversized composer drafts

This commit is contained in:
DodgyBadger
2026-09-20 07:21:20 +00:00
parent a220c28a14
commit 884713cca5
2 changed files with 24 additions and 1 deletions
+9 -1
View File
@@ -654,6 +654,9 @@ const KeyboardAccessoryBar = {
_composerDrafts: new Map(), _composerDrafts: new Map(),
_composerUploads: new Map(), _composerUploads: new Map(),
_composerOverlay: null, _composerOverlay: null,
// Leave room for both six-character bracketed-paste markers under the
// WebSocket input frame's 64 KiB character limit.
_composerMaxLength: 65524,
/** HTML for simple mode: arrows, commands, Compose, Esc, dismiss */ /** HTML for simple mode: arrows, commands, Compose, Esc, dismiss */
_simpleButtons: ` _simpleButtons: `
@@ -1193,7 +1196,12 @@ const KeyboardAccessoryBar = {
// Match xterm's prepareTextForTerminal(): CR keeps embedded newlines inside // Match xterm's prepareTextForTerminal(): CR keeps embedded newlines inside
// the single-line input transport and is what terminal.paste() emitted. // the single-line input transport and is what terminal.paste() emitted.
const pasteText = text.replace(/\r?\n/g, '\r'); const pasteText = text.replace(/\r?\n/g, '\r');
app._sendInputAsync(sessionId, `\x1b[200~${pasteText}\x1b[201~`); const payload = `\x1b[200~${pasteText}\x1b[201~`;
if (payload.length > 65536) {
app.showToast?.(`Prompt is too long to send (maximum ${this._composerMaxLength.toLocaleString()} characters)`, 'error');
return false;
}
app._sendInputAsync(sessionId, payload);
setTimeout(() => app._sendInputAsync(sessionId, '\r', { useMux: true }), 120); setTimeout(() => app._sendInputAsync(sessionId, '\r', { useMux: true }), 120);
return true; return true;
}, },
+15
View File
@@ -269,6 +269,21 @@ describe('mobile prompt composer', () => {
expect(app._echoPassthroughSessions.has('session-2')).toBe(true); expect(app._echoPassthroughSessions.has('session-2')).toBe(true);
}); });
it('keeps an oversized prompt as a draft instead of queueing a rejected frame', () => {
const { app, bar, document } = loadComposer();
bar.composePrompt();
const input = textarea(document);
input.value = 'x'.repeat(65525);
input.dispatchEvent(new document.defaultView!.Event('input', { bubbles: true }));
(document.querySelector('.paste-send') as HTMLButtonElement).click();
expect(app._sendInputAsync).not.toHaveBeenCalled();
expect(app.showToast).toHaveBeenCalledWith(expect.stringContaining('too long'), 'error');
expect(document.querySelector('.prompt-composer-overlay')).not.toBeNull();
expect(bar._composerDrafts.get('session-1')).toHaveLength(65525);
});
it('preserves the draft and focuses xterm when Use terminal keyboard is chosen', () => { it('preserves the draft and focuses xterm when Use terminal keyboard is chosen', () => {
const { app, bar, document, localEcho, runTimers } = loadComposer(); const { app, bar, document, localEcho, runTimers } = loadComposer();
const composeButton = mountComposeButton(bar, document); const composeButton = mountComposeButton(bar, document);