Skip to content

Commit 3ffa9ab

Browse files
🎨 Palette: Hide terminal cursor during CLI spinner animation
πŸ’‘ What: Hides the terminal cursor (`\x1b[?25l`) while the `Spinner` is active and restores it (`\x1b[?25h`) when the spinner finishes, fails, or goes out of scope (via the `Drop` trait). 🎯 Why: Prevents the terminal cursor from jumping around or rendering alongside the spinner frames during long-running tasks, creating a smoother and more polished CLI experience. The `Drop` implementation ensures safety so the cursor is not permanently hidden if the CLI crashes or is interrupted. πŸ“Έ Before/After: Visual noise during "πŸ¦€ Thinking..." is reduced. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 6309a89 commit 3ffa9ab

10 files changed

Lines changed: 59 additions & 11 deletions

β€Ž.Jules/palette.mdβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## $(date +%Y-%m-%d) - [CLI Cursor UX Fix]
2+
**Learning:** This app is a Rust CLI, not a web frontend. Standard web accessibility rules (ARIA labels, DOM structure) do not apply. UX improvements here involve terminal manipulation (using `crossterm` for ANSI output, cursor hiding, colors, text alignment). Hiding the terminal cursor during a `Spinner` animation prevents the cursor from awkwardly jumping or rendering alongside spinner frames.
3+
**Action:** When implementing CLI UX changes that modify terminal state (like hiding a cursor), ALWAYS implement a `Drop` trait to ensure the state (like cursor visibility) is predictably restored if the process is interrupted or panics.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"session_id": "108fa52a890542199d833cd93ed0429f",
3+
"messages": [
4+
"review MCP tool"
5+
],
6+
"input_tokens": 3,
7+
"output_tokens": 13
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"session_id": "200c7e63f6f04a57ac6211a90e4e35d0",
3+
"messages": [
4+
"review MCP tool",
5+
"review MCP tool"
6+
],
7+
"input_tokens": 6,
8+
"output_tokens": 32
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"session_id": "59b491f8b3b2439a957e0c680cd193bc",
3+
"messages": [
4+
"review MCP tool",
5+
"review MCP tool"
6+
],
7+
"input_tokens": 6,
8+
"output_tokens": 32
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"session_id": "d5662f5a103444a9984129c1c8b01597",
3+
"messages": [
4+
"review MCP tool",
5+
"review MCP tool"
6+
],
7+
"input_tokens": 6,
8+
"output_tokens": 32
9+
}

β€Žrust/.claw/sessions/session-1775386832313-0.jsonlβ€Ž

Lines changed: 0 additions & 2 deletions
This file was deleted.

β€Žrust/.claw/sessions/session-1775386842352-0.jsonlβ€Ž

Lines changed: 0 additions & 2 deletions
This file was deleted.

β€Žrust/.claw/sessions/session-1775386852257-0.jsonlβ€Ž

Lines changed: 0 additions & 2 deletions
This file was deleted.

β€Žrust/.claw/sessions/session-1775386853666-0.jsonlβ€Ž

Lines changed: 0 additions & 2 deletions
This file was deleted.

β€Žrust/crates/rusty-claude-cli/src/render.rsβ€Ž

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Write as FmtWrite;
22
use std::io::{self, Write};
33

4-
use crossterm::cursor::{MoveToColumn, RestorePosition, SavePosition};
4+
use crossterm::cursor::{Hide, MoveToColumn, RestorePosition, SavePosition, Show};
55
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor, Stylize};
66
use crossterm::terminal::{Clear, ClearType};
77
use crossterm::{execute, queue};
@@ -47,6 +47,16 @@ impl Default for ColorTheme {
4747
#[derive(Debug, Default, Clone, PartialEq, Eq)]
4848
pub struct Spinner {
4949
frame_index: usize,
50+
cursor_hidden: bool,
51+
}
52+
53+
impl Drop for Spinner {
54+
fn drop(&mut self) {
55+
if self.cursor_hidden {
56+
let mut out = io::stdout();
57+
let _ = execute!(out, Show);
58+
}
59+
}
5060
}
5161

5262
impl Spinner {
@@ -63,6 +73,10 @@ impl Spinner {
6373
theme: &ColorTheme,
6474
out: &mut impl Write,
6575
) -> io::Result<()> {
76+
if !self.cursor_hidden {
77+
queue!(out, Hide)?;
78+
self.cursor_hidden = true;
79+
}
6680
let frame = Self::FRAMES[self.frame_index % Self::FRAMES.len()];
6781
self.frame_index += 1;
6882
queue!(
@@ -85,13 +99,15 @@ impl Spinner {
8599
out: &mut impl Write,
86100
) -> io::Result<()> {
87101
self.frame_index = 0;
102+
self.cursor_hidden = false;
88103
execute!(
89104
out,
90105
MoveToColumn(0),
91106
Clear(ClearType::CurrentLine),
92107
SetForegroundColor(theme.spinner_done),
93108
Print(format!("βœ” {label}\n")),
94-
ResetColor
109+
ResetColor,
110+
Show
95111
)?;
96112
out.flush()
97113
}
@@ -103,13 +119,15 @@ impl Spinner {
103119
out: &mut impl Write,
104120
) -> io::Result<()> {
105121
self.frame_index = 0;
122+
self.cursor_hidden = false;
106123
execute!(
107124
out,
108125
MoveToColumn(0),
109126
Clear(ClearType::CurrentLine),
110127
SetForegroundColor(theme.spinner_failed),
111128
Print(format!("✘ {label}\n")),
112-
ResetColor
129+
ResetColor,
130+
Show
113131
)?;
114132
out.flush()
115133
}

0 commit comments

Comments
Β (0)