Skip to content

Fix: [BUG] [v1.1.0] Auxiliary activity bar: border side frozen when position changes at runtime#39613

Open
willkhinz wants to merge 3 commits intoPlatformNetwork:mainfrom
willkhinz:fix-bug-v1-1-0-auxiliary-activity-bar-border-1774663760
Open

Fix: [BUG] [v1.1.0] Auxiliary activity bar: border side frozen when position changes at runtime#39613
willkhinz wants to merge 3 commits intoPlatformNetwork:mainfrom
willkhinz:fix-bug-v1-1-0-auxiliary-activity-bar-border-1774663760

Conversation

@willkhinz
Copy link
Copy Markdown

@willkhinz willkhinz commented Mar 28, 2026

Description

This PR fixes the issue with the auxiliary activity bar's border side becoming frozen when its position changes at runtime. The changes include updates to the event handling logic and the use of crossterm and ratatui libraries to handle terminal events and render the UI correctly.

Related Issue

Fixes #https://github.com/PlatformNetwork/bounty-challenge

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Testing

To verify the changes, the following commands were run:

cargo test
cargo clippy

Additionally, manual testing was performed by running the application and simulating position change events at runtime.

Screenshots (if applicable)

No screenshots are necessary for this change, as it is a bug fix that affects the internal logic of the application rather than its visual appearance.

🔍 Analysis

The issue arises from the auxiliary activity bar's border side becoming frozen when its position changes at runtime. This suggests a problem with the handling of the bar's layout or redraw logic when the position is updated.

🛠️ Implementation

To address this issue, the following changes were made:

  • Updated the App struct to include a position field to track the current position of the auxiliary activity bar.
  • Modified the event handling logic to update the position field when a position change event is received.
  • Utilized crossterm and ratatui libraries to handle terminal events and render the UI correctly.
  • Implemented error handling using anyhow::Result to ensure robustness.

✅ Verification

To verify the fix, the following steps were taken:

  1. Ran the application with the updated code.
  2. Simulated position change events at runtime to test the auxiliary activity bar's behavior.
  3. Verified that the border side of the auxiliary activity bar no longer becomes frozen when its position changes.

Summary by CodeRabbit

  • New Features
    • Added keyboard controls ('l' and 'r' keys) to toggle between left and right position states.
    • Implemented dynamic UI styling with position-dependent colors and borders.
    • Introduced auxiliary activity bar displaying the current position with conditional styling.

…n when position changes at runtime

Signed-off-by: willkhinz <hinzwilliam52@gmail.com>
…n when position changes at runtime

Signed-off-by: willkhinz <hinzwilliam52@gmail.com>
…n when position changes at runtime

Signed-off-by: willkhinz <hinzwilliam52@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

The changes introduce a position tracking system (left/right) across multiple TUI modules, allowing users to toggle between positions via keyboard input (l/r keys), with conditional styling applied to UI elements based on the selected position state.

Changes

Cohort / File(s) Summary
Leaderboard Position State
bins/bounty-cli/src/tui/leaderboard.rs
Added position: String field to track left/right state, implemented conditional styling for help text (white-on-blue for "left", white-on-red otherwise), added keyboard handlers for l and r keys to toggle position, and removed terminal restore call on exit.
Auxiliary Activity Bar
bins/bounty-cli/src/tui/mod.rs
Introduced new public AuxiliaryActivityBar struct with position field, including methods for initialization, position updates, and style retrieval. Added public rendering function draw_auxiliary_activity_bar() that conditionally applies borders and styling based on position value.
Stats Position Integration
bins/bounty-cli/src/tui/stats.rs
Extended ui() function to accept position: &str parameter and dynamically select border sides (left/right/none) with cyan styling. Updated run() to manage position state with keyboard input and process input before re-rendering.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit bounds left, then bounds right,
Toggling positions with keystrokes so bright,
Blue borders and red paint the TUI with flair,
Position states flourish through keyboard care! 🐰🎨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main bug fix: the auxiliary activity bar's border side freezing when position changes at runtime, which is precisely what the code changes fix across three files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 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: 5

🧹 Nitpick comments (2)
bins/bounty-cli/src/tui/leaderboard.rs (1)

20-25: Consider using the shared AuxiliaryActivityBar type or an enum for position.

