Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@fcoury
Copy link
Owner

@fcoury fcoury commented Dec 14, 2025

Summary

  • Add animated braille spinner overlay in the grid area when queries run
  • Show elapsed time in the loading overlay
  • Increase event loop polling frequency (16ms vs 50ms) during queries to keep UI responsive and animations smooth
  • Upgrade status bar running indicator to Priority::Critical with bold yellow styling for better visibility
  • Add throbber-widgets-tui 0.9 dependency for spinner widget

Problem

When queries execute in tsql, the UI blocks completely and the current loading indicator ("⏳ running" in the status bar) is too subtle, making the application appear frozen.

Solution

  1. Non-blocking UI: Dynamic poll duration - 16ms (~60 FPS) when query running, 50ms when idle
  2. Prominent Loading Overlay: Centered spinner in grid area with elapsed time counter
  3. Enhanced Status Bar: Running indicator upgraded to Critical priority with bold styling

Testing

  • All 337 tests pass
  • cargo fmt --all passes
  • cargo clippy --all --all-targets -- -D warnings passes
  • Manual testing recommended with queries of various durations (fast, 2s, 10s+)

Summary by CodeRabbit

  • New Features

    • Centered loading spinner overlay during database queries with real-time elapsed execution time.
    • Enhanced status line to prominently indicate running queries.
    • Faster polling while a query is active for snappier UI responsiveness.
  • Bug Fixes

    • Overlay appears only when there’s sufficient space and reliably clears on completion, error, or cancellation.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add animated braille spinner overlay in the grid area when queries run
- Show elapsed time in the loading overlay
- Increase event loop polling frequency (16ms vs 50ms) during queries
  to keep UI responsive and animations smooth
- Upgrade status bar running indicator to Priority::Critical with bold
  yellow styling for better visibility
- Add throbber-widgets-tui 0.9 dependency for spinner widget

This addresses the issue where the UI appeared frozen during long-running
queries and the existing "⏳ running" indicator was too subtle.
@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Caution

Review failed

The pull request is closed.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: adding a prominent loading spinner that displays during query execution, with supporting features like elapsed time tracking and improved polling.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 012295d and 312fb19.

