Skip to content

Conversation

@cheesycod
Copy link
Contributor

@cheesycod cheesycod commented Dec 28, 2025

Summary by CodeRabbit

  • New Features

    • Interactive terminal UI now the default CLI interface with REPL/TUI modes, command history, autocomplete, scrolling output and status bar
    • Multiple color themes with live theme switcher and theme cycling
    • New modals: Help, About, and Quit; keyboard shortcuts for help, clear, theme, navigation, and quit
  • Documentation

    • Added comprehensive CLI README with usage, shortcuts, examples and architecture
  • Chores

    • Updated UI dependencies and a platform-specific compilation tweak for network error handling

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Adds a new Terminal UI (TUI) to the Khronos CLI: app state, event loop, widgets, theming, rendering, and a TUI entrypoint. CLI defaults to launching the TUI when no script is provided. New dependencies and docs included; one platform-gated error path adjusted.

Changes

Cohort / File(s) Summary
UI Dependencies
crates/cli/Cargo.toml
Added Ratatui ecosystem and related crates (ratatui, crossterm, tui-textarea, syntect, indicatif, comfy-table, unicode-width, ansi-to-tui, unicode-segmentation, textwrap) and expanded sqlx features (postgres, uuid).
Documentation
crates/cli/README.md
New CLI README with features, quick start, shortcuts, architecture, customization, troubleshooting, and contribution guidance.
CLI Entrypoint & Dispatch
crates/cli/src/cli.rs, crates/cli/src/main.rs
Added CliEntrypointAction::Tui, dispatch for launching TUI (run_tui), and changed default entrypoint to Tui when no script/inline_script provided; added tui and tui_entrypoint modules.
TUI App State
crates/cli/src/tui/app.rs
New public App<'a> state manager and AppMode enum with methods for theme handling, input/output/history, scrolling, modals, autocomplete, and lifecycle controls.
Event System
crates/cli/src/tui/events.rs
New Event enum and EventHandler that polls terminal input with tick timeouts; key-match helpers and common key checks (quit, theme, clear, help).
Theme System
crates/cli/src/tui/theme.rs
New Theme struct, HSL→RGB helper, 10+ preset themes, all(), default(), and cycling utilities with unit tests.
Rendering & Module Facade
crates/cli/src/tui/mod.rs, crates/cli/src/tui/ui.rs
New TUI module re-exports; render(frame, app) composes layout (content/input/status) and overlays (help, theme switcher).
Widget Suite
crates/cli/src/tui/widgets/mod.rs, crates/cli/src/tui/widgets/...
New widgets and modals: Dashboard, OutputPanel, ReplInput, StatusBar, ThemeSwitcher, HelpModal, QuitModal, AboutModal, plus associated Widget trait impls.
TUI Runtime Loop
crates/cli/src/tui_entrypoint.rs
New pub async fn run_tui(cli: &mut Cli) -> io::Result<()> that initializes terminal, runs render/event loop, handles input, history, execution, modals, and restores terminal on exit.
Platform-specific error gating
crates/runtime/src/plugins/antiraid/httpserver/mod.rs
Restricted non-TCP socket error branch to Unix builds via #[cfg(unix)].
Docs tweak
docs/primitives.md
Capitalization change and added HTTPClient: httpclientP.Plugin field to TemplateContext documentation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Terminal
    participant EventHandler
    participant App
    participant Cli

    rect rgb(220,235,250)
    note over Terminal,App: Initialization
    Terminal->>Terminal: enable raw mode, alt screen, mouse
    Terminal->>App: create App state
    Terminal->>EventHandler: create EventHandler(tick_rate)
    end

    rect rgb(250,245,220)
    loop until App.should_quit
        Terminal->>App: render(frame, app)
        User->>Terminal: key/resize/input
        Terminal->>EventHandler: poll event
        EventHandler-->>App: emit Event (Key/Resize/Tick)
        alt Theme switcher active
            App->>App: update filter/selection/apply
        else Help/Modal active
            App->>App: handle modal keys (ESC/Enter)
        else Normal REPL mode
            App->>Cli: on Enter -> spawn_script / execute
            Cli-->>App: execution result -> App.add_output(...)
            App->>App: save history, update scroll/time
        end
    end
    end

    rect rgb(220,245,220)
    note over Terminal: Teardown
    Terminal->>Terminal: disable raw mode, restore screen
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 In terminal meadows colors bloom bright,
Widgets nibble keys from morning to night,
Themes hop and shimmer, history takes flight,
The CLI now dances in TUI light. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.05% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'TUI' is vague and non-descriptive. While it refers to a real component in the changeset, it provides no meaningful information about what the change accomplishes, why it was made, or what a teammate would learn from scanning the PR history. Expand the title to describe the main change, such as 'Implement Terminal UI (TUI) mode for CLI' or 'Add interactive TUI interface with theme switcher and command history'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tui-ran

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

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

