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
19 changes: 16 additions & 3 deletions bins/bounty-cli/src/tui/leaderboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct App {
entries: Vec<LeaderboardEntry>,
scroll_offset: usize,
error: Option<String>,
position: String,
}

fn parse_entries(data: &Value) -> Vec<LeaderboardEntry> {
Expand Down Expand Up @@ -123,10 +124,16 @@ fn ui(frame: &mut Frame, app: &App) {
)
.row_highlight_style(Style::default().bg(Color::DarkGray));

let style = if app.position == "left" {
Style::default().fg(Color::White).bg(Color::Blue)
} else {
Style::default().fg(Color::White).bg(Color::Red)
};

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

let help = Paragraph::new(" ↑/↓ scroll | q/Esc quit | auto-refresh 5s")
.style(Style::default().fg(Color::DarkGray))
.style(style)
.block(Block::default().borders(Borders::ALL));
frame.render_widget(help, chunks[1]);
}
Expand All @@ -137,6 +144,7 @@ pub async fn run(rpc_url: &str) -> Result<()> {
entries: vec![],
scroll_offset: 0,
error: None,
position: "right".to_string(),
};

let mut last_fetch = Instant::now() - Duration::from_secs(10);
Expand Down Expand Up @@ -169,13 +177,18 @@ pub async fn run(rpc_url: &str) -> Result<()> {
app.scroll_offset += 1;
}
}
KeyCode::Char('l') => {
app.position = "left".to_string();
}
KeyCode::Char('r') => {
app.position = "right".to_string();
}
_ => {}
}
}
}
}
}

super::restore_terminal(&mut terminal)?;
Ok(())
}
}
Comment on lines 191 to +194
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 call leaves terminal in corrupted state.

When the user exits the leaderboard (via q or Esc), the function returns without restoring the terminal. This leaves raw mode enabled and the alternate screen active, corrupting the user's terminal session.

The stats.rs module correctly calls super::restore_terminal(&mut terminal)? before returning (see line 174 in stats.rs). This file should follow the same pattern.

🐛 Proposed fix to restore terminal before returning
     }

+    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 191 - 194, The function
in leaderboard.rs exits without restoring the terminal; ensure you call
super::restore_terminal(&mut terminal)? before any return paths (including the
early exits on 'q' or 'Esc') and immediately before the final Ok(()) return so
raw mode and the alternate screen are cleaned up; locate uses of the local
variable terminal and add the call to super::restore_terminal(&mut terminal)? in
those exit branches and prior to the final return.

51 changes: 51 additions & 0 deletions bins/bounty-cli/src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,54 @@ pub fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -
terminal.show_cursor()?;
Ok(())
}

pub struct AuxiliaryActivityBar {
position: String,
}

impl AuxiliaryActivityBar {
pub fn new(position: String) -> Self {
Self { position }
}

pub fn update_position(&mut self, position: String) {
self.position = position;
}

pub fn get_activity_bar_style(&self) -> Style {
let mut style = Style::default();
if self.position == "right" {
style = style.fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD);
} else if self.position == "left" {
style = style.fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD);
}
style
}
}

pub fn draw_auxiliary_activity_bar(
f: &mut Frame<CrosstermBackend<io::Stdout>>,
activity_bar: &mut AuxiliaryActivityBar,
) -> Result<()> {
let chunks = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size());
Comment on lines +57 to +59
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

Use f.area() instead of deprecated f.size().

In Ratatui 0.29.0, Frame::size() is deprecated in favor of Frame::area(). Other files in this PR (e.g., leaderboard.rs line 67) correctly use frame.area().

🔧 Proposed fix
     let chunks = Layout::default()
         .constraints([Constraint::Percentage(100)].as_ref())
-        .split(f.size());
+        .split(f.area());
📝 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
let chunks = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size());
let chunks = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.area());
🤖 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 57 - 59, Replace the deprecated
call to Frame::size() with Frame::area() in the layout creation: locate the code
that builds the Layout using
Layout::default().constraints([...]).split(f.size()) and change split(f.size())
to split(f.area()), ensuring the use of the same frame variable (f) as in other
files (e.g., leaderboard.rs) and keeping Layout, Constraint and split unchanged.

let activity_bar_style = activity_bar.get_activity_bar_style();
let activity_bar_text = if activity_bar.position == "right" {
"Right"
} else {
"Left"
};
let border_style = if activity_bar.position == "right" {
Borders::RIGHT
} else {
Borders::LEFT
};
f.render_widget(
Paragraph::new(activity_bar_text)
.style(activity_bar_style)
.block(Block::default().borders(border_style)),
chunks[0],
)?;
Ok(())
Comment on lines +71 to +77
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