📒 Files selected for processing (1)
  • crates/tsql/src/app/app.rs (14 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 0

🧹 Nitpick comments (4)
crates/tsql/src/app/app.rs (4)

726-730: Consider keeping timer/throbber state co-located with DbSession (optional), but current wiring is fine.
Right now db.running lives in DbSession while query_start_time/throbber_state live in App. This works, but if you anticipate more “running UI” state, it may be cleaner to group them (or wrap into a small struct) to avoid drift.

Also applies to: 844-846


1254-1313: Guard overlay rendering for extremely small grid_area to avoid zero-area widget edge cases.
If the terminal is tiny, overlay_area/inner can end up with 0 height/width, and some widgets can behave oddly. Consider an early return like “only render overlay if grid_area.width >= 20 && grid_area.height >= 3” (or if inner.height >= 2).

-                if self.db.running {
+                if self.db.running && grid_area.width >= 20 && grid_area.height >= 3 {
                     // Calculate centered overlay area (40% width, minimum 20 chars, 5 lines height)
                     let overlay_width = (grid_area.width * 40 / 100).max(20).min(grid_area.width);
                     let overlay_height = 5u16.min(grid_area.height);
@@
-                    // Layout for spinner and elapsed time
+                    // Layout for spinner and elapsed time
                     let chunks = Layout::default()
                         .direction(Direction::Vertical)
                         .constraints([
                             Constraint::Length(1), // Spinner with label
                             Constraint::Length(1), // Elapsed time
                         ])
                         .split(inner);

4961-4964: Elapsed timer is only set for execute_query(); decide if meta queries / cell updates should also set it.
db.running becomes true in execute_meta_query() and execute_cell_update(), so the overlay shows, but elapsed time won’t (since query_start_time remains None). If the intent is “elapsed for any running operation”, set query_start_time = Some(Instant::now()) in those paths too.

Also applies to: 3277-3286, 3021-3028


5137-5140: Clear query_start_time on DbEvent::CellUpdated too (defensive consistency).
Today CellUpdated doesn’t set query_start_time, so it won’t leak, but clearing it there makes the state machine harder to accidentally break later (especially if you later add elapsed timing for updates).

             DbEvent::CellUpdated { row, col, value } => {
                 self.db.running = false;
+                self.query_start_time = None;
                 // Update the grid cell
                 if let Some(grid_row) = self.grid.rows.get_mut(row) {

Also applies to: 5181-5190, 5202-5211

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f326d2c and 11f0985.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • Cargo.toml (1 hunks)
  • crates/tsql/Cargo.toml (1 hunks)
  • crates/tsql/src/app/app.rs (11 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Run cargo fmt --all to format the code before committing changes
Run cargo clippy --all --all-targets -- -D warnings to check for lints and fix them before pushing changes

**/*.rs: Use rustfmt with default settings as the source of truth for code formatting in Rust files
Use snake_case for function and module names in Rust
Use CamelCase for type and trait names in Rust
Use SCREAMING_SNAKE_CASE for constant names in Rust
Keep changes focused and prefer small, composable helper functions over large UI/app methods
Place unit tests colocated with code using #[cfg(test)] modules for pure logic testing

Files:

  • crates/tsql/src/app/app.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (windows-latest)
🔇 Additional comments (5)
Cargo.toml (1)

55-57: LGTM: dependency added in the right place; sanity-check for duplicates and advisories.
Given this is a new third-party dependency, double-check there isn’t another throbber-widgets-tui declaration (different version/features) elsewhere and consider running your usual advisory scan (e.g., cargo audit) in CI.

crates/tsql/src/app/app.rs (3)

14-17: Confirm throbber-widgets-tui API usage matches v0.9 (and ratatui 0.29).
The imports and ThrobberState usage look straightforward, but since this is an external UI widget crate, please confirm BRAILLE_SIX, calc_next(), and render_stateful_widget() are the intended v0.9 API surface for your ratatui version.

Also applies to: 47-47


990-994: Nice: dynamic polling + per-tick throbber advance; please manually verify CPU impact.
At 16ms polling you’ll effectively redraw ~60 FPS while a query runs. That’s likely fine, but it’s worth a quick manual check on slower terminals/remote SSH to ensure it doesn’t peg a core.

Also applies to: 1654-1662


5310-5319: Status-line “running” segment upgrade is a good UX improvement.
Making it Priority::Critical with bold yellow styling should address the “app appears frozen” perception even without the overlay.

crates/tsql/Cargo.toml (1)

48-50: Dependency verification complete: throbber-widgets-tui 0.9 is fully compatible with your setup. The crate is designed for ratatui (confirmed compatible with 0.29), has MSRV of 1.74.0 (satisfied by workspace 1.80), and uses MIT license matching the workspace.

- Add minimum grid area guard (width >= 20, height >= 5) to prevent
  overlay rendering issues on small terminals
- Set query_start_time in execute_cell_update and execute_meta_query
  for consistent timing display across all query types
- Clear query_start_time on CellUpdated event for defensive consistency
The timing_info segment already shows "N rows (Xms)", so last_status
now only shows "Ready" or "[truncated]" instead of duplicating the
row count and timing information.
@fcoury fcoury merged commit 4257ba0 into master Dec 14, 2025
5 checks passed
@fcoury fcoury deleted the feat/non-blocking-query-spinner branch December 14, 2025 20:54
Copy link

@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: 1

🧹 Nitpick comments (2)
crates/tsql/src/app/app.rs (2)

726-729: New App state looks fine; consider grouping spinner/timing state into a small struct.

Right now App is already very large; QueryRunUi { throbber_state, query_start_time } would keep this more maintainable without changing behavior.

Also applies to: 844-845


990-993: Tick-rate change achieves smooth animation, but consider making the idle redraw cheaper/configurable.

20 FPS redraw while idle (50ms) may be more CPU than needed for a mostly-static screen. If you want to keep it snappy, consider (a) increasing idle to ~100–200ms, or (b) only forcing redraws on state changes while idle.

Also applies to: 1654-1661

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 11f0985 and 012295d.

📒 Files selected for processing (1)
  • crates/tsql/src/app/app.rs (14 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Run cargo fmt --all to format the code before committing changes
Run cargo clippy --all --all-targets -- -D warnings to check for lints and fix them before pushing changes

**/*.rs: Use rustfmt with default settings as the source of truth for code formatting in Rust files
Use snake_case for function and module names in Rust
Use CamelCase for type and trait names in Rust
Use SCREAMING_SNAKE_CASE for constant names in Rust
Keep changes focused and prefer small, composable helper functions over large UI/app methods
Place unit tests colocated with code using #[cfg(test)] modules for pure logic testing

Files:

  • crates/tsql/src/app/app.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test (windows-latest)
🔇 Additional comments (4)
crates/tsql/src/app/app.rs (4)

1254-1313: Loading overlay rendering is solid; layering order works.

Nice touch with Clear + centered block + elapsed time, and the grid_area size guard prevents ugly layouts.


3026-3029: Query timer is started consistently across query/update/meta paths.

This aligns well with the overlay + elapsed UX.

Also applies to: 3285-3288, 4963-4966


5313-5321: Promoted status “running” indicator is clear and consistent with the new overlay.

Priority + bold styling should prevent the “app frozen” perception.


14-16: No changes needed—all imports are compatible with your locked dependency versions.

ratatui::layout::Alignment, ratatui::style::Modifier, and throbber_widgets_tui::{Throbber, ThrobberState, BRAILLE_SIX} are all properly exported in ratatui 0.29.0 and throbber-widgets-tui 0.9.0 respectively. The usage patterns in the code (e.g., Alignment::Center, Throbber::default().throbber_set(BRAILLE_SIX), ThrobberState::default()) match the documented APIs for both crates.

Comment on lines 5140 to 5142
self.db.running = false;
self.query_start_time = None;
self.db.last_command_tag = result.command_tag.clone();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clear query_start_time in all paths that force db.running = false (e.g., connect failures / disconnect).

Right now, some branches set self.db.running = false but don’t reset query_start_time (e.g., DbEvent::ConnectError, DbEvent::ConnectionLost, and :disconnect). Overlay won’t show because it’s gated by db.running, but stale state is easy to avoid.

diff --git a/crates/tsql/src/app/app.rs b/crates/tsql/src/app/app.rs
@@
             DbEvent::ConnectError { error } => {
                 self.db.status = DbStatus::Error;
                 self.db.client = None;
                 self.db.running = false;
+                self.query_start_time = None;
                 self.current_connection_name = None;
                 self.last_status = Some("Connect failed (see error)".to_string());
                 self.last_error = Some(format!("Connection error: {}", error));
             }
             DbEvent::ConnectionLost { error } => {
                 self.db.status = DbStatus::Error;
                 self.db.client = None;
                 self.db.running = false;
+                self.query_start_time = None;
                 self.current_connection_name = None;
                 self.last_status = Some("Connection lost (see error)".to_string());
                 self.last_error = Some(format!("Connection lost: {}", error));
             }
@@
             "disconnect" | "dc" => {
                 self.db.client = None;
                 self.db.cancel_token = None;
                 self.db.status = DbStatus::Disconnected;
                 self.db.running = false;
+                self.query_start_time = None;
                 self.last_status = Some("Disconnected".to_string());
             }

Also applies to: 5183-5186, 5189-5192, 5204-5207

🤖 Prompt for AI Agents
In crates/tsql/src/app/app.rs around lines 5140-5142 (and also the similar
blocks at 5183-5186, 5189-5192, 5204-5207), some branches set self.db.running =
false but do not clear self.query_start_time, leaving stale state; update each
branch that forces db.running = false (e.g., DbEvent::ConnectError,
DbEvent::ConnectionLost, :disconnect handlers and any other early-return paths
that set running=false) to also set self.query_start_time = None immediately
after setting self.db.running = false so the overlay/state is fully cleared.

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.

2 participants