Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions bins/bounty-cli/src/tui/leaderboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ struct App {
entries: Vec<LeaderboardEntry>,
scroll_offset: usize,
error: Option<String>,
is_resizing: bool,
last_mouse_position: (u16, u16),
}

fn parse_entries(data: &Value) -> Vec<LeaderboardEntry> {
Expand Down Expand Up @@ -121,7 +123,11 @@ fn ui(frame: &mut Frame, app: &App) {
.border_style(Style::default().fg(Color::Cyan))
.title(title),
)
.row_highlight_style(Style::default().bg(Color::DarkGray));
.row_highlight_style(Style::default().bg(if app.is_resizing {
Color::DarkGray
} else {
Color::DarkGray
}));
Comment on lines +126 to +130
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

Conditional always returns the same value.

Both branches of the if app.is_resizing conditional return Color::DarkGray, making the conditional meaningless. This appears to be incomplete implementation or copy-paste error.

πŸ”§ Suggested fix - either remove the conditional or differentiate colors
         .row_highlight_style(Style::default().bg(if app.is_resizing {
-            Color::DarkGray
+            Color::Gray  // or another distinct color during resize
         } else {
             Color::DarkGray
         }));

Or simply:

-        .row_highlight_style(Style::default().bg(if app.is_resizing {
-            Color::DarkGray
-        } else {
-            Color::DarkGray
-        }));
+        .row_highlight_style(Style::default().bg(Color::DarkGray));
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.row_highlight_style(Style::default().bg(if app.is_resizing {
Color::DarkGray
} else {
Color::DarkGray
}));
.row_highlight_style(Style::default().bg(if app.is_resizing {
Color::Gray // or another distinct color during resize
} else {
Color::DarkGray
}));
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bins/bounty-cli/src/tui/leaderboard.rs` around lines 126 - 130, The
conditional in the call to row_highlight_style always returns Color::DarkGray
(if app.is_resizing { Color::DarkGray } else { Color::DarkGray }), so remove the
redundant branch or provide a different color for the non-resizing branch;
update the row_highlight_style invocation that uses Style::default() and
app.is_resizing to either pass Color::DarkGray directly or choose a distinct
color (e.g., Color::Gray or Color::Black) for the else branch so the resizing
flag actually changes the highlight color.


frame.render_widget(table, chunks[0]);

Expand All @@ -137,6 +143,8 @@ pub async fn run(rpc_url: &str) -> Result<()> {
entries: vec![],
scroll_offset: 0,
error: None,
is_resizing: false,
last_mouse_position: (0, 0),
};

let mut last_fetch = Instant::now() - Duration::from_secs(10);
Expand Down Expand Up @@ -172,10 +180,16 @@ pub async fn run(rpc_url: &str) -> Result<()> {
_ => {}
}
}
} else if let Event::Mouse(mouse) = event::read()? {
if mouse.kind == event::MouseEventKind::Down {
app.is_resizing = true;
} else if mouse.kind == event::MouseEventKind::Up {
app.is_resizing = false;
} else if mouse.kind == event::MouseEventKind::Moved {
app.last_mouse_position = (mouse.column, mouse.row);
}
}
Comment on lines +183 to 191
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 | πŸ”΄ Critical

Critical: Double event::read() causes event consumption bug and potential blocking.

Line 168 calls event::read() which consumes the event. If that event is NOT a Key event, the else if at line 183 calls event::read() again, which blocks waiting for a completely different event. This means mouse events will be lost or cause the application to hang.

The pattern should match on the already-read event, not read again:

πŸ› Proposed fix
         if event::poll(Duration::from_millis(100))? {
-            if let Event::Key(key) = event::read()? {
+            match event::read()? {
+                Event::Key(key) => {
                 if key.kind == KeyEventKind::Press {
                     match key.code {
                         KeyCode::Char('q') | KeyCode::Esc => break,
                         KeyCode::Up | KeyCode::Char('k') => {
                             app.scroll_offset = app.scroll_offset.saturating_sub(1);
                         }
                         KeyCode::Down | KeyCode::Char('j') => {
                             if app.scroll_offset + 1 < app.entries.len() {
                                 app.scroll_offset += 1;
                             }
                         }
                         _ => {}
                     }
                 }
-            } else if let Event::Mouse(mouse) = event::read()? {
+                }
+                Event::Mouse(mouse) => {
                 if mouse.kind == event::MouseEventKind::Down {
                     app.is_resizing = true;
                 } else if mouse.kind == event::MouseEventKind::Up {
                     app.is_resizing = false;
                 } else if mouse.kind == event::MouseEventKind::Moved {
                     app.last_mouse_position = (mouse.column, mouse.row);
                 }
+                }
+                _ => {}
             }
         }
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bins/bounty-cli/src/tui/leaderboard.rs` around lines 183 - 191, The code
currently calls event::read() twice which consumes events and can block; reuse
the already-read event instead of calling event::read() again. Change the second
branch to match on the previously obtained event value (the Event variable from
the earlier event::read() call) and handle Event::Mouse by updating
app.is_resizing and app.last_mouse_position accordingly (references:
Event::Mouse, mouse.kind, app.is_resizing, app.last_mouse_position) so mouse
events are processed from the same event object and no extra event::read() is
performed.

}
}

