Skip to content

fix(tui): add guarded mouse list interactions#281

Merged
peaktwilight merged 1 commit intomainfrom
fix/270-tui-scroll-clean
May 1, 2026
Merged

fix(tui): add guarded mouse list interactions#281
peaktwilight merged 1 commit intomainfrom
fix/270-tui-scroll-clean

Conversation

@peaktwilight
Copy link
Copy Markdown
Collaborator

@peaktwilight peaktwilight commented May 1, 2026

Summary

  • Persist TUI findings ListState so keyboard and mouse navigation do not pin selection to the viewport edge.
  • Add guarded mouse wheel, click-to-select, and hover handling only when the findings list is active.
  • Document mouse support and Shift-drag selection-copy behavior in help.

Review notes

Closes #269

Summary by CodeRabbit

  • New Features
    • Added mouse support to the findings list with scroll-wheel navigation, click-to-select functionality, and hover highlighting for improved usability and interaction.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

Adds mouse support to the TUI findings list with scroll-wheel navigation, click-to-select, and hover tracking. Simultaneously fixes scrolling behavior by persisting ListState across frames and introducing hover-aware styling for findings rows.

Changes

Cohort / File(s) Summary
TUI Mouse Support & State Persistence
src/tui.rs
Enables mouse capture during terminal session lifecycle; adds handle_mouse dispatcher supporting scroll, click, and hover actions over findings list. Persists ListState, list_area, and hover_index in TuiApp to maintain scroll state and apply hover styling. Introduces coordinate-to-index mapping with fixed row height calculations and validation tests. Updates input loop to prioritize mouse events and on-screen help text. Applies .scroll_padding(0) to list widget configuration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit's delight, swift paws so keen,
Now dancing through findings, pixel by screen,
Scroll, click, and hover—the mouse finds its way,
While state persists true, through each rendered day, 🖱️
No more viewport walls, just smooth, flowing grace!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 accurately describes the main change: adding guarded mouse list interactions to the TUI findings list, which aligns with the PR's primary objective.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #269: persisting ListState to fix selection pinning, adding scroll padding, and additionally implements guarded mouse handling for comprehensive list interaction.
Out of Scope Changes check ✅ Passed All changes are within scope: persisting ListState, adding mouse support with guards, hover tracking, and help text updates directly address the issue objectives without introducing unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/270-tui-scroll-clean

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tui.rs`:
- Around line 412-418: The handler is calling drain_queued_scroll_events(kind)
which consumes and discards the first non-scroll event and collapses a burst of
wheel events into a single move, causing rapid scrolls to be limited and queued
clicks/keys to be lost; change the logic to not consume past the first
non-scroll event—either process every MouseEventKind::ScrollUp/ScrollDown
individually (calling move_selection per scroll event) or implement safe
coalescing by counting consecutive scroll events, buffering the first non-scroll
event returned by drain_queued_scroll_events (do not drop it) and replaying it
into the main event loop after applying the appropriate number of
move_selection(delta) calls; update the code paths around
drain_queued_scroll_events and the match handling in the main loop to ensure
non-scroll events are preserved and replayed rather than discarded.
- Around line 143-146: hover_index is stored as an absolute index into the
filtered list and must be cleared whenever the visible list or ListState
changes; update code paths that mutate scrolling, filtering, sorting or the list
state (e.g., the functions that handle wheel scrolling, filter/sort updates, and
any code that updates list_state or list_area) to set self.hover_index = None
after mutating the list or ListState (and after resizing the list_area) so the
hover background won’t stick to the wrong visible row; locate usages of
hover_index, ListState, selected and list_area and add hover_index invalidation
immediately after those mutations.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a689aa3-113f-4332-83cf-44d08b9c05b5

📥 Commits

Reviewing files that changed from the base of the PR and between 21f00b8 and 041d6ea.

📒 Files selected for processing (1)
  • src/tui.rs

Comment thread src/tui.rs
Comment on lines 143 to +146
selected: usize,
list_state: ListState,
list_area: Rect,
hover_index: Option<usize>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Invalidate hover_index when the visible list changes.

hover_index is cached as an absolute filtered-list index, so after wheel scrolling or any filter/sort change the row under the pointer can change without a MouseEventKind::Moved. The hover background can then stick to the wrong visible row until the mouse moves again.

Also applies to: 431-439, 1376-1387

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 143 - 146, hover_index is stored as an absolute
index into the filtered list and must be cleared whenever the visible list or
ListState changes; update code paths that mutate scrolling, filtering, sorting
or the list state (e.g., the functions that handle wheel scrolling, filter/sort
updates, and any code that updates list_state or list_area) to set
self.hover_index = None after mutating the list or ListState (and after resizing
the list_area) so the hover background won’t stick to the wrong visible row;
locate usages of hover_index, ListState, selected and list_area and add
hover_index invalidation immediately after those mutations.

Comment thread src/tui.rs
Comment on lines +412 to +418
kind @ (MouseEventKind::ScrollUp | MouseEventKind::ScrollDown) => {
let last_kind = drain_queued_scroll_events(kind);
match last_kind {
MouseEventKind::ScrollUp => self.move_selection(-1),
MouseEventKind::ScrollDown => self.move_selection(1),
_ => {}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't drain and collapse the event queue here.

drain_queued_scroll_events() consumes the first queued non-scroll event and drops it on _ => break, and it also compresses an arbitrary wheel burst into a single row move. That means rapid scrolling is capped to one step, and a click/key queued behind the wheel can disappear.

🛠️ Safe fallback
-            kind @ (MouseEventKind::ScrollUp | MouseEventKind::ScrollDown) => {
-                let last_kind = drain_queued_scroll_events(kind);
-                match last_kind {
-                    MouseEventKind::ScrollUp => self.move_selection(-1),
-                    MouseEventKind::ScrollDown => self.move_selection(1),
-                    _ => {}
-                }
-            }
+            MouseEventKind::ScrollUp => self.move_selection(-1),
+            MouseEventKind::ScrollDown => self.move_selection(1),

If you still want coalescing, buffer the first non-scroll event and replay it in the main loop instead of reading past it.

Also applies to: 2922-2934

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 412 - 418, The handler is calling
drain_queued_scroll_events(kind) which consumes and discards the first
non-scroll event and collapses a burst of wheel events into a single move,
causing rapid scrolls to be limited and queued clicks/keys to be lost; change
the logic to not consume past the first non-scroll event—either process every
MouseEventKind::ScrollUp/ScrollDown individually (calling move_selection per
scroll event) or implement safe coalescing by counting consecutive scroll
events, buffering the first non-scroll event returned by
drain_queued_scroll_events (do not drop it) and replaying it into the main event
loop after applying the appropriate number of move_selection(delta) calls;
update the code paths around drain_queued_scroll_events and the match handling
in the main loop to ensure non-scroll events are preserved and replayed rather
than discarded.

@peaktwilight peaktwilight merged commit 61c7230 into main May 1, 2026
17 checks passed
@peaktwilight peaktwilight deleted the fix/270-tui-scroll-clean branch May 1, 2026 13:17
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.

TUI: findings list selection pinned to viewport edge when scrolling

1 participant