Skip to content

fix(terminal): clear stuck xterm selection drag#917

Open
PeppaPigw wants to merge 4 commits into
crynta:mainfrom
PeppaPigw:fix/terminal-stuck-selection
Open

fix(terminal): clear stuck xterm selection drag#917
PeppaPigw wants to merge 4 commits into
crynta:mainfrom
PeppaPigw:fix/terminal-stuck-selection

Conversation

@PeppaPigw

@PeppaPigw PeppaPigw commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What

Adds a small guard around the terminal renderer pool to clear xterm selections when a terminal selection drag loses its matching mouseup event.

Why

If xterm receives a selection mousedown and later misses the corresponding mouseup, subsequent mouse movement can keep extending the terminal selection even when the primary button is no longer pressed. In the app this appears as large text ranges becoming selected while simply moving the mouse.

How

  • Track primary-button mousedown events that start inside .xterm.
  • Stop tracking on normal primary-button mouseup.
  • If a tracked drag later sees mousemove.buttons === 0, clear selections on live terminal slots.
  • Also clear tracked terminal selections when the window blurs or the document becomes hidden.

Testing

  • pnpm exec vitest run src/modules/terminal/lib/stuckSelectionGuard.test.ts
  • pnpm exec tsc --noEmit clean
  • pnpm test
  • git diff --check
  • pnpm lint exits 0; existing Biome warnings remain outside this diff
  • Platforms tested: macOS
  • Full Tauri UI smoke test in this branch

Screenshots / GIFs

Not applicable; no visible UI change.

Notes reviewer

This intentionally keeps the fix scoped to xterm selection state. It does not change composer behavior, markdown rendering, or terminal input handling.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed terminal text selections getting stuck after mouse interactions.
    • Terminal selections now clear when a selection drag is interrupted/released during selection tracking.
    • Improved focus/visibility handling so selections clear reliably only for the active terminal pane.
  • Tests

    • Added automated coverage for selection start detection, release-during-selection behavior, and clearing selection only for the matching active terminal pane.

@PeppaPigw PeppaPigw requested a review from crynta as a code owner July 1, 2026 14:41
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 76b78d43-752d-4b7d-9a1e-a2475bb77eb0

📥 Commits

Reviewing files that changed from the base of the PR and between 9b0b5b2 and 77fc1ef.

📒 Files selected for processing (3)
  • src/modules/terminal/lib/rendererPool.ts
  • src/modules/terminal/lib/stuckSelectionGuard.test.ts
  • src/modules/terminal/lib/stuckSelectionGuard.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/modules/terminal/lib/stuckSelectionGuard.test.ts
  • src/modules/terminal/lib/rendererPool.ts

📝 Walkthrough

Walkthrough

Adds a new selection guard module, wires it into renderer pool event handling, and adds Vitest coverage for the guard helpers and slot-clearing behavior.

Changes

Stuck Terminal Selection Guard

Layer / File(s) Summary
Guard helper types and functions
src/modules/terminal/lib/stuckSelectionGuard.ts
Adds the target/slot types and helper functions for detecting selection start, detecting released moves during selection, and clearing a live selection for a matching leaf.
Renderer pool wiring
src/modules/terminal/lib/rendererPool.ts
Imports the stuck-selection helpers, adds module state for tracking an active drag, defines the guard listener binding, and invokes it from renderer pool configuration.
Guard unit tests
src/modules/terminal/lib/stuckSelectionGuard.test.ts
Adds Vitest coverage for selection-start detection, released-move detection, and clearing only the matching live slot.

Estimated code review effort: 2 (Simple) | ~12 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and clearly matches the PR's xterm stuck-selection fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/modules/terminal/lib/rendererPool.ts (1)

100-141: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Track the initiating slot, not just a boolean, before clearing.