super::restore_terminal(&mut terminal)?;
Ok(())
}
}
Comment on lines 193 to +195
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 | πŸ”΄ Critical

Critical: Missing restore_terminal() leaves terminal in corrupted state.

The function calls super::setup_terminal() at line 141, which enables raw mode, enters alternate screen, and enables mouse capture. However, when the loop exits, there's no call to super::restore_terminal(&mut terminal)? before returning. This will leave the user's terminal in raw mode with the alternate screen still active.

Compare with stats.rs which correctly calls super::restore_terminal(&mut terminal)? at line 168.

πŸ› Proposed fix
         }
     }
+    super::restore_terminal(&mut terminal)?;
     Ok(())
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}
super::restore_terminal(&mut terminal)?;
Ok(())
}
}
}
super::restore_terminal(&mut terminal)?;
Ok(())
}
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bins/bounty-cli/src/tui/leaderboard.rs` around lines 193 - 195, The function
calls super::setup_terminal() (which enables raw mode/alternate screen) but
never calls super::restore_terminal(&mut terminal) on exit; add a call to
super::restore_terminal(&mut terminal)? just before returning Ok(()) so the
terminal is restored (match the pattern used in stats.rs), ensuring terminal is
cleaned up even when the event loop exits or on early returns.

26 changes: 24 additions & 2 deletions bins/bounty-cli/src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod weights;

use anyhow::Result;
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
Expand All @@ -13,15 +14,36 @@ use std::io;
pub fn setup_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend)?;
Ok(terminal)
}

pub fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()> {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?;
terminal.show_cursor()?;
Ok(())
}

pub fn handle_events(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, is_resizing: &mut bool) -> Result<()> {
loop {
if let event::Event::Mouse(event) = event::read()? {
match event.kind {
event::MouseEventKind::Moved => {
// Handle mouse move event
}
event::MouseEventKind::Pressed(_) => {
*is_resizing = true;
// Handle mouse press event
}
event::MouseEventKind::Released(_) => {
*is_resizing = false;
// Handle mouse release event
}
_ => {}
}
}
}
Comment on lines +30 to +48
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 | πŸ”΄ Critical

🧩 Analysis chain

🌐 Web query:

crossterm 0.28 MouseEventKind enum variants

πŸ’‘ Result:

The MouseEventKind enum in crossterm 0.28 has the following variants: pub enum MouseEventKind { Down(MouseButton), Up(MouseButton), Drag(MouseButton), Moved, ScrollDown, ScrollUp, ScrollLeft, ScrollRight, }

Citations:


🏁 Script executed:

cat bins/bounty-cli/src/tui/mod.rs

Repository: PlatformNetwork/bounty-challenge

Length of output: 1652


🏁 Script executed:

rg "handle_events" --type rs

Repository: PlatformNetwork/bounty-challenge

Length of output: 102


🏁 Script executed:

rg "handle_events"

Repository: PlatformNetwork/bounty-challenge

Length of output: 222


🏁 Script executed:

cat bins/bounty-cli/src/tui/stats.rs | head -200

Repository: PlatformNetwork/bounty-challenge

Length of output: 5271


🏁 Script executed:

cat bins/bounty-cli/src/tui/leaderboard.rs | head -200

Repository: PlatformNetwork/bounty-challenge

Length of output: 6365


Critical: MouseEventKind variants don't exist in crossterm 0.28 and will cause compilation failure.

This function has multiple critical issues:

  1. Non-existent enum variants: MouseEventKind::Pressed(_) and MouseEventKind::Released(_) do not exist in crossterm 0.28. The correct variants are MouseEventKind::Down(MouseButton) and MouseEventKind::Up(MouseButton). This will cause immediate compilation errors.

  2. Infinite blocking loop: The loop has no break condition and event::read() blocks indefinitely, so the function never returns.

  3. Unused parameter: terminal is never used.

  4. Dead code and architectural mismatch: This function is never called. Additionally, both stats.rs and leaderboard.rs use the non-blocking pattern with event::poll(Duration::from_millis(100))? and proper exit conditions. This blocking approach is incompatible with that architecture.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bins/bounty-cli/src/tui/mod.rs` around lines 30 - 48, The handle_events
function uses non-existent MouseEventKind variants (Pressed/Released), blocks
indefinitely with event::read(), never uses the terminal param, and doesn't
match the non-blocking pattern used by stats.rs and leaderboard.rs; fix by
updating pattern matches to MouseEventKind::Down(MouseButton) and
MouseEventKind::Up(MouseButton) in handle_events, replace event::read() with the
non-blocking event::poll(Duration::from_millis(100))? + event::read() only when
poll returns true, add a clear exit/break condition consistent with
stats.rs/leaderboard.rs (e.g., checking for a Quit key or returning on a
specific event), and either use or remove the terminal parameter in
handle_events to avoid unused-parameter warnings; ensure is_resizing is set on
Down/Up events and integrate this function into the same non-blocking event loop
architecture used by stats.rs and leaderboard.rs.

}
21 changes: 18 additions & 3 deletions bins/bounty-cli/src/tui/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fn stat_block<'a>(label: &'a str, value: u64, color: Color) -> Paragraph<'a> {
)
}