🧹 Nitpick comments (8)
crates/cli/src/tui/widgets/output_panel.rs (1)

128-133: Loose substring matching may cause false positives.

The check line.contains("nil") will match words like "vanilla" or "nillable". Similarly, line.contains("true") matches "truetype" and line.contains("false") matches "falsehood". Consider using word boundary matching or checking for standalone values.

🔎 Proposed fix using word boundaries
-                } else if line.contains("nil") || line.contains("true") || line.contains("false") {
+                } else if line.split_whitespace().any(|w| matches!(w, "nil" | "true" | "false")) {

Alternatively, use a regex with word boundaries if more complex patterns are needed.

crates/cli/src/tui/widgets/dashboard.rs (2)

117-135: Duplicated helper function centered_rect.

This function is identical to centered_rect in crates/cli/src/tui/ui.rs (lines 72-90). Extract it to a shared utility module to avoid duplication.

🔎 Suggested approach

Create a shared utility module, e.g., crates/cli/src/tui/utils.rs:

// crates/cli/src/tui/utils.rs
use ratatui::layout::{Constraint, Direction, Layout, Rect};

pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let popup_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - percent_y) / 2),
            Constraint::Percentage(percent_y),
            Constraint::Percentage((100 - percent_y) / 2),
        ])
        .split(r);

    Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - percent_x) / 2),
            Constraint::Percentage(percent_x),
            Constraint::Percentage((100 - percent_x) / 2),
        ])
        .split(popup_layout[1])[1]
}

Then import from both ui.rs and dashboard.rs.


52-60: Consider naming the magic number for spacing.

The value 5 used for spacing could be extracted to a named constant for clarity.

🔎 Proposed fix
+        const INPUT_BAR_CLEARANCE: u16 = 5;
+        
         // Spacer to push commands down below the input bar
-        let commands_area_y = chunks[2].y + 5; // Leave room for input bar
-        let commands_height = chunks[2].height.saturating_sub(5);
+        let commands_area_y = chunks[2].y + INPUT_BAR_CLEARANCE;
+        let commands_height = chunks[2].height.saturating_sub(INPUT_BAR_CLEARANCE);
crates/cli/src/tui/ui.rs (2)

72-91: Duplicate centered_rect function.

This helper is duplicated in theme_switcher.rs (lines 112-130). Consider extracting to a shared utility module to avoid code duplication.


32-32: Unnecessary clone on Copy type.

Theme derives Copy (per theme.rs line 5), so .clone() is redundant and can be removed.

🔎 Proposed fix
-        frame.render_widget(Dashboard::new(theme.clone()), main_chunks[0]);
+        frame.render_widget(Dashboard::new(*theme), main_chunks[0]);
crates/cli/src/tui/widgets/theme_switcher.rs (1)

93-95: highlight_style has no effect without ListState.

The highlight_style on List only applies when rendering with StatefulWidget::render and a ListState. Since you're manually styling selected items in lines 79-89, this is redundant and can be removed.

🔎 Proposed fix
         List::new(list_items)
-            .highlight_style(Style::default().bg(theme.card))
             .render(chunks[1], buf);
crates/cli/src/tui/theme.rs (1)

334-338: Potentially confusing Default implementation.

Self::default() inside impl Default calls the inherent Theme::default() method due to method resolution rules, but this pattern is fragile and confusing. If someone renames or removes the inherent default() method, this becomes infinite recursion.

🔎 Proposed fix - be explicit
 impl Default for Theme {
     fn default() -> Self {
-        Self::default()
+        Self::zen_dark()
     }
 }
crates/cli/src/tui/app.rs (1)

164-173: O(n) removal from front of Vec.

self.command_history.remove(0) is O(n) since it shifts all elements. With MAX_COMMAND_HISTORY = 1000, this is noticeable on frequent command entries. Consider using VecDeque like you do for output.

