Skip to content

fix(terminal): don't double a desktop dead key, and bracket Ctrl+V after replay - #726

Merged
Ark0N merged 2 commits into
Ark0N:masterfrom
Hugo-DDT:fix/desktop-deadkey-bracketed-paste-716
Oct 28, 2026
Merged

Ark0N merged 2 commits into
Ark0N:masterfrom
Hugo-DDT:fix/desktop-deadkey-bracketed-paste-716

Conversation

@Hugo-DDT

Copy link
Copy Markdown
Contributor

Fixes #716.

KeyCode 229 recovery now attaches only when matchMedia('(pointer: coarse)') matches, so a desktop dead-key backtick is delivered once. Coarse pointers keep the soft-keyboard recovery.

Ctrl+V plain text is a hand-built bracketed paste on the session input path (nested 200~/201~ stripped, newlines normalized to CR). Image and file upload is unchanged.

Tests: test/terminal-keycode229-recovery.test.ts, test/terminal-tile-input.test.ts, test/image-paste-trap.test.ts. The browser wiring suite emulates a coarse pointer and asserts a fine pointer installs nothing.

KeyCode 229 recovery forwarded a composed character that xterm had
already delivered, so a desktop dead-key backtick arrived twice.
create() now attaches only when the primary pointer is coarse.

Ctrl+V text paste went through terminal.paste(), which drops the
CSI 200~/201~ markers after a replay clears xterm's DECSET 2004
mirror. Plain text is now a hand-built bracketed frame on the
session input path.

Fixes Ark0N#716

Co-authored-by: DDT <1786035110@qq.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@Ark0N

Ark0N commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Thanks a lot for this, @Hugo-DDT. It fixes both parts of #716: the soft-keyboard recovery now stays off on a fine pointer, so a desktop dead key arrives once, and plain-text Ctrl+V is framed as a bracketed paste by Codeman itself, so a replay can no longer turn a copied newline into a submit. The tests are good: every new case fails against master's code and passes here, and the browser wiring suite passes too. I also checked the premise on tmux 3.4: tmux strips CSI 200~/201~ for a pane that never enabled bracketed paste and forwards them for one that did, so always sending the frame is safe.