Compilation error: render_widget returns (), not Result.

Frame::render_widget() in Ratatui returns (), so the ? operator cannot be applied. This code will fail to compile.

🐛 Proposed fix
     f.render_widget(
         Paragraph::new(activity_bar_text)
             .style(activity_bar_style)
             .block(Block::default().borders(border_style)),
         chunks[0],
-    )?;
+    );
     Ok(())
 }
What is the return type of Frame::render_widget in ratatui 0.29?
🤖 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 71 - 77, The code incorrectly
applies the `?` operator to `Frame::render_widget`, which returns `()` (not a
`Result`); remove the `?` from the `f.render_widget(...)` call (in the block
that builds the
`Paragraph::new(activity_bar_text).style(activity_bar_style).block(Block::default().borders(border_style))`)
and ensure the surrounding function still returns `Ok(())` (or propagates an
actual `Result`) as appropriate.

}
31 changes: 26 additions & 5 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>, position: &str) {
let outer = Layout::default()
.direction(Direction::Vertical)
.constraints([
Expand Down Expand Up @@ -117,13 +117,28 @@ 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]);

// Update the border based on the position
let border_style = Style::default().fg(Color::Cyan);
let borders = match position {
"left" => Borders::LEFT,
"right" => Borders::RIGHT,
_ => Borders::NONE,
};
frame.render_widget(
Block::default()
.borders(borders)
.border_style(border_style),
frame.area(),
);
}
Comment on lines +120 to 134
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

Border overlay renders on top of all content, potentially obscuring widgets.

The border Block is rendered at frame.area() (full frame) after the title, stats grid, and help widgets. In Ratatui, later render_widget calls draw over earlier ones. Depending on the terminal and border character rendering, this may partially overwrite the existing content.

Consider rendering the border first (before other widgets) or rendering it in a dedicated side region rather than the full frame.

🔧 Suggested approach: Render border first

Move the border rendering to the beginning of ui():

 fn ui(frame: &mut Frame, stats: &StatsData, error: &Option<String>, position: &str) {
+    // Render position-based border first (background layer)
+    let border_style = Style::default().fg(Color::Cyan);
+    let borders = match position {
+        "left" => Borders::LEFT,
+        "right" => Borders::RIGHT,
+        _ => Borders::NONE,
+    };
+    frame.render_widget(
+        Block::default()
+            .borders(borders)
+            .border_style(border_style),
+        frame.area(),
+    );
+
     let outer = Layout::default()
         .direction(Direction::Vertical)
         // ... rest of function
-
-    // Update the border based on the position
-    let border_style = Style::default().fg(Color::Cyan);
-    let borders = match position {
-        "left" => Borders::LEFT,
-        "right" => Borders::RIGHT,
-        _ => Borders::NONE,
-    };
-    frame.render_widget(
-        Block::default()
-            .borders(borders)
-            .border_style(border_style),
-        frame.area(),
-    );
 }
📝 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
// Update the border based on the position
let border_style = Style::default().fg(Color::Cyan);
let borders = match position {
"left" => Borders::LEFT,
"right" => Borders::RIGHT,
_ => Borders::NONE,
};
frame.render_widget(
Block::default()
.borders(borders)
.border_style(border_style),
frame.area(),
);
}
}
🤖 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 120 - 134, The border Block is
being drawn last (using border_style/borders and frame.render_widget with
frame.area()), which causes it to overlay and potentially overwrite the title,
stats grid, and help widgets in ui(); move the border rendering to the start of
ui() so the border is drawn first, or alternatively compute a dedicated side
region and render the border Block into that region instead of frame.area()
(adjust the code around the borders variable, border_style, and the
Block::default() call to use the new region).


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 position = "left"; // default position
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

Inconsistent default position across modules.

This module defaults to "left" while leaderboard.rs defaults to "right". For a consistent user experience, consider aligning the default position across all TUI screens.

🤖 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` at line 141, The default value for the TUI
position in this module is inconsistent: change the variable named position
(currently set via let mut position = "left" in stats.rs) to use the same
default as leaderboard.rs (i.e., "right") so all TUI screens share the same
default placement; update the initialization of position to "right" and run a
quick build/test to ensure no other logic relies on the old default.


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

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

// Check for position updates
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press
if key.kind == KeyEventKind::Press && matches!(key.code, KeyCode::Char('l')) {
position = "left";
} else if key.kind == KeyEventKind::Press && matches!(key.code, KeyCode::Char('r')) {
position = "right";
} else if key.kind == KeyEventKind::Press
&& matches!(key.code, KeyCode::Char('q') | KeyCode::Esc)
{
break;
}
}
}

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

}

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