client: drop drag-select clipboard trim — no tmux OSC 52 off-by-one exists#68
Conversation
|
Thanks for the careful diagnosis and the regression tests — the bug is Small fixes1. Broken Russian phrase in README.ru.mdThe new persistent-sessions bullet contains Suggested replacement (matches the impersonal compact tone of the
Lines 41 to 46 in e2c2705 2. Stale "trim window" reference in createPane comment block
Suggested rewrite of the trailing clause:
Lines 228 to 240 in e2c2705 Concept-level: tmux 3.4+ floor is the tightest in the projectThe README's other compat floors are intentionally inclusive — Python As of May 2026, common LTS targets ship tmux below 3.4:
On those targets the post-PR symptom is paste-too-long with no in-product (Note: dynamic compare via Proposed mechanism (~25 LoC)In echo "WEBSH_TMUX_VERSION=$(tmux -V 2>&1)"
exec tmux new-session -A -D -s websh-<slot> \; set -g mouse on \; ...That line is the first byte on the SSE pipe. On the client SSE handler: let m = /WEBSH_TMUX_VERSION=tmux (\d+)\.(\d+)/.exec(chunk);
if (m) {
let major = +m[1], minor = +m[2];
p._tmuxNeedsTrim = major < 3 || (major === 3 && minor < 4);
// Strip the marker line before forwarding to term.write(); tmux's
// attach-time screen-clear usually overwrites it, but not reliably
// (small terminals, alt-screen apps that take over before the clear),
// so make it invisible explicitly.
chunk = chunk.replace(/WEBSH_TMUX_VERSION=tmux [^\r\n]*[\r\n]+/, '');
}Reapply the conditional in term.onSelectionChange(() => {
let sel = term.getSelection();
if (!sel) return;
if (p._tmuxNeedsTrim) sel = sel.slice(0, -1);
if (sel) copyText(sel);
});Optionally, surface a one-time info bar when Non-persistent connects, missing tmux, or unparseable version output all This keeps every still-supported LTS working with clean drag-select copy,
If you go this route, the new regression tests need updatingThe two
That also fixes the post-drag branch of the OSC 52 test, which currently Alternative: ship as-is, version-detect as follow-upDefensible: ship this PR, open a follow-up issue, accept the symmetric Let me know which direction makes sense for this PR. |
|
Updated the comment above @gorevds |
55507ca to
e72de34
Compare
|
Conditional trim landed. Both small fixes folded in, history squashed Signaling channel: OSC 1338 over regex-stripPicked the OSC route instead of the visible echo + regex-strip: printf '\033]1338;websh-tmux-version=%s\033\\' "$(tmux -V 2>&1)"
exec tmux new-session ...
Probe lands in both wrapper branches: TTL=0 → Detection thresholdp._tmuxNeedsTrim = major < 3 || (major === 3 && minor < 4);
UX: silentNo toast on detect — per the README's "X+ recommended; older auto-trims" Trim contract: single-char short-circuitBoth paths slice with if (text) copyText(text); // OSC 52
// and
if (sel) copyText(sel); // onSelectionChangeA length-1 selection (bare focus-click on legacy tmux) trims to empty Small fixes
TestsFrontend +8 (519 / 0 total, was 511):
Server +3 (423 / 0 total, was 420):
Live verificationDeployed to https://websh.gorev.space (Ubuntu 24.04 → tmux 3.4-1 |
e72de34 to
ea74b33
Compare
|
Spent some time live-verifying this end-to-end on stock Ubuntu LTS The OSC 1338-over-regex-strip swap is a clear improvement on what I Smoking gunStock Ubuntu 22.04 Docker (
Raw OSC 52 payload is bit-identical between tmux 3.2a and 3.4 for the Verifying outside websh / xterm.js tooTo rule out anything client-side, ran the same selection via two more Pure-tmux mouse via PTY injection (no websh, no xterm.js)
tmux selection model on both versions: Pure-tmux keyboard copy-mode
tmux 3.4 CHANGES doesn't claim an OSC 52 selection fix between 3.2a and 3.4Only OSC 52 mention in
That's about the kind parameter ( My read on what likely happened in 4703bc1Hypothesis — I can't test the counterfactual cleanly, but it's the The visual observation that motivated 4703bc1 was real and worth The jump from "visible cursor cell artifact" to "therefore OSC 52 What I'd proposeRevert to PR-attempt-1 shape:
End result: clean contract, both How to verify on your end (single tab, no infra)Open DevTools Console on a websh tab connected to any tmux target in window.atob = (orig => s => {
const r = orig(s);
if (r && /^\d/.test(r)) console.log('OSC52 raw:', JSON.stringify(r), 'len:', r.length);
return r;
})(window.atob);Then Caveats — what I haven't covered
If you've directly observed paste-too-long on a |
…xists 4703bc1 (PR dolonet#54) added a one-character `trimDragTail` to the OSC 52 and onSelectionChange clipboard paths, on the premise that tmux includes an extra trailing cursor cell in OSC 52 payloads. That premise was never verified against the wire — it was inferred from a *visual* artifact (xterm.js painting the cursor bar over the trailing edge of tmux's yellow selection) and written into the commit message as fact. Wire-level measurement shows the premise is false: - tmux 3.4, SGR mouse drag injected over a pty.fork'd `tmux attach`, OSC 52 captured off the stream: `press@1 release@9` yields exactly `012345678` (9 cells) — the selection is `[press, release]` with no extra cursor cell. - tmux 3.2a (Ubuntu 22.04) measured three independent ways — browser, raw-PTY SGR injection, keyboard copy-mode — is byte-identical to 3.4. - The tmux CHANGES log has no entry between 3.2a and 3.4 that alters OSC 52 selection content; the sole 3.4 OSC 52 change is passing the clipboard *kind* argument through, unrelated to content. tmux's selection is `[start, end)` exclusive on every version. The trim therefore always dropped a real visible character — silently on every target. That is the paste-too-short bug this branch set out to fix; the fix is simply to remove the trim, not to gate it on a tmux version that has no behavioral difference. This supersedes the earlier conditional-trim approach on this branch (server OSC 1338 version probe + client `_tmuxNeedsTrim` gate): with no off-by-one to compensate for, version detection has nothing to do, so the probe and handler are dropped entirely (server.py and test_server.py return to their upstream state). Changes: - Remove `trimDragTail`, `DRAG_TRIM_WINDOW_MS`, `_recentDragSelectAt`; both clipboard paths now pass tmux's payload through unmodified. - Keep the CURSOR_HIDE machinery intact — it fixes the real, independent visual artifact (cursor bar/blink over the selection's trailing cell) via `cursorInactiveStyle:'none'` + drag-blur + deferred focus restore. Rename the `SELECTION_TRIM:` marker to `CURSOR_HIDE:` since the trim it referred to is gone. - Frontend: drop the `trimDragTail` unit test; add two passthrough regression tests pinning that OSC 52 and onSelectionChange payloads reach the clipboard byte-identical, so the trim can't be reintroduced silently. - README / README.ru / docs: keep a plain "tmux 3.4+ recommended" note; drop the (incorrect) OSC 52 off-by-one rationale.
ea74b33 to
de66729
Compare
|
Reworked — force-pushed Independent verificationRe-ran the measurement before reverting, two ways I could do without tmux 3.4, OSC 52 off the wire. Exactly tmux CHANGES, 3.2a → 3.4. Pulled What I could not do: measure 3.2a directly — no Docker in my What changed in
|
The 3.4+ note was residue from the OSC 52 off-by-one hypothesis this branch debunked. With the clipboard payload byte-identical across tmux versions there is nothing 3.4+ buys; the persistent-sessions note now reads "any reasonably recent version", consistent with the project's other inclusive compat floors.
|
@gorevds — folded in the last touches so this doesn't need another round:
|
4703bc1 (PR #54) added a one-character
trimDragTailto the OSC 52 andonSelectionChange clipboard paths, on the premise that tmux includes an
extra trailing cursor cell in OSC 52 payloads. That premise was never
verified against the wire — it was inferred from a visual artifact
(xterm.js painting the cursor bar over the trailing edge of tmux's
yellow selection) and written into the commit message as fact.
Wire-level measurement shows the premise is false:
tmux attach,OSC 52 captured off the stream:
press@1 release@9yields exactly012345678(9 cells) — the selection is[press, release]with noextra cursor cell.
raw-PTY SGR injection, keyboard copy-mode — is byte-identical to 3.4.
OSC 52 selection content; the sole 3.4 OSC 52 change is passing the
clipboard kind argument through, unrelated to content.
tmux's selection is
[start, end)exclusive on every version. Thetrim therefore always dropped a real visible character — silently on
every target. That is the paste-too-short bug this branch set out to
fix; the fix is simply to remove the trim, not to gate it on a tmux
version that has no behavioral difference.
This supersedes the earlier conditional-trim approach on this branch
(server OSC 1338 version probe + client
_tmuxNeedsTrimgate): with nooff-by-one to compensate for, version detection has nothing to do, so
the probe and handler are dropped entirely (server.py and test_server.py
return to their upstream state).
Changes:
trimDragTail,DRAG_TRIM_WINDOW_MS,_recentDragSelectAt;both clipboard paths now pass tmux's payload through unmodified.
independent visual artifact (cursor bar/blink over the selection's
trailing cell) via
cursorInactiveStyle:'none'+ drag-blur +deferred focus restore. Rename the
SELECTION_TRIM:marker toCURSOR_HIDE:since the trim it referred to is gone.trimDragTailunit test; add two passthroughregression tests pinning that OSC 52 and onSelectionChange payloads
reach the clipboard byte-identical, so the trim can't be
reintroduced silently.
rationale and the
tmux 3.4+version floor — the clipboard payloadis byte-identical across tmux versions, so the persistent-sessions
note reads "any reasonably recent version", in line with the
project's other inclusive compat floors.