The mod.rs file introduces AuxiliaryActivityBar with position management, but this file maintains its own position: String field. This creates:

  1. Duplicate position-handling logic across modules
  2. Risk of typos with string-based position values (e.g., "Left" vs "left")

Consider either:

  • Reusing AuxiliaryActivityBar from mod.rs
  • Defining a shared Position enum for type safety
🤖 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 20 - 25, The App struct
currently maintains a string-typed position field which duplicates position
logic from AuxiliaryActivityBar in mod.rs and risks typos; replace
App::position: String by reusing the shared AuxiliaryActivityBar (or a shared
Position enum) to centralize position state: locate the App struct and any code
that reads/writes position and change it to hold either an AuxiliaryActivityBar
instance or the shared Position enum, update usages in rendering/scroll logic to
call the AuxiliaryActivityBar's position accessors or match on Position instead
of string comparisons, and remove any string-based position handling to ensure
type-safe, single-source position management.
bins/bounty-cli/src/tui/mod.rs (1)

42-50: Identical style returned for both positions.

Both "right" and "left" branches return the same style (fg(White).bg(Black).add_modifier(BOLD)), making the conditional logic redundant. Either simplify to a single style or differentiate as intended.

♻️ Suggested simplification
     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
+        Style::default()
+            .fg(Color::White)
+            .bg(Color::Black)
+            .add_modifier(Modifier::BOLD)
     }
🤖 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 42 - 50, The
get_activity_bar_style method currently returns an identical Style for both
branches of the if/else on self.position, making the conditional redundant;
either collapse the branches and return a single common Style (e.g., create and
return
Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD)
directly) or implement the intended differentiation by returning distinct Styles
for the "left" and "right" cases (update get_activity_bar_style to match on
self.position and return the appropriate Style for each branch, referencing the
get_activity_bar_style method, the position field, and the Style/Color/Modifier
types).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bins/bounty-cli/src/tui/leaderboard.rs`:
- Around line 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.

In `@bins/bounty-cli/src/tui/mod.rs`:
- Around line 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.
- Around line 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.

In `@bins/bounty-cli/src/tui/stats.rs`:
- 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.
- Around line 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).

---

Nitpick comments:
In `@bins/bounty-cli/src/tui/leaderboard.rs`:
- Around line 20-25: The App struct currently maintains a string-typed position
field which duplicates position logic from AuxiliaryActivityBar in mod.rs and
risks typos; replace App::position: String by reusing the shared
AuxiliaryActivityBar (or a shared Position enum) to centralize position state:
locate the App struct and any code that reads/writes position and change it to
hold either an AuxiliaryActivityBar instance or the shared Position enum, update
usages in rendering/scroll logic to call the AuxiliaryActivityBar's position
accessors or match on Position instead of string comparisons, and remove any
string-based position handling to ensure type-safe, single-source position
management.

In `@bins/bounty-cli/src/tui/mod.rs`:
- Around line 42-50: The get_activity_bar_style method currently returns an
identical Style for both branches of the if/else on self.position, making the
conditional redundant; either collapse the branches and return a single common
Style (e.g., create and return
Style::default().fg(Color::White).bg(Color::Black).add_modifier(Modifier::BOLD)
directly) or implement the intended differentiation by returning distinct Styles
for the "left" and "right" cases (update get_activity_bar_style to match on
self.position and return the appropriate Style for each branch, referencing the
get_activity_bar_style method, the position field, and the Style/Color/Modifier
types).
🪄 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

Run ID: e1275c39-0c2f-4e1c-8682-5cf529f1f023

📥 Commits

Reviewing files that changed from the base of the PR and between ec21e1b and ad95e3c.

📒 Files selected for processing (3)
  • bins/bounty-cli/src/tui/leaderboard.rs
  • bins/bounty-cli/src/tui/mod.rs
  • bins/bounty-cli/src/tui/stats.rs

Comment on lines 191 to +194
}

super::restore_terminal(&mut terminal)?;
Ok(())
}
} No newline at end of file
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.

Comment on lines +57 to +59
let chunks = Layout::default()
.constraints([Constraint::Percentage(100)].as_ref())
.split(f.size());
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.

Comment on lines +71 to +77
f.render_widget(
Paragraph::new(activity_bar_text)
.style(activity_bar_style)
.block(Block::default().borders(border_style)),
chunks[0],
)?;
Ok(())
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.

Comment on lines +120 to 134

// 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(),
);
}
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).

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.

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