🔎 Proposed fix

In the struct definition:

-    pub command_history: Vec<String>,
+    pub command_history: VecDeque<String>,

In save_to_history:

     pub fn save_to_history(&mut self) {
         let input = self.get_input();
         if !input.trim().is_empty() {
-            self.command_history.push(input);
+            self.command_history.push_back(input);
             if self.command_history.len() > MAX_COMMAND_HISTORY {
-                self.command_history.remove(0);
+                self.command_history.pop_front();
             }
         }
         self.history_index = None;
     }

Note: You'll need to update history_prev and history_next to use index-based access which VecDeque supports.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e34059a and b0de6d8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • crates/cli/Cargo.toml
  • crates/cli/README.md
  • crates/cli/src/cli.rs
  • crates/cli/src/main.rs
  • crates/cli/src/tui/app.rs
  • crates/cli/src/tui/events.rs
  • crates/cli/src/tui/mod.rs
  • crates/cli/src/tui/theme.rs
  • crates/cli/src/tui/ui.rs
  • crates/cli/src/tui/widgets/dashboard.rs
  • crates/cli/src/tui/widgets/mod.rs
  • crates/cli/src/tui/widgets/output_panel.rs
  • crates/cli/src/tui/widgets/repl_input.rs
  • crates/cli/src/tui/widgets/status_bar.rs
  • crates/cli/src/tui/widgets/theme_switcher.rs
  • crates/cli/src/tui_entrypoint.rs
  • crates/runtime/src/plugins/antiraid/httpserver/mod.rs
🧰 Additional context used
🧬 Code graph analysis (8)
crates/cli/src/cli.rs (1)
crates/cli/src/tui_entrypoint.rs (1)
  • run_tui (16-221)
crates/cli/src/tui/widgets/output_panel.rs (2)
crates/cli/src/tui/app.rs (2)
  • new (73-97)
  • default (258-260)
crates/cli/src/tui/ui.rs (1)
  • render (11-70)
crates/cli/src/tui/ui.rs (3)
crates/cli/src/tui/widgets/status_bar.rs (2)
  • render (30-163)
  • new (16-18)
crates/cli/src/tui/widgets/theme_switcher.rs (3)
  • render (22-109)
  • new (16-18)
  • centered_rect (113-131)
crates/cli/src/tui/app.rs (2)
  • default (258-260)
  • new (73-97)
crates/cli/src/tui/widgets/dashboard.rs (2)
crates/cli/src/tui/ui.rs (2)
  • render (11-70)
  • centered_rect (73-91)
crates/cli/src/tui/theme.rs (2)
  • default (307-309)
  • default (335-337)
crates/cli/src/tui/events.rs (8)
crates/cli/src/cli.rs (1)
  • new (88-90)
crates/cli/src/tui/app.rs (1)
  • new (73-97)
crates/cli/src/tui/widgets/dashboard.rs (1)
  • new (16-18)
crates/cli/src/tui/widgets/output_panel.rs (1)
  • new (16-18)
crates/cli/src/tui/widgets/repl_input.rs (1)
  • new (10-12)
crates/cli/src/tui/widgets/status_bar.rs (1)
  • new (16-18)
crates/cli/src/tui/widgets/theme_switcher.rs (1)
  • new (16-18)
crates/cli/src/tui/theme.rs (1)
  • next (313-318)
crates/cli/src/tui/widgets/repl_input.rs (2)
crates/cli/src/tui/app.rs (2)
  • new (73-97)
  • default (258-260)
crates/cli/src/tui/ui.rs (1)
  • render (11-70)
crates/cli/src/tui/widgets/status_bar.rs (1)
crates/cli/src/tui/app.rs (2)
  • new (73-97)
  • default (258-260)
crates/cli/src/tui/app.rs (6)
crates/cli/src/tui/widgets/dashboard.rs (1)
  • new (16-18)
crates/cli/src/tui/widgets/output_panel.rs (1)
  • new (16-18)
crates/cli/src/tui/widgets/repl_input.rs (1)
  • new (10-12)
crates/cli/src/tui/widgets/status_bar.rs (1)
  • new (16-18)
crates/cli/src/tui/widgets/theme_switcher.rs (1)
  • new (16-18)
