Skip to content

tmux: touch scrolling for terminals that send a swipe as a click pair (a-Shell on iPhone) - #88

Merged
cverorg merged 10 commits into
mainfrom
feat/tmux-touch-scroll
Sep 13, 2026
Merged

tmux: touch scrolling for terminals that send a swipe as a click pair (a-Shell on iPhone)#88
cverorg merged 10 commits into
mainfrom
feat/tmux-touch-scroll

Conversation

@cverorg

@cverorg cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member

Why

From a-Shell (iPhone) attached over ssh to a clikae session you could not scroll back. Probe (printf '\e[?1000h\e[?1006h'; cat -v, swipe): a-Shell sends a swipe as one SGR press + one release at different rows, never a wheel event; the Claude Code pane has mouse tracking on, so tmux hands the click to the app and nothing scrolls.

What

  • lib/core/touch_scroll.sh — standalone helper: reads the press row from a pane-local option (walking the real scope chain: pane → window → this session → global — session-level set @clikae_touch_scroll off now reaches it, see round 1 below), scrolls copy-mode by @clikae_touch_scroll_lines × |dy| (default 2) when |dy| ≥ 2 (finger up ⇒ scroll-up), a tap in copy-mode ⇒ cancel; @clikae_touch_scroll off disables. @clikae_touch_y is unset the instant it is read, so an Up with no matching Down can never reuse a stale value.
  • lib/core/tmux.shmouse on stays a standalone call; six bindings (root + copy-mode + copy-mode-vi MouseDown1Pane / MouseUp1Pane) install after it, gated on a declared tmux floor (>= 3.1, needed for set-option -p/-pu). Root's MouseUp1Pane forwards the release before translating (send-keys -M; run-shell …), so a mouse-tracking app always receives both press and release — the default press action was already kept; round 1 fixed the release. Desktop wheel and drag-selection unaffected (drags arrive as MouseDrag*Pane). choose-mode / choose-mode-vi are deliberately left unbound.
  • tests/bats/tmux.bats (14 cases): chain includes the bindings, the version-floor skip, and the copy-mode-vi mirror; helper against a stub tmux — up/down/tap/no-op/multiplier/off/stacked-mode-count/stale-value-unset.
  • docs/usage.md section for ssh-from-phone (updated in round 1 to match); CHANGELOG.

Verified on the phone by the operator against a live prototype with identical bindings (swipe scrolls, tap returns).

🤖 Generated with Claude Code


Fix round 1 (2026-09-13)