A few small things:

  1. Ctrl+V no longer scrolls to the bottom or clears the selection (src/web/public/image-input.js:254). terminal.paste() ended in triggerDataEvent(text, true), and that wasUserInput flag is what makes xterm snap a scrolled-up viewport back to the prompt and drop the highlight. The new path never touches the terminal, so "scroll up, select, Ctrl+C, Ctrl+V" now leaves the view in scrollback. The simplest fix is to hand the frame to the target terminal with xterm's public terminal.input(framed):

    } else if (text && terminal) {
      const body = text.replace(/\x1b\[20[01]~/g, '').replace(/\r?\n/g, '\r');
      terminal.input(`\x1b[200~${body}\x1b[201~`);
    }

    That fires onData, so the main pane's existing bracketed-paste branch in terminal-ui.js (the Pasting content doesn't append to existing prompt #219 flush and 80 ms delay) and the tile's _onTerminalData handle delivery, and the scroll and selection behaviour come back. It also lets the local-echo block in _deliverBracketedPaste (image-input.js:286) go, which today is a second copy of that onData branch and already differs from it slightly (with no overlay text, onData sends any keystrokes still queued in _pendingInput together with the frame; the copy sends the frame first). The frame is not caught by the query-reply or mouse-report filters, so neither handler drops it. If you prefer to keep the current path, calling terminal.scrollToBottom() and terminal.clearSelection() on the target terminal covers the first part.

  2. Two comments still describe the old behaviour: src/web/public/terminal-tile.js:345-349 ("pasted text goes into this xterm with its bracketed-paste markers intact") and src/web/public/image-input.js:194-196 ("text pastes into THAT xterm"). The text now goes to that pane's session as a frame Codeman builds, and the terminal is only refocused.

  3. Optional: the strip, newline and wrap steps now exist in both image-input.js and keyboard-accessory.js. A shared helper next to BRACKETED_PASTE_START on window.CodemanTerminalInput would keep the nested-marker strip in one place. Fine to leave for later.

None of this changes the fix itself. If you push 1 and 2 this is ready to merge; otherwise they are small enough to apply on top at merge time.

Hand the bracketed frame to the target terminal with input() so onData
scrolls to the bottom, clears the selection, and delivers the bytes.
The paste trap no longer writes the frame itself.

Fixes Ark0N#716

Co-authored-by: DDT <1786035110@qq.com>
@Ark0N

Ark0N commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Thanks @Hugo-DDT for the quick follow-up. The second commit took the terminal.input() route and dropped the duplicate local-echo block, so plain-text Ctrl+V now goes out as a bracketed paste through each pane's own onData, with scroll-to-bottom and selection clearing back, and the soft-keyboard recovery stays off on a fine pointer so a desktop dead key arrives once.

I ran typecheck, lint, the format and frontend checks, the full npm test gate and test/terminal-keycode229-recovery.browser.test.ts: all green, and the new tests fail against master's code as they should. I also checked it in Chromium: the frame reaches the wire intact, a scrolled-up view snaps to the bottom, and unsent local-echo text goes out ahead of the paste.

At merge time I will update two comments in src/web/public/terminal-ui.js that still describe the bracketed-paste branch as fed by terminal.paste() while DECSET 2004 is on (lines 304 and 1919), since Ctrl+V now feeds it through terminal.input() every time. The shared strip/wrap helper stays optional, as before.

Merging.

@Ark0N
Ark0N merged commit 5add56e into Ark0N:master Oct 28, 2026
Ark0N pushed a commit that referenced this pull request Oct 28, 2026
)

Ctrl+V's hand-built bracketed paste (#726) and the phone composer's
dropped CSI 200~/201~ markers from the text in ONE pass, so a clipboard
carrying ESC[20 ESC[201~ 1~ came out of it with an ESC[201~ that ended
the frame early: everything after it was typed as keys and each CR ran.
Both now strip until nothing matches. The composer's copy predates this
release; it is the same one-liner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 28, 2026
…release review, #726)

#726 attaches the keyCode 229 recovery only when (pointer: coarse)
matches, which keeps a desktop dead key from arriving twice. Chromium
reports the PRIMARY pointer as fine once a mouse, a trackpad cover or a
stylus is attached, so on such an Android device the module was off
while the text still came from the soft keyboard, and the #441 dropped
last character and the #541 autocorrect duplication came back.

The gate is now a coarse pointer OR an Android user agent. Android ran
the recovery unconditionally before #726, so this only keeps that; a
desktop (fine pointer, not Android) and a touchscreen laptop stay off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Ark0N

Ark0N commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Merged in 1.47.0 (https://github.com/Ark0N/Codeman/releases/tag/codeman%401.47.0). Thank you for taking both review points into the second commit, and for removing the duplicate delivery path along the way. The release review added two fixes on top: the nested-marker strip now repeats until nothing matches (9f7cba0), because one pass over ESC[20 + ESC[201~ + 1~ left an ESC[201~ that ended the frame early (the phone composer had the same one-liner, fixed with it), and the keyCode 229 recovery is now on for a coarse pointer OR an Android user agent (8074f67), because Chromium reports the primary pointer as fine on an Android device with a mouse, a trackpad cover or a stylus, which would have brought #441 and #541 back there. A desktop and a touchscreen laptop stay off, so #716 holds. One gap is left for a follow-up if you want it: right-click Paste, Shift+Insert and Linux middle-click paste still go through xterm's own textarea handler, so after a replay they still lose the markers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop: dead-key backtick is sent twice, and Ctrl+V paste loses bracketed paste after replay

3 participants