crates/cli/src/tui/theme.rs (3)
  • default (307-309)
  • default (335-337)
  • all (291-304)
🪛 markdownlint-cli2 (0.18.1)
crates/cli/README.md

132-132: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


200-200: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


205-205: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (21)
crates/runtime/src/plugins/antiraid/httpserver/mod.rs (2)

411-415: LGTM! Proper cross-platform compilation fix.

The #[cfg(unix)] attribute correctly gates the catch-all error arm, ensuring the match is exhaustive on non-Unix platforms (where only the Tcp variant exists) and correct on Unix platforms (where both Unix and Tcp variants exist).


417-421: LGTM! Consistent cross-platform fix.

The #[cfg(unix)] attribute properly mirrors the fix applied to the addr getter, ensuring correct compilation across all platforms.

crates/cli/Cargo.toml (1)

24-35: LGTM - TUI dependencies are appropriate.

The selected Ratatui ecosystem libraries (crossterm, tui-textarea) and supporting crates for syntax highlighting (syntect), progress indication (indicatif), and text handling (unicode-width, unicode-segmentation, textwrap) are well-suited for building a terminal UI.

crates/cli/src/cli.rs (1)

310-319: LGTM - TUI entrypoint integration is clean.

The new Tui variant follows the existing pattern for other entrypoint actions with consistent verbose logging and appropriate error handling to stderr.

crates/cli/src/main.rs (2)

8-9: LGTM - Module declarations added correctly.


447-448: Behavior change: Default is now TUI instead of REPL.

This changes the default interactive mode from the classic REPL to the new TUI. Users who prefer the traditional REPL will need to use the /repl command within the TUI or explicitly request it. Consider documenting this in the README or changelog.

crates/cli/src/tui/mod.rs (1)

1-8: LGTM - Clean module structure.

The TUI module organization with separate concerns (app state, events, theme, UI rendering, widgets) follows good architectural principles.

crates/cli/src/tui/widgets/output_panel.rs (1)

20-36: LGTM - Scroll calculation logic is correct.

The visible lines calculation correctly implements a "tail-follow" behavior where the most recent output is shown by default, and increasing scroll_offset reveals older history.

crates/cli/src/tui/widgets/repl_input.rs (1)

15-63: LGTM - Widget implementation follows standard patterns.

Cloning the TextArea on each render is a common pattern with tui-textarea since the Widget trait consumes self. The conditional styling for dashboard vs REPL mode provides appropriate visual distinction.

crates/cli/src/tui/widgets/dashboard.rs (1)

36-47: LGTM - ASCII logo rendering is well-implemented.

The logo is properly styled with the theme's primary color and centered alignment.

crates/cli/src/tui/widgets/status_bar.rs (1)

1-27: LGTM!

The struct definition and get_mode_text helper are clean and well-organized. Mode mapping to icons and text labels is appropriate.

crates/cli/src/tui/ui.rs (1)

29-58: LGTM!

The conditional rendering logic for dashboard vs REPL mode is clear and handles the state transitions appropriately.

crates/cli/src/tui/widgets/mod.rs (1)

1-13: LGTM!

Clean module facade pattern with proper public re-exports for all TUI widgets.

crates/cli/src/tui_entrypoint.rs (1)

99-168: LGTM!

The command execution flow is well-structured with proper history management, timing capture, and error handling for Lua execution.

crates/cli/src/tui/events.rs (2)

29-45: Blocking poll in async context.

event::poll() and event::read() are blocking calls. While they work correctly here since the TUI runs on a dedicated thread context, be aware this blocks the current task. If you later need concurrent operations (e.g., background updates), consider using crossterm::event::EventStream with async support.


48-73: LGTM!

Key matching utilities are well-defined and cover common shortcuts. The helper functions provide a clean abstraction for key event handling.

crates/cli/src/tui/widgets/theme_switcher.rs (1)

74-91: LGTM!

Theme list rendering with per-item selection styling is correctly implemented.

crates/cli/src/tui/theme.rs (2)

340-370: LGTM!

The HSL to RGB conversion is correctly implemented following the standard algorithm.


372-393: LGTM!

Good test coverage for theme cycling behavior and verification that all themes are present.

crates/cli/src/tui/app.rs (2)

71-97: LGTM!

App constructor properly initializes all state with sensible defaults and adds a welcome message.


175-214: LGTM!