Round-1 adversarial review (2 P1, 2 P2, 4 P3) found every P1/P2 only reproduces on a real tmux server — the bats suite above is stub-only. Fixed 7 of 8 findings, each proven red→green on a real tmux 3.4 throwaway socket (never the shared/default server) the way the review did; commits in order:

  1. P1-1 6670ec6 — root's MouseUp1Pane bound run-shell only, consuming the release instead of forwarding it (stock tmux's root table has no MouseUp1Pane at all). Every mouse-tracking app — Claude Code included — got the press and never the release. Fixed: send-keys -M before run-shell. Measured with a real client reading raw SGR bytes off a pty: un-fixed delivered only the press (M); fixed delivers both press and release (M then m).
  2. P1-2 b94c688copy-mode-vi (what tmux actually uses under set -g mode-keys vi, a common ~/.tmux.conf line) had no bindings of its own, so a tap there reused a stale @clikae_touch_y and kept scrolling backward instead of cancelling — the only way back on a device with no scroll wheel. Fixed: mirrored the copy-mode Down/Up pair onto copy-mode-vi, and the helper now unsets @clikae_touch_y the instant it reads it, so no key table can ever reuse a stale value structurally.
  3. P2-2 f0e109a#{pane_in_mode} is a stack depth, not a boolean; = 1 was false at depth 2 (reachable via this PR's own root-table fallback in choose-tree), so a swipe re-entered copy-mode on top of itself and a tap silently did nothing. Fixed with a numeric -gt 0 guard.
  4. P2-1 fcf1ee4scripts/doc-names-exist.sh's new @-option branch used an unconditional continue, so a name occurring as @name anywhere exempted it from the function-existence check everywhere, including a different, bare, function-shaped mention of the same name. Fixed: only skip the function check when every occurrence is @-prefixed; added the review's exact negative control as a bats probe.
  5. P3-1 / P3-2 / P3-4 325cd43 — no tmux version floor was declared anywhere (bind-key's own command validation made a below-floor install all-or-nothing only by the luck of write order); the helper only ever read the global option scope, so a session-level set @clikae_touch_scroll off (no -g) was silently unreachable; a blank line split the CHANGELOG's ### Added list. Fixed: an explicit tmux -V floor check (>= 3.1) gates the binding chain, a hand-walked scope chain (pane → window → session → global) makes session-level overrides work, and the blank line is gone. docs/usage.md updated to say the release is forwarded and the bindings are server-wide.

Not fixed this round (out of scope by design): P3-3 (the TS_PANE_ID-shape lint exception relies on there being only two call sites today, not on an enforced shape) — flagged by the review as a should-eventually-harden note, not a correctness defect, and wasn't in this round's decision list.

Full receipts (real-tmux red→green transcripts, bats, shellcheck, bash-3.2 lint, doc-names-exist.sh, git merge-tree) in the round-1 fix report.


Fix round 2 (2026-09-13)

Round-2 adversarial review (0 P1, 1 P2, 7 P3, all measured on real tmux 3.4) found one real bug and six follow-ups. Fixed the P2 and three of the seven P3s (the decision list for this round); 3 commits in order:

  1. P2-1 27b67f5 — the binding passed #{pane_in_mode} (a stacked-layer COUNT), so tree-mode (Ctrl-b w) and clock-mode (Ctrl-b t) — both nonzero — fell into the same "already in a mode" branch as copy-mode and dispatched a send-keys -X call with no command table to match. That failed with tmux's own not in a mode, and because it was one of the only two calls in the file without 2>/dev/null, run-shell rendered the stderr as a view-mode box stacked over the picker/clock — reachable from any session on the server, not just clikae's. Fixed: the binding now passes #{pane_mode} (the mode NAME); the helper acts only on ''/copy-mode/view-mode and exits before any tmux call otherwise. Measured on real tmux 3.4 with a real pty client: Ctrl-b w → tap leaves tree-mode untouched, nothing printed; Ctrl-b t → tap is tmux's own any-key exit, nothing printed; copy-mode tap still cancels and copy-mode swipe still scrolls (regression check on round 1's fixes).
  2. P3-1 ab550fa@clikae_touch_scroll off was case-sensitive; OFF/Off/FALSE scrolled right past the case list, silently. Fixed: fold through tr before matching (bash 3.2 has no ${var,,}). 13 case variants measured on real tmux.
  3. P3-2 / P3-3 / P3-4 / P3-5 aedf4f5 — no doc anywhere declared the tmux floor this feature needs (>= 3.1, set-option -p); docs/DESIGN-tmux.md's Rule 1/Rule 9 global-settings inventories never picked up the six bindings and two options this feature added; nothing told a user that their own root-table MouseDown1Pane/MouseUp1Pane binding gets silently overwritten. Fixed: the floor is now stated in docs/usage.md (with the fallback behavior below it), README.md links to that section, and docs/DESIGN-tmux.md carries the inventory entry in both rules; docs/usage.md states the override plainly. tests/bats/tmux.bats also gained a version-gate test that pins the actual boundary (3.0 red, 3.1 green) instead of only proving the gate fires somewhere in (3.0, 3.4].

Not fixed this round (out of scope by this round's decision list): P3-6 (per-click subprocess cost — a cost, not a defect) and P3-7 (the TS_PANE_ID lint exception's shape is unenforced, same as round 1's P3-3 — the PR already commented that this round would not harden it).

Full receipts (real-tmux red→green transcripts with a real pty client, bats, shellcheck, bash-3.2 lint, doc-names-exist.sh, git merge-tree) in the round-2 fix report.

cverorg pushed a commit that referenced this pull request Sep 13, 2026
Two of the repo's own gates went red on #88, both on true claims reported
as the wrong kind of lie:

- doc gate: `@clikae_touch_scroll` / `@clikae_touch_scroll_lines` in
  docs/usage.md matched the FUNCTION extractor (\b matches after `@`) and
  were reported as 「defined nowhere」. They are tmux user options; the doc's
  claim is that lib/core/tmux.sh sets them. The gate now holds `@`-names to
  that claim instead, with a probe that it fires on an unset one.
- exact-target lint: `send-keys -t "$pane"` in touch_scroll.sh. The value
  is #{pane_id} from the binding — an ID, already exact — and tmux 3.4
  rejects `-t =%0` (measured: can't find pane), so the `=` the lint wants
  is wrong here. Renamed to TS_PANE_ID and added it to the lint's named
  exceptions next to $TMUX_PANE, with the receipt.
cverorg and others added 2 commits September 13, 2026 11:13
Measured 2026-09-13 with a-Shell on iPhone attached over ssh: a swipe
arrives as one SGR left-button press at y1 and one release at y2 —
never a wheel event — and because the Claude Code pane has mouse
tracking on, tmux hands the click to the app and nothing scrolls.

MouseDown1Pane now records the press row (root and copy-mode tables,
keeping the default actions), MouseUp1Pane runs lib/core/touch_scroll.sh:
a movement of two or more rows enters copy-mode and scrolls by
@clikae_touch_scroll_lines × |dy| (default 2) in the finger's
direction; a tap while in copy-mode returns to the live view;
@clikae_touch_scroll off disables it. Desktop wheel and drag-selection
are untouched (drags arrive as MouseDrag*Pane). Verified by the
operator on the phone against a live prototype of the same bindings.

Built by codex (gpt-6-astra) on tank crazy; report in the lane worktree.
Two of the repo's own gates went red on #88, both on true claims reported
as the wrong kind of lie:

- doc gate: `@clikae_touch_scroll` / `@clikae_touch_scroll_lines` in
  docs/usage.md matched the FUNCTION extractor (\b matches after `@`) and
  were reported as 「defined nowhere」. They are tmux user options; the doc's
  claim is that lib/core/tmux.sh sets them. The gate now holds `@`-names to
  that claim instead, with a probe that it fires on an unset one.
- exact-target lint: `send-keys -t "$pane"` in touch_scroll.sh. The value
  is #{pane_id} from the binding — an ID, already exact — and tmux 3.4
  rejects `-t =%0` (measured: can't find pane), so the `=` the lint wants
  is wrong here. Renamed to TS_PANE_ID and added it to the lint's named
  exceptions next to $TMUX_PANE, with the receipt.
@cverorg
cverorg force-pushed the feat/tmux-touch-scroll branch from 3f53fd5 to 298718e Compare September 13, 2026 11:15
KITT added 5 commits September 13, 2026 15:49
…(P1-1)

Stock tmux's root table has no MouseUp1Pane binding at all, so a click's
release falls through to the pane's program by default. This PR's
MouseUp1Pane bound run-shell ONLY, consuming the release instead of
forwarding it — any mouse-tracking app (Claude Code included) got the
press and never the release, turning every desktop click into a stuck
button.

Measured (tmux 3.4, throwaway socket, `mouse on`, pane running
`printf '\e[?1006h\e[?1000h'; cat -v`): stock tmux delivers both the
SGR press (M) and release (m) sequences to the pane; the un-fixed
binding delivered only the press. Fixed by binding "forward first,
translate second" — `send-keys -M` before `run-shell` — so a
mouse-tracking app always receives both press and release. A swipe now
also reaches the app as a click at the release row (acceptable and
documented: the app already got the press on the way down).

2026-09 R1 review, P1-1.
…_touch_y impossible (P1-2)

tmux enters copy-mode OR copy-mode-vi at copy-mode-entry time depending
on the `mode-keys` option, and clikae has never set that option — the
same lesson docs/DESIGN-tmux.md:74 already draws about `base-index`:
not setting an option does not mean the default applies, it means
whatever the human's ~/.tmux.conf says applies, and `set -g mode-keys
vi` is a very common line. Binding only `copy-mode` left
`copy-mode-vi` without its own Down, so a tap there reused whatever
@clikae_touch_y an earlier, unrelated Down had written.

Measured (tmux 3.4, `set -g mode-keys vi`): a tap after a swipe that
had already entered copy-mode-vi kept scrolling further back instead of
cancelling — the only escape a phone (no scroll wheel) has, going
backwards. Instrumented the helper's own tmux calls: the un-fixed
script issued a second `scroll-up` on a same-row tap; the fixed one
issues `cancel`.

Fixed two ways, together: (a) mirror the copy-mode Down/Up pair onto
copy-mode-vi so it gets its own fresh @clikae_touch_y; (b)
touch_scroll.sh now UNSETS @clikae_touch_y (`set-option -pu`) the
moment it reads it, so an Up with no Down on its own table is
structurally incapable of reusing a value some earlier Down left
behind. choose-mode / choose-mode-vi are deliberately left unbound —
a tap there must select, not cancel, which is a different design left
out of scope for this fix.

2026-09 R1 review, P1-2.
`[ "$inmode" = 1 ]` compared a literal against `#{pane_in_mode}`,
which is how many mode layers are stacked on a pane, not a boolean. A
run-shell that produces output stacks its own view-mode over an
existing copy-mode, so a pane already in copy-mode can read inmode=2 —
measured in choose-tree, reachable by this PR's own root-table
fallback (P1-2's review).

With inmode=2: a swipe's `= 1` check was false, so it re-entered
copy-mode on a pane already in one; a tap's `elif` was also false, so
it silently did nothing instead of cancelling — the user could not tap
their way out. Fixed with a numeric guard, `-gt 0`, which treats any
nonzero depth as already-in-mode; the raw arg is also normalized to 0
first so a non-numeric #{pane_in_mode} cannot crash the comparison.

2026-09 R1 review, P2-2.
…on (P2-1)

The new @-option branch in scripts/doc-names-exist.sh:118-122 used an
unconditional `continue` once a name occurred anywhere as `@name`,
which skipped the function-existence check for that name everywhere
else in the docs too. A name can be named both ways in the same doc
set: once `@clikae_touch_scroll` occurred (a real tmux option), a
DIFFERENT, later sentence naming `clikae_touch_scroll` bare —
backtick-wrapped, no `@` — a claim that a function of that name
exists, went silently unchecked.

Negative control from the review: a doc naming `clikae_touch_scroll`
bare, alongside its existing `@clikae_touch_scroll` mentions, stayed
green. Fixed by only skipping the function check when EVERY occurrence
of the name in the docs is @-prefixed; `@?\bname\b` captures each
occurrence with or without its `@`, so any match missing the `@`
proves a bare, function-shaped mention exists and must still resolve
to a real function. Added the review's exact negative control as a
probe in tests/bats/doc-contract.bats; the existing probes stay green.

2026-09 R1 review, P2-1.
…to match (P3)

Three P3s, together because none is independently review-blocking but
all three touch the same install chain:

- touch_scroll.sh's reads only ever asked the GLOBAL scope
  (`show-options -gqv`), so `set @clikae_touch_scroll off` in one
  session (no -g) left the helper reading the global "on" — the
  override was silently unreachable. New _touch_opt() walks the real
  scope chain by hand (tmux's own -A only chains pane -> window ->
  global-window, a SEPARATE namespace from session -> global-session):
  pane (-p -A), then this session, then global. Measured (real tmux,
  two sessions, one instrumented): the un-fixed helper scrolled in
  BOTH the overridden and the untouched session; the fixed helper
  disables translation in the overridden session only.

- No tmux version floor was declared anywhere in the repo, and
  bind-key validates its own command at bind time — a tmux command
  list aborts at the first failure, so on an old tmux the touch-scroll
  chain used to stop at the first rejected line and install whatever
  came before it, all-or-nothing only by the luck of which bind-key
  was written first. `set-option -p`/`-pu` (used by every binding)
  needs tmux >= 3.1; the copy-mode-vi key table needs only >= 2.4.
  New _tmux_touch_scroll_floor_met() (grep tmux -V, bash 3.2-safe
  per-segment numeric compare) gates the whole chain explicitly.
  `mouse on` stays outside the gate — it predates this feature and
  needs none of these flags, so it must not regress on an old tmux for
  a convenience feature that tmux can't run anyway.

- docs/usage.md corrected: the release is now always forwarded (P1-1),
  a swipe reaches the app as a click at the release row too, the
  bindings are server-wide (every session on the tmux server, not only
  clikae's), and dropping `-g` overrides per-session (P3-2).
  CHANGELOG.md: removed the blank line that split the Unreleased ###
  Added list under a strict Markdown renderer.

2026-09 R1 review, P3-1/P3-2/P3-4.
@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

P1-1 fixed (6670ec6): root's MouseUp1Pane bound run-shell only, consuming the release instead of forwarding it — stock tmux's root table has no MouseUp1Pane at all, so any mouse-tracking app (Claude Code included) got the press and never the release. Fixed by binding send-keys -M; run-shell … — forward first, translate second. Proved on a real tmux 3.4 throwaway socket with a real client reading raw SGR bytes off a pty: un-fixed delivered only the press (M); fixed delivers both press and release (M then m).

@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

P1-2 fixed (b94c688): copy-mode-vi — what tmux actually uses under set -g mode-keys vi, a common ~/.tmux.conf line clikae never set — had no bindings of its own, so a tap there fell back and reused a stale @clikae_touch_y, scrolling further backward instead of cancelling (the only way back with no scroll wheel). Fixed by mirroring the copy-mode Down/Up pair onto copy-mode-vi, and by having the helper UNSET @clikae_touch_y the instant it reads it — an Up with no matching Down on its own table can now never reuse a stale value, structurally. Proved on real tmux 3.4 with mode-keys vi: instrumented the helper's own tmux calls — un-fixed issued a second scroll-up on a same-row tap; fixed issues cancel. choose-mode/choose-mode-vi intentionally left unbound (out of scope — a tap there must select, not cancel).

@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

P2-2 fixed (f0e109a): #{pane_in_mode} is a stack depth, not a boolean — a run-shell that prints output stacks its own view-mode over an existing copy-mode, so a pane already in copy-mode can read inmode=2 (measured in choose-tree, reachable via this PR's own root-table fallback). = 1 was false at depth 2: a swipe re-entered copy-mode on top of itself, a tap silently did nothing. Fixed with a numeric -gt 0 guard (plus normalizing a non-numeric arg to 0 first). Proved on real tmux 3.4 with @clikae_touch_y and inmode set directly: un-fixed called copy-mode -t … again on the swipe and sent nothing on the tap; fixed skips the redundant copy-mode call and sends cancel.

@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

P2-1 fixed (fcf1ee4): scripts/doc-names-exist.sh's new @-option branch used an unconditional continue, so once a name occurred anywhere as @name the function-existence check never ran again for that name — even where a DIFFERENT, bare, backtick-wrapped mention claimed a function of the same name exists. Reviewer's negative control (clikae_touch_scroll named bare alongside its real @clikae_touch_scroll mentions) went silently green before this fix. Fixed: only skip the function check when every occurrence in the docs is @-prefixed. Added the review's exact negative control as a probe in tests/bats/doc-contract.bats; all existing probes stay green.

@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

P3-1, P3-2, P3-4 fixed (325cd43): (1) No tmux version floor was declared anywhere — bind-key validates its own command at bind time and a tmux command list aborts at the first failure, so a below-floor tmux used to half-install the chain, all-or-nothing only by the luck of which bind-key came first. set-option -p/-pu (used by every binding here) needs tmux >= 3.1; added an explicit tmux -V floor check gating the whole chain (mouse on itself stays outside the gate — it predates this feature and needs none of these flags). (2) The helper only ever read the global option scope (show-options -gqv), so set @clikae_touch_scroll off in one session (no -g) was silently unreachable. Fixed with a hand-walked scope chain: pane (-p -A) → this session → global. Proved on real tmux with two sessions, one instrumented: un-fixed scrolled in BOTH the overridden and untouched session; fixed disables translation in the overridden session only. (3) Removed the blank line that split the CHANGELOG's ### Added list under a strict Markdown renderer. docs/usage.md updated to match all of the above (release forwarded, server-wide bindings, per-session override).

@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

P3-3 not fixed this round (out of scope by design): the TS_PANE_ID-shape lint exception in lib/core/touch_scroll.sh still relies on there being only two call sites today rather than an enforced shape (case "$TS_PANE_ID" in %[0-9]*) ;; esac). The reviewer flagged it as a should-eventually-harden note, not a correctness defect, and it wasn't in this round's decision list — leaving it open rather than silently dropping it.

KITT added 3 commits September 13, 2026 17:05
…unt (P2-1)

The binding now passes #{pane_mode} (the mode tmux is actually in) instead of
#{pane_in_mode} (a stack-depth count) as touch_scroll.sh's third argument.
tree-mode (choose-tree), clock-mode, and any other mode this helper does not
know how to talk to now exit before making a single tmux call; previously a
nonzero pane_in_mode routed them into a send-keys -X call with no command
table to match, which failed with tmux's own "not in a mode", and because
that call was one of the only two in the file without 2>/dev/null, run-shell
rendered the stderr as a view-mode box stacked over the picker/clock.

Measured on real tmux 3.4 with a real pty client (throwaway socket, PATH
shim pinned to it): Ctrl-b w -> tap leaves tree-mode untouched and prints
nothing; Ctrl-b t -> tap is a tmux-native any-key exit with nothing printed;
copy-mode tap still cancels and copy-mode swipe still scrolls (r1 regression
check). Direct-helper receipts confirm mode=clock-mode/tree-mode/choose-mode/
choose-mode-vi all rc=0 with empty stdout/stderr.

lib/core/tmux.sh's comment at the choose-mode/clock-mode note is rewritten to
say what the old "stacked layer" actually was (an error message, not a real
mode transition) and that clock-mode is now covered by the same guard.

tests/bats/tmux.bats: the third release() argument is now a mode NAME
('' / copy-mode / view-mode / tree-mode / clock-mode / garbage) instead of a
count; new coverage for tree-mode+clock-mode no-op and an unrecognized mode
string no-op.
…vely (P3-1)

`Ctrl-b :` is hand-typed, and the old case list (off|0|no|false) only matched
those exact strings — OFF/Off/FALSE scrolled right on past it, silently: no
error, no indication the setting did not apply. Fold the value through `tr
'[:upper:]' '[:lower:]'` before matching; bash 3.2 has no ${var,,}.

Measured on real tmux 3.4 (throwaway socket): 11 case variants of
off/0/no/false all disable (pane_mode stays empty, scroll_position stays
unset) and on/nope (values that must NOT match) still enter copy-mode and
scroll (scroll_position=20). bats: two new tests, the case-insensitive matches
and a negative check that only-resembles-off values (offline, ' off', 'off ')
keep scrolling, so folding did not become substring matching.
…g/option inventory (P3-2, P3-3, P3-4, P3-5)

P3-2: no doc anywhere said touch-scroll needs tmux >= 3.1 (`set-option -p`,
pane-scoped options — CHANGES FROM 3.0 TO 3.1). A 3.0 user followed
docs/usage.md's recipe and nothing happened, with no explanation why.
docs/usage.md now states the floor; README.md links to that section from the
existing tmux-persistence paragraph; docs/DESIGN-tmux.md's Rule 1 states it
as part of the new inventory entry (below) since DESIGN-tmux is this repo's
declared single source of truth for tmux interactions.

P3-4: DESIGN-tmux.md's Rule 1 (global settings) and Rule 9 (mouse on /
set-clipboard) never got the six key bindings and two `@clikae_touch_*`
options this feature added — the diff that shipped them only touched
docs/usage.md. Both rules now carry an inventory entry for the touch-scroll
chain, cross-referenced to each other and to lib/core/tmux.sh's own comments.

P3-5: docs/usage.md now says plainly that clikae's `bind-key` on
root/copy-mode/copy-mode-vi MouseDown1Pane/MouseUp1Pane overwrites a user's
own binding on those keys (no `-o`, no per-key opt-out) and that
`@clikae_touch_scroll off` silences the translation without restoring the
original binding.

docs/usage.md also picks up the two loose ends from the last two commits
while in the neighborhood: the off value is case-insensitive (P3-1), and the
version floor this commit documents (P3-2/P3-3).

P3-3: tests/bats/tmux.bats' floor test only proved the gate fires SOMEWHERE
(3.0 skip, the 3.4 default install) — any floor anywhere in (3.0, 3.4] made
both pass, wrong ones included. A new test pins the actual boundary: 3.0 is
RED (chain skipped), 3.1 is GREEN (chain installed) — the same boundary
lib/core/tmux.sh's _tmux_touch_scroll_floor_met and the new doc text declare,
backed by upstream tmux's own CHANGES file.
@cverorg

cverorg commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

Round-2 review (0 P1, 1 P2, 7 P3) addressed: P2-1 — the binding now passes #{pane_mode} (the mode name) instead of #{pane_in_mode} (a stacked-layer count), so tree-mode/clock-mode/choose-* exit before any tmux call instead of erroring with "not in a mode" and stacking a view-mode box over the picker/clock. P3-1@clikae_touch_scroll off is now matched case-insensitively. P3-2/P3-3/P3-4/P3-5 — the tmux ≥ 3.1 floor is now stated in docs/usage.md, README.md, and docs/DESIGN-tmux.md; DESIGN-tmux's Rule 1/Rule 9 inventories now list the six bindings and two options; usage.md says a user's own root-table MouseDown1Pane/MouseUp1Pane gets overwritten; the bats version-gate test now pins the actual 3.0-red/3.1-green boundary instead of only proving the gate fires somewhere.

Not fixed this round (out of scope by the decision list): P3-6 (per-click subprocess cost, not a defect) and P3-7 (same as round 1's P3-3 — the TS_PANE_ID lint exception's shape stays unenforced).

All measured on real tmux 3.4 with a real pty client (throwaway socket, PATH-shimmed, never the shared/default server). Full receipts in the round-2 fix report.

@cverorg
cverorg merged commit 2d63407 into main Sep 13, 2026
9 checks passed
@cverorg
cverorg deleted the feat/tmux-touch-scroll branch September 13, 2026 18:02
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.

1 participant