fn ui(frame: &mut Frame, stats: &StatsData, error: &Option<String>) {
fn ui(frame: &mut Frame, stats: &StatsData, error: &Option<String>, is_resizing: bool) {
let outer = Layout::default()
.direction(Direction::Vertical)
.constraints([
Expand Down Expand Up @@ -117,13 +117,21 @@ fn ui(frame: &mut Frame, stats: &StatsData, error: &Option<String>) {
.style(Style::default().fg(Color::DarkGray))
.block(Block::default().borders(Borders::ALL));
frame.render_widget(help, outer[2]);

if is_resizing {
let resize_text = Paragraph::new("Resizing...")
.style(Style::default().fg(Color::Red).bold())
.block(Block::default().borders(Borders::ALL));
frame.render_widget(resize_text, outer[2]);
}
}

pub async fn run(rpc_url: &str) -> Result<()> {
let mut terminal = super::setup_terminal()?;
let mut stats = StatsData::default();
let mut error: Option<String> = None;
let mut last_fetch = Instant::now() - Duration::from_secs(10);
let mut is_resizing = false;

loop {
if last_fetch.elapsed() >= Duration::from_secs(5) {
Expand All @@ -137,7 +145,7 @@ pub async fn run(rpc_url: &str) -> Result<()> {
last_fetch = Instant::now();
}

terminal.draw(|f| ui(f, &stats, &error))?;
terminal.draw(|f| ui(f, &stats, &error, is_resizing))?;

if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
Expand All @@ -146,10 +154,17 @@ pub async fn run(rpc_url: &str) -> Result<()> {
{
break;
}
} else if let Event::Mouse(event) = event::read()? {
if event.kind == MouseEventKind::Down {
Comment on lines +157 to +158
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

Missing import for MouseEventKind.

MouseEventKind is used directly at lines 158 and 160-161, but it's not imported. The current import at line 2 only brings in event (the module), Event, KeyCode, and KeyEventKind. Either qualify as event::MouseEventKind (which would be consistent with leaderboard.rs) or add it to the import.

πŸ”§ Suggested fix - add to imports
-use crossterm::event::{self, Event, KeyCode, KeyEventKind};
+use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseEventKind};
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if let Event::Mouse(event) = event::read()? {
if event.kind == MouseEventKind::Down {
use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseEventKind};
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bins/bounty-cli/src/tui/stats.rs` around lines 157 - 158, The code uses
MouseEventKind but doesn't import it; update the imports to either add
MouseEventKind to the existing import list (so MouseEventKind is available
unqualified) or qualify its usage as event::MouseEventKind where Event::Mouse is
handled (in the block containing Event::Mouse(event) and checks like event.kind
== MouseEventKind::Down) so the symbol resolves; ensure the change is applied
near the top imports that currently bring in event, Event, KeyCode, and
KeyEventKind.

is_resizing = true;
} else if event.kind == MouseEventKind::Up {
is_resizing = false;
terminal.draw(|f| ui(f, &stats, &error, is_resizing))?;
}
}
Comment on lines +157 to 164
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 | πŸ”΄ Critical

Critical: Double event::read() causes event consumption bug and potential blocking.

Same issue as in leaderboard.rs: line 151 calls event::read() and consumes the event. If it's not a Key event, the else if at line 157 calls event::read() again, blocking for a new event instead of handling the one already read.

πŸ› Proposed fix
         if event::poll(Duration::from_millis(100))? {
-            if let Event::Key(key) = event::read()? {
+            match event::read()? {
+                Event::Key(key) => {
                 if key.kind == KeyEventKind::Press
                     && matches!(key.code, KeyCode::Char('q') | KeyCode::Esc)
                 {
                     break;
                 }
-            } else if let Event::Mouse(event) = event::read()? {
+                }
+                Event::Mouse(event) => {
                 if event.kind == MouseEventKind::Down {
                     is_resizing = true;
                 } else if event.kind == MouseEventKind::Up {
                     is_resizing = false;
                     terminal.draw(|f| ui(f, &stats, &error, is_resizing))?;
                 }
+                }
+                _ => {}
             }
         }
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bins/bounty-cli/src/tui/stats.rs` around lines 157 - 164, The code calls
event::read() twice which consumes the first event and can block; instead, read
the event once into a single variable and then match on that variable (avoid
calling event::read() again). Modify the logic around Event::Mouse and the
earlier key-handling branch so you use the same event value (the variable
returned from the single event::read() call) to check MouseEventKind::Down and
::Up, toggle is_resizing, and call terminal.draw(|f| ui(f, &stats, &error,
is_resizing)) when needed; update any pattern matches using Event::Mouse to
reference the existing event variable rather than calling event::read() again.

}
}

super::restore_terminal(&mut terminal)?;
Ok(())
}
}