History navigation logic correctly handles edge cases (empty history, boundaries) and properly reconstructs the TextArea from historical commands.

Comment on lines +16 to +22
pub async fn run_tui(cli: &mut Cli) -> io::Result<()> {
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Terminal state not restored on error during setup or main loop.

If any operation fails between enable_raw_mode() and the cleanup block (lines 212-218), the terminal will be left in raw mode with the alternate screen active, making it unusable. This includes panics and early ? returns.

Consider using a guard pattern (drop guard or scopeguard crate) to ensure cleanup always runs.

🔎 Proposed fix using a cleanup helper
+fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>) -> io::Result<()> {
+    disable_raw_mode()?;
+    execute!(
+        terminal.backend_mut(),
+        LeaveAlternateScreen,
+        DisableMouseCapture
+    )?;
+    terminal.show_cursor()?;
+    Ok(())
+}

 pub async fn run_tui(cli: &mut Cli) -> io::Result<()> {
     // Setup terminal
     enable_raw_mode()?;
     let mut stdout = io::stdout();
-    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
+    let setup_result = execute!(stdout, EnterAlternateScreen, EnableMouseCapture);
+    if setup_result.is_err() {
+        let _ = disable_raw_mode();
+        return setup_result;
+    }
     let backend = CrosstermBackend::new(stdout);
     let mut terminal = Terminal::new(backend)?;

+    let result = run_tui_inner(cli, &mut terminal).await;
+    
+    // Always restore terminal
+    let _ = restore_terminal(&mut terminal);
+    result
+}
+
+async fn run_tui_inner(cli: &mut Cli, terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>) -> io::Result<()> {
     // ... main loop code ...

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +136 to +141
let build_status = " 🛠️ READY ";
let builder_badge_width = build_status.len() as u16 + 4; // prefix/suffix

if current_width + builder_badge_width < available_width {
let padding = available_width - current_width - builder_badge_width;
spans.push(Span::raw(" ".repeat(padding as usize)));
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

Incorrect width calculation for emoji-containing string.

build_status.len() returns byte length, not display width. The string " 🛠️ READY " contains multi-byte emoji characters, so .len() will overestimate the width (returns ~17 bytes vs ~12 display columns). This causes the right-aligned badge positioning to be incorrect.

Use unicode_width or Ratatui's Span::width() for consistent measurement.

🔎 Proposed fix
         // Right-aligned: Build Status (Badge style)
         let build_status = " 🛠️  READY ";
-        let builder_badge_width = build_status.len() as u16 + 4; // prefix/suffix
+        let build_status_span = Span::raw(build_status);
+        let builder_badge_width = build_status_span.width() as u16 + 4; // prefix/suffix
📝 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 build_status = " 🛠️ READY ";
let builder_badge_width = build_status.len() as u16 + 4; // prefix/suffix
if current_width + builder_badge_width < available_width {
let padding = available_width - current_width - builder_badge_width;
spans.push(Span::raw(" ".repeat(padding as usize)));
let build_status = " 🛠️ READY ";
let build_status_span = Span::raw(build_status);
let builder_badge_width = build_status_span.width() as u16 + 4; // prefix/suffix
if current_width + builder_badge_width < available_width {
let padding = available_width - current_width - builder_badge_width;
spans.push(Span::raw(" ".repeat(padding as usize)));
🤖 Prompt for AI Agents
In crates/cli/src/tui/widgets/status_bar.rs around lines 136-141, the code uses
build_status.len() to compute display width but len() returns byte length and
miscalculates strings with emoji; replace the byte-length calculation with a
display-width measurement (e.g., use
unicode_width::UnicodeWidthStr::width(build_status) and cast to u16) or use
Ratatui's Span::width() to compute the actual column width, update
builder_badge_width accordingly, and add the necessary use/import so the padding
calculation uses the display width rather than .len().

Comment on lines +29 to +30
// Clear the area before rendering to simulation floating
Clear.render(switcher_area, buf);
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

Minor typo.

"simulation" should be "simulate".

🔎 Proposed fix
-        // Clear the area before rendering to simulation floating
+        // Clear the area before rendering to simulate floating
📝 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
// Clear the area before rendering to simulation floating
Clear.render(switcher_area, buf);
// Clear the area before rendering to simulate floating
Clear.render(switcher_area, buf);
🤖 Prompt for AI Agents
In crates/cli/src/tui/widgets/theme_switcher.rs around lines 29-30, the inline
comment contains a typo: "simulation" should be "simulate"; update the comment
to read "Clear the area before rendering to simulate floating" (or similar) so
the verb is correct while leaving the surrounding code unchanged.

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

♻️ Duplicate comments (1)
crates/cli/src/tui_entrypoint.rs (1)

16-22: Terminal state not restored on error during setup or main loop.

This concern from a previous review remains unaddressed. If any operation fails between enable_raw_mode() (line 18) and the cleanup block (lines 242-248), the terminal will be left in raw mode with the alternate screen active. Consider using a drop guard or the scopeguard crate to ensure cleanup always runs.

🧹 Nitpick comments (5)
crates/cli/src/tui/widgets/quit_modal.rs (1)

58-76: Extract centered_rect into a shared utility module.

This helper function is duplicated verbatim in help_modal.rs, about_modal.rs, theme_switcher.rs, and ui.rs. Consider extracting it to a shared module (e.g., crates/cli/src/tui/utils.rs or crates/cli/src/tui/widgets/mod.rs) to reduce duplication and ensure consistency.

crates/cli/src/tui_entrypoint.rs (1)

189-198: Use is_some() instead of if let Some(_).

The pattern if let Some(_) = ... is less idiomatic than if ... .is_some() when the value isn't used.

🔎 Suggested fix
-                        KeyCode::Tab => {
-                            // Autocomplete on Tab
-                            if let Some(_) = app.get_active_suggestion() {
-                                app.autocomplete();
-                            } else {
+                        KeyCode::Tab => {
+                            // Autocomplete on Tab
+                            if app.get_active_suggestion().is_some() {
+                                app.autocomplete();
+                            } else {
crates/cli/src/tui/ui.rs (1)

25-27: Remove duplicate comment.

The comment "Render status bar" appears twice (lines 25-26 and 27).

🔎 Suggested fix
-
-    // Render status bar
-    // Render status bar at the bottom
+    // Render status bar at the bottom
crates/cli/src/tui/widgets/repl_input.rs (1)

36-36: Unused variable prompt.

The prompt variable is defined but only used in the interactive block's title. In the non-interactive branch, it's never referenced.

🔎 Suggested fix
-        let prompt = if self.app.is_interactive { "> " } else { "  " };
-
         let block = if !self.app.is_interactive {
             // Dashboard Style: Search bar look
             ratatui::widgets::Block::default()
@@ -46,6 +44,7 @@
                 // .title(" Ask anything... or type /help ") // Removed for cleaner look
         } else {
             // REPL Style: Minimal top separator
+            let prompt = "> ";
             ratatui::widgets::Block::default()
                 .borders(ratatui::widgets::Borders::TOP)
                 .border_style(Style::default().fg(theme.border))
crates/cli/src/tui/app.rs (1)

183-186: Consider using VecDeque for command_history to avoid O(n) removal.

Vec::remove(0) is O(n) because it shifts all elements. With MAX_COMMAND_HISTORY = 1000, this could have noticeable overhead when the history is full. Using VecDeque would make pop_front() O(1), matching the pattern already used for output.

🔎 Suggested changes

In the struct definition:

-    /// Command history
-    pub command_history: Vec<String>,
+    /// Command history
+    pub command_history: VecDeque<String>,

In save_to_history:

         if !input.trim().is_empty() {
             self.command_history.push(input);
             if self.command_history.len() > MAX_COMMAND_HISTORY {
-                self.command_history.remove(0);
+                self.command_history.pop_front();
             }
         }

Note: This would require updating index-based access in history_prev and history_next to use get() or iterator patterns.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0de6d8 and fcf59d7.

📒 Files selected for processing (9)
  • crates/cli/src/tui/app.rs
  • crates/cli/src/tui/ui.rs
  • crates/cli/src/tui/widgets/about_modal.rs
  • crates/cli/src/tui/widgets/help_modal.rs
  • crates/cli/src/tui/widgets/mod.rs
  • crates/cli/src/tui/widgets/quit_modal.rs
  • crates/cli/src/tui/widgets/repl_input.rs
  • crates/cli/src/tui_entrypoint.rs
  • docs/primitives.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/cli/src/tui/widgets/mod.rs
🧰 Additional context used
🧬 Code graph analysis (4)
crates/cli/src/tui/widgets/about_modal.rs (3)
crates/cli/src/tui/app.rs (2)
  • new (86-113)
  • default (341-343)
crates/cli/src/tui/widgets/quit_modal.rs (3)
  • new (15-17)
  • render (21-55)
  • centered_rect (58-76)
crates/cli/src/tui/ui.rs (2)
  • render (11-88)
  • centered_rect (91-109)
crates/cli/src/tui/ui.rs (3)
crates/cli/src/tui/widgets/about_modal.rs (1)
  • centered_rect (58-76)
crates/cli/src/tui/widgets/help_modal.rs (1)
  • centered_rect (79-97)
crates/cli/src/tui/widgets/theme_switcher.rs (1)
  • centered_rect (113-131)
crates/cli/src/tui_entrypoint.rs (9)
crates/cli/src/tui/app.rs (1)
  • new (86-113)
crates/cli/src/tui/widgets/repl_input.rs (2)
  • new (10-12)
  • render (16-105)
crates/cli/src/tui/events.rs (5)
  • new (24-26)
  • is_quit (54-57)
  • is_help (71-73)
  • is_theme_switch (60-62)
  • is_clear (66-68)
crates/cli/src/cli.rs (1)
  • new (88-90)
crates/cli/src/tui/widgets/dashboard.rs (2)
  • new (16-18)
  • render (22-113)
crates/cli/src/tui/widgets/output_panel.rs (2)
  • new (16-18)
  • render (40-158)
crates/cli/src/tui/widgets/status_bar.rs (2)
  • new (16-18)
  • render (30-163)
crates/cli/src/tui/widgets/theme_switcher.rs (2)
  • new (16-18)
  • render (22-109)
crates/cli/src/tui/ui.rs (1)
  • render (11-88)
crates/cli/src/tui/widgets/help_modal.rs (3)
crates/cli/src/tui/app.rs (2)
  • new (86-113)
  • default (341-343)
crates/cli/src/tui/widgets/quit_modal.rs (3)
  • new (15-17)
  • render (21-55)
  • centered_rect (58-76)
crates/cli/src/tui/ui.rs (2)
  • render (11-88)
  • centered_rect (91-109)
🪛 markdownlint-cli2 (0.18.1)
docs/primitives.md

522-522: Hard tabs
Column: 1

(MD010, no-hard-tabs)


523-523: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (13)
docs/primitives.md (2)

1-1: Header capitalization looks good.

Standard title-case formatting for the documentation heading.

Also applies to: 3-3


722-729: Documentation section for HTTPClient is well-structured.

The new documentation section follows the established pattern for plugin fields, with correct module reference and proper placement within the TemplateContext documentation.

crates/cli/src/tui/widgets/quit_modal.rs (1)

10-56: Modal implementation looks good.

The QuitModal follows a consistent pattern with other modals: themed styling, centered rendering via Clear + Block, and clear user instructions. The destructive color scheme appropriately signals the quit action.

crates/cli/src/tui_entrypoint.rs (1)

35-239: Main event loop is well-structured.

The event loop correctly handles modal state transitions, input processing, and command execution. The modal priority order (theme switcher → quit → help/about → normal input) ensures proper UX. The async script execution with timing metrics is a nice touch.

crates/cli/src/tui/widgets/help_modal.rs (2)

10-76: Help modal implementation is clean and consistent.

The widget follows the established modal pattern and provides clear, styled help content. The section headers with empty values for styling differentiation is a reasonable approach.


79-97: Duplicate centered_rect helper.

Already flagged in quit_modal.rs - this should be extracted to a shared location.

crates/cli/src/tui/ui.rs (1)

11-88: Well-organized render function with proper modal layering.

The render function correctly handles the different UI modes (dashboard vs REPL) and layers modals in the proper z-order. The theme switcher renders last, ensuring it's always on top.

crates/cli/src/tui/widgets/repl_input.rs (2)

64-104: Suggestion overlay has limitations acknowledged in comments.

The cursor position calculation (line 73) assumes single-line input without scrolling, which is noted in the comments. This is acceptable for slash commands but may need refinement if multi-line input with suggestions is needed later.


15-62: Widget implementation follows ratatui patterns correctly.

The textarea cloning and styling application is the standard approach when working with tui-textarea in a Widget context. The dual block styles for dashboard vs REPL modes provide good visual distinction.

crates/cli/src/tui/widgets/about_modal.rs (1)

10-56: About modal implementation is clean.

Good use of env!("CARGO_PKG_VERSION") to pull the version at compile time. The styling is consistent with other modals.

crates/cli/src/tui/app.rs (3)

272-303: Modal toggle methods correctly ensure mutual exclusivity.

The toggle methods close other modals when opening a new one, preventing confusing overlapping states. This is a good UX pattern.


84-113: App initialization is comprehensive and well-documented.

The new() method properly initializes all state with sensible defaults. The placeholder text and welcome message provide good onboarding UX.


305-337: Autocomplete logic is correct and safe.

get_active_suggestion correctly returns None for non-slash inputs and only suggests when the input is a strict prefix. The autocomplete method adds a trailing space for convenience, which is a nice UX touch.

Comment on lines +60 to +64
// If help is shown, overlay it (using Dashboard/Help widget)
if app.show_help {
let help_area = centered_rect(80, 80, frame.area());
frame.render_widget(Dashboard::new(theme.clone()), help_area);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for all assignments to show_help
rg -n 'show_help\s*=' --type rust

Repository: Anti-Raid/khronos

Length of output: 134


🏁 Script executed:

# Search for show_help in the codebase to understand its usage
rg -n 'show_help' --type rust -B 2 -A 2

Repository: Anti-Raid/khronos

Length of output: 5073


🏁 Script executed:

# Find and examine app.rs to see the initialization
fd -n 'app.rs' --type f | head -5

Repository: Anti-Raid/khronos

Length of output: 291


🏁 Script executed:

# Search for where toggle_help_modal or Help key is handled
rg -n 'toggle_help_modal|KeyCode::F\(1\)|Help' --type rust -B 2 -A 2

Repository: Anti-Raid/khronos

Length of output: 10144


🏁 Script executed:

# Also check tui_entrypoint.rs more thoroughly to see the full input handling
sed -n '70,150p' crates/cli/src/tui_entrypoint.rs

Repository: Anti-Raid/khronos

Length of output: 4337


Remove the dead show_help code path and field.

The show_help field and the render logic at lines 60-64 are unused. The toggle method was explicitly removed in favor of toggle_help_modal() (see app.rs comment at line 232), and show_help is never set to true anywhere in the codebase. All help functionality is properly implemented through show_help_modal, which is toggled by F1 (and the /help command) and renders a proper HelpModal. Remove the field from the App struct, delete the render block at lines 60-64, and clean up the conditional guards (!app.show_help) in ui.rs and tui_entrypoint.rs.

🤖 Prompt for AI Agents
In crates/cli/src/tui/ui.rs around lines 60-64, remove the dead show_help render
path and the now-unused show_help boolean: delete the conditional block that
centers and renders Dashboard when app.show_help is true, remove any remaining
uses/guards of app.show_help in this file and in
crates/cli/src/tui/tui_entrypoint.rs, and also remove the show_help field from
the App struct (wherever it is declared). After removal, update any conditionals
that used !app.show_help to only use the current help-modal flag
(show_help_modal) and run a build to fix any remaining references or
unused-import warnings.

Comment on lines +522 to +524
--- HTTP Client plugin
HTTPClient: httpclientP.Plugin,
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

Fix hard tabs in HTTPClient field declaration.

Lines 522–523 use hard tabs instead of spaces, violating the markdown linting rule (MD010). Replace with spaces to match the indentation style of surrounding fields.

🔎 Proposed fix
-	--- HTTP Client plugin
-	HTTPClient: httpclientP.Plugin,
+    --- HTTP Client plugin
+    HTTPClient: httpclientP.Plugin,

The field addition itself is correct and follows the established plugin pattern in TemplateContext.

📝 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
--- HTTP Client plugin
HTTPClient: httpclientP.Plugin,
--- HTTP Client plugin
HTTPClient: httpclientP.Plugin,
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

522-522: Hard tabs
Column: 1

(MD010, no-hard-tabs)


523-523: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In docs/primitives.md around lines 522 to 524, the HTTPClient field declaration
uses hard tabs for indentation which violates MD010; replace the hard tabs with
spaces so the line indentation matches surrounding fields (use the same number
of spaces used elsewhere for field alignment) and save the file.

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.

3 participants