terminalSelectionMouseDown only records that a drag started in some .xterm, not which pooled slot. When the stuck-drag clear fires (mousemove-release, blur, or hidden), clearLiveTerminalSelections(slots) at line 107 clears every slot with a non-null currentLeafId — up to 5 bound terminals — including ones with no relation to the drag that got stuck. It's scoped correctly to the buggy edge case (normal mouseup already resets the flag before blur/hide can fire), but the actual clear is broader than the bug: rare, but it can silently wipe a user's unrelated selection sitting in a different bound tab.

If the mousedown target can be resolved back to a specific slot/leaf id (e.g. via a data-leaf-id on the .xterm container), consider tracking that id instead of a boolean and filtering clearLiveTerminalSelections to just that slot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/terminal/lib/rendererPool.ts` around lines 100 - 141, The
stuck-selection guard in bindStuckSelectionGuard is clearing all pooled terminal
slots because it only tracks a boolean drag state, not the specific slot that
started the selection. Update the mousedown path to resolve the originating slot
or leaf id from the event target (for example via the terminal container), store
that identifier instead of just terminalSelectionMouseDown, and then have
clearTrackedSelection filter clearLiveTerminalSelections so only the matching
slot is reset on mousemove-release, blur, or visibilitychange.
🧹 Nitpick comments (1)
src/modules/terminal/lib/stuckSelectionGuard.test.ts (1)

8-13: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Lock the closest receiver contract.

This mock only checks the selector, so it would still pass if isTerminalSelectionStart stopped binding the original target before calling closest. Add a receiver assertion or use a real element spy so the .call(target, ".xterm") contract stays covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/terminal/lib/stuckSelectionGuard.test.ts` around lines 8 - 13,
The current `target()` mock in `stuckSelectionGuard.test.ts` only validates the
selector and does not verify the `closest` receiver, so it can miss regressions
in `isTerminalSelectionStart`. Update the test around `target()` and the
`closest` spy to assert the method is invoked with the original target as `this`
(for example via a receiver assertion or a real element spy), so the
`.call(target, ".xterm")` binding contract remains covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/terminal/lib/stuckSelectionGuard.ts`:
- Around line 28-35: The current stuck-selection cleanup is too broad because
clearLiveTerminalSelections clears every slot with a non-null currentLeafId
instead of only the terminal that started the drag. Update
bindStuckSelectionGuard in rendererPool.ts to track the originating leaf/slot
for the active drag, then pass that specific target into
clearLiveTerminalSelections so only that terminal’s selection is cleared. Keep
the fix localized around clearLiveTerminalSelections and
bindStuckSelectionGuard, and avoid iterating over all bound slots for cleanup.

---

Duplicate comments:
In `@src/modules/terminal/lib/rendererPool.ts`:
- Around line 100-141: The stuck-selection guard in bindStuckSelectionGuard is
clearing all pooled terminal slots because it only tracks a boolean drag state,
not the specific slot that started the selection. Update the mousedown path to
resolve the originating slot or leaf id from the event target (for example via
the terminal container), store that identifier instead of just
terminalSelectionMouseDown, and then have clearTrackedSelection filter
clearLiveTerminalSelections so only the matching slot is reset on
mousemove-release, blur, or visibilitychange.

---

Nitpick comments:
In `@src/modules/terminal/lib/stuckSelectionGuard.test.ts`:
- Around line 8-13: The current `target()` mock in `stuckSelectionGuard.test.ts`
only validates the selector and does not verify the `closest` receiver, so it
can miss regressions in `isTerminalSelectionStart`. Update the test around
`target()` and the `closest` spy to assert the method is invoked with the
original target as `this` (for example via a receiver assertion or a real
element spy), so the `.call(target, ".xterm")` binding contract remains covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b327ef4d-1367-4ef1-8432-d6a7d1525bdd

📥 Commits

Reviewing files that changed from the base of the PR and between 3f4d680 and 9b0b5b2.

📒 Files selected for processing (3)
  • src/modules/terminal/lib/rendererPool.ts
  • src/modules/terminal/lib/stuckSelectionGuard.test.ts
  • src/modules/terminal/lib/stuckSelectionGuard.ts

Comment thread src/modules/terminal/lib/stuckSelectionGuard.ts Outdated
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