🎨 Palette: Dynamic Reasoning Spinner#53
Conversation
This implements Phase 2.2 of the TUI Enhancement Plan. During an extended reasoning step (`ThinkingDelta`), the standard `🦀 Thinking...` indicator is dynamically replaced by a `🧠 Reasoning...` indicator. This is animated by cycling through frames and overwriting the current line using standard ANSI escapes so it fits seamlessly into the current synchronous stream processor without changing the underlying thread/async structures. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
Pull request overview
Adds a distinct “Reasoning…” animated indicator during streaming thinking phases to better differentiate extended model reasoning from the generic “Thinking” state in the Rust TUI.
Changes:
- Made
Spinnerconfigurable with pluggable frame sets and introducedREASONING_FRAMES. - Updated streaming handling to render an inline animated “Reasoning…” line on
ThinkingDeltaand clear it on block end. - Renamed “Thinking hidden” summaries to “Reasoning hidden” (including updating a test) and added a short palette note under
.Jules/.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| rust/crates/rusty-claude-cli/src/render.rs | Adds configurable spinner frames + new reasoning frame set. |
| rust/crates/rusty-claude-cli/src/main.rs | Renders an inline animated “Reasoning…” indicator during streaming thinking deltas; updates hidden-summary wording + test. |
| .Jules/palette.md | Documents the rationale/approach for inline streaming spinners. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| pub fn set_frames(&mut self, frames: &'static [&'static str]) { | ||
| self.frames = frames; |
There was a problem hiding this comment.
set_frames accepts any slice, including an empty one (&[]). If an empty slice is set, tick() will panic due to self.frames.len() being 0 (modulo / index). Consider rejecting empty slices (e.g., return a Result, or debug_assert!(!frames.is_empty()) + keep current frames) to make the API safe.
| self.frames = frames; | |
| debug_assert!(!frames.is_empty(), "spinner frames must not be empty"); | |
| if !frames.is_empty() { | |
| self.frames = frames; | |
| } |
| if !block_has_thinking_summary { | ||
| render_thinking_block_summary(out, None, false)?; | ||
| // We do NOT render the static block summary here anymore, | ||
| // we just start animating the reasoning spinner. | ||
| block_has_thinking_summary = true; | ||
| } |
There was a problem hiding this comment.
This change stops printing the static summary on ThinkingDelta, but push_output_block() still renders a summary on ContentBlockStart for OutputContentBlock::Thinking (which is typically thinking: "" in streaming). That will still emit a "Reasoning (0 chars hidden)" line before the animation, which contradicts the stated goal of avoiding static thinking/hidden messages during standard queries. Consider suppressing the summary for streaming thinking blocks and relying solely on the animated line (or render a summary only at block end when the final char count is known).
| if self.emit_output { | ||
| let frame = Spinner::REASONING_FRAMES | ||
| [reasoning_frame_idx % Spinner::REASONING_FRAMES.len()]; | ||
| reasoning_frame_idx += 1; | ||
|
|
||
| write!( | ||
| out, | ||
| "\r\x1b[2K\x1b[38;5;208m{frame} Reasoning...\x1b[0m" | ||
| ) | ||
| .and_then(|()| out.flush()) | ||
| .map_err(|error| RuntimeError::new(error.to_string()))?; | ||
| } |
There was a problem hiding this comment.
ThinkingDelta can arrive in many small chunks; this implementation writes + flushes on every delta, which can become a high-frequency stdout flush loop and slow down streaming. Consider throttling updates (e.g., only advance/render a frame every N ms using Instant, or every N deltas) while still clearing on ContentBlockStop.
This implements Phase 2.2 of the TUI Enhancement Plan. During an extended reasoning step (`ThinkingDelta`), the standard static block summary is replaced with a dynamically animated `🧠 Reasoning...` spinner. The animation is performed by emitting standard ANSI escape sequences directly into the streaming `ContentBlockDelta` logic in `AnthropicRuntimeClient::consume_stream`, seamlessly rendering animation without redesigning the CLI threads. This clearly visually differentiates the "Thinking" state from the active "Reasoning" state as requested. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
|
@claude code review |
|
@codex code review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 347ea83906
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| [reasoning_frame_idx % Spinner::REASONING_FRAMES.len()]; | ||
| reasoning_frame_idx += 1; | ||
|
|
||
| write!(out, "\r\x1b[2K\x1b[38;5;208m{frame} Reasoning...\x1b[0m") |
There was a problem hiding this comment.
Replace raw ANSI spinner writes with terminal-safe rendering
The new reasoning indicator in consume_stream writes raw escape sequences directly to out ("\r\x1b[2K..."), which bypasses the terminal abstraction used elsewhere (Spinner::tick via crossterm). In environments that do not interpret ANSI control codes (notably some Windows consoles or redirected logs), this will surface literal escape bytes/carriage returns and degrade output readability; using crossterm commands (or gating on ANSI capability) avoids this regression.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@copilot, make changes based on the above suggestion.
There was a problem hiding this comment.
@codex, make changes based on:
@copilot, make changes based on:
There was a problem hiding this comment.
Summary
- Added crossterm terminal-rendering imports in
main.rsso the reasoning indicator uses terminal-safe commands instead of raw ANSI escape bytes. rust/crates/rusty-claude-cli/src/main.rsL41-L45 - Updated
consume_streamto use a localOutputWriteradapter (stdoutvssink) that still respectsemit_outputwhile providing a concrete writer type compatible with crossterm macros. rust/crates/rusty-claude-cli/src/main.rsL6441-L6478 - Replaced raw
write!(..., "\r\x1b...")spinner updates with crosstermqueue!commands (MoveToColumn,Clear,SetForegroundColor,Print,ResetColor) and replaced raw clear writes withexecute!on block stop. rust/crates/rusty-claude-cli/src/main.rsL6559-L6584
Testing
- ❌
cargo fmt --package rusty-claude-cli(failed initially because command was run from repo root without aCargo.tomlthere). - ✅
cargo fmt --manifest-path rust/Cargo.toml -p rusty-claude-cli - ✅
cargo check --manifest-path rust/Cargo.toml -p rusty-claude-cli
Committed as:
e505452— Use crossterm commands for reasoning spinner rendering.
Also created the follow-up PR message via make_pr with title:
- Follow-up: terminal-safe reasoning spinner rendering.
|
@gemini-code-assist, what are the steps and line numbers to make this fix? |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
💡 What
Added a dynamic animated
🧠 Reasoning...spinner that appears whenever the model enters an extended reasoning phase (ThinkingDelta).🎯 Why
To clearly visually differentiate the generic waiting "Thinking" state from the active extended "Reasoning" state as requested in the TUI Enhancement Plan Phase 2.2.
♿ Accessibility / UX
Thinkingblock ends.PR created automatically by Jules for task 11951746077518678892 started by @badMade