fix(tui): Reset committed_line_count on markdown line count regression#353
Closed
fix(tui): Reset committed_line_count on markdown line count regression#353
Conversation
Created a basic Rust binary package that prints Hello, world! Includes standard cargo project structure with Cargo.toml and src/main.rs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Adds automated checks for: - Code formatting (cargo fmt --check) - Linting (cargo clippy -D warnings) - Tests (cargo test --verbose) Runs on push to main and all pull requests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- pr-ci.yml runs on pull_request events only - main-ci.yml runs on push to main only - Removes unified rust-ci.yml workflow This allows future differentiation between PR checks (fast feedback) and main branch checks (comprehensive validation + deployment) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add Rust Hello World package
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
## Summary - Converted nori-cli from simple println to ratatui-based TUI - Added dependencies: ratatui 0.29.0, crossterm 0.28.1, color-eyre 0.6.3 - Implemented three-phase pattern: init → event loop → restore - Displays stylized "Hello, World!" with green/bold and cyan/italic text ## Test Plan - [x] cargo build succeeds - [x] cargo test passes (0 tests) - [x] cargo fmt passes - [x] cargo clippy passes with -D warnings - [ ] Manual test: Run application in terminal to verify TUI displays correctly - [ ] Verify application exits cleanly on key press 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Implement a terminal user interface (TUI) that routes user prompts to either Claude Code or GPT Codex CLI implementations as subprocesses, streaming their responses back in real-time. **Key Changes:** - Built TUI with ratatui using The Elm Architecture (TEA) pattern for state management - Implemented async subprocess spawning with `tokio::process::Command` - Added JSONL parsing for streaming events (agent_message, file_change, command_execution) - Created trait-based backend abstraction for easy extensibility - Integrated tui-textarea for multi-line prompt input **Architecture:** - **Backends**: Spawn `claude --print --output-format stream-json` or `codex exec --json` as child processes - **Event Loop**: `tokio::select!` multiplexes keyboard events, subprocess stdout, and rendering - **State Machine**: Clean state transitions between Selection → Input → Streaming modes ## Test Plan - [x] All unit tests pass (state machine, subprocess spawning, JSON parsing) - [x] `cargo fmt` applied - [x] `cargo clippy` passes with `-D warnings` - [x] Manual testing with mock backend (printf JSONL output) - [ ] Manual testing with real Claude Code CLI (requires API key) - [ ] Manual testing with real Codex CLI (requires API key) ## Technical Details **Files Added:** - `src/app.rs` - TEA state machine (Model, Message, update) - `src/ui.rs` - Rendering for 3 modes (selection, input, streaming) - `src/backends/` - AgentBackend trait + implementations (claude, codex, mock) - `tests/` - State machine and subprocess integration tests - `README.md` - Comprehensive usage and architecture documentation **Dependencies:** - ratatui 0.29, tokio (full), tui-textarea 0.7, serde_json, crossterm with event-stream 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Fixed a race condition where the event handler task maintained its own local copy of `current_mode` that would become desynchronized with the actual Model state. - Event handler now receives mode updates via dedicated channel from main loop - Establishes `Model.current_mode` as single source of truth - Prevents key events from being filtered based on stale state ## What Was Broken After submitting a prompt and receiving a streamed response: - User would return to the Selection screen - Arrow keys and other input would not work - Only 'q' for quit would respond ## Root Cause The event handler's local `current_mode` variable would update immediately when messages were sent, but the actual `Model.current_mode` wouldn't update until the message was processed in the main loop. This race condition meant the event handler was filtering key events based on outdated state. ## The Fix Created a bidirectional communication pattern: - Added `mode_tx`/`mode_rx` channel for mode synchronization - Main loop sends current mode to event handler after each state change - Event handler updates its local mode only from the channel - No more local state inference from messages ## Test Plan - [x] Added `test_post_stream_state_handling` test to verify state transitions - [x] All existing tests pass (5 tests) - [x] Manual testing: Selected agent → submitted prompt → stream completed → verified arrow keys work correctly - [x] Cargo fmt and clippy pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
## Summary Transformed JSONL streaming output into natural, styled chat interface by introducing an intermediate conversation type that separates backend-specific parsing from display rendering. - Created `ConversationEvent` enum to represent different event types (assistant messages, system events, results, stderr) - Implemented `parse_jsonl_event()` to parse actual Claude CLI JSONL format - Implemented `render_event()` to convert events into styled Ratatui Lines with visual distinction - Refactored data flow: Backend JSONL → ConversationEvent (parsing) → Line (rendering) → UI - Updated all tests and added comprehensive edge case coverage ## Test Plan - [x] All 17 tests pass (12 conversation rendering tests + 3 state machine + 2 subprocess) - [x] cargo fmt and clippy pass with no issues - [x] Verified actual Claude CLI event format through testing - [x] Manual smoke test confirms build succeeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Refactor the TUI from a three-screen mode-based interface to a chat-style interface where conversation history is always visible and the agent router is accessed via an overlay. ### Key Changes - **Chat-style UI**: Single view with messages above and input at bottom - Title shows currently selected agent - Messages display full conversation history (user + assistant) - Input field (tui-textarea) always at bottom - Instructions adapt to context - **Agent Router Overlay**: Toggle with Alt+A - Renders centered overlay using Clear widget - Navigate with arrow keys, Enter to select, Esc to close - Does not disrupt conversation flow - **Conversation History**: Preserved across interactions - Added `UserMessage` event type with cyan `[user]` prefix - Messages accumulate in `response_events` - Terminal scrolling handles long conversations ### Implementation - Added `show_agent_router` boolean to track overlay visibility - Refactored state management: navigation/input now gate on overlay state - Rewrote rendering: `render_chat()` base + `render_agent_router_overlay()` - Updated all tests to reflect new architecture (14 tests passing) ## Test Plan - [x] All 14 tests passing (7 conversation + 5 state machine + 2 subprocess) - [x] cargo fmt and cargo clippy clean - [ ] Manual testing of chat flow - [ ] Manual testing of overlay toggle (Alt+A) - [ ] Manual testing of agent selection - [ ] Verify conversation history persists - [ ] Verify terminal scrolling works for long conversations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Replace keyboard shortcuts (Alt+A, q) with an extensible slash command system that uses a trait-based registry pattern for command routing. - Add `/exit` command to quit the application - Add `/switch-model` command to open the agent router overlay - Remove Alt+A and 'q' keyboard shortcuts - Create CommandHandler trait and CommandRegistry for extensibility - Support command parsing with error messages for unknown commands ## Architecture The slash command system uses: - **CommandHandler trait**: Standard interface for all commands - **CommandRegistry**: HashMap-based routing with auto-registration - **Individual command files**: exit.rs, switch_model.rs for modularity - **Parse function**: Detects "/" prefix and extracts command names Adding new commands requires implementing CommandHandler and registering in CommandRegistry::default(). ## Test Plan - [x] All 22 existing tests pass - [x] 8 new tests for slash command parsing and execution - [x] Commands execute successfully (exit, switch-model) - [x] Unknown commands show helpful error messages - [x] Regular input (non-commands) works as before - [x] cargo fmt and cargo clippy pass with -D warnings ## Files Changed - `src/commands/mod.rs` - CommandHandler trait, registry, parsing - `src/commands/exit.rs` - /exit implementation - `src/commands/switch_model.rs` - /switch-model implementation - `src/main.rs` - Integration with main loop - `src/ui.rs` - Updated instructions - `tests/slash_commands_test.rs` - Comprehensive test coverage - `README.md`, `src/commands/docs.md` - Documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Implements backend installation checking and user prompts when backends are missing. When a user attempts to use an unavailable backend (claude, codex), the system detects this and guides them through installation. ## Implementation Details ### Core Features - **Backend availability detection**: Uses `which` crate to check if commands exist in PATH - **Proactive checking**: Validates backend availability before attempting to spawn - **User prompts**: Modal overlay with options to open installation page or cancel - **Visual indication**: Agent router shows "[Not Installed]" for unavailable backends - **Improved error handling**: Distinguishes NotFound errors from other spawn failures ### Architecture Changes - Extended `AgentBackend` trait with `command_name()` and `install_url()` methods - Added `is_available()` function for PATH checking - New state in Model: `show_install_prompt`, `install_prompt_backend`, `install_prompt_url`, `install_prompt_choice` - New messages: `ShowInstallPrompt`, `NavigateInstallChoice`, `ConfirmInstall`, `CancelInstall` - Install prompt overlay rendered on top of all other UI elements ### User Experience 1. User selects backend in agent router 2. Unavailable backends display with "[Not Installed]" suffix in dark gray 3. On prompt submission, system checks if backend is available 4. If not: install prompt appears with backend name and options 5. User can navigate with arrow keys, confirm with Enter, cancel with Esc 6. "Open Installation Page" opens the install URL in default browser via `opener` crate ### Dependencies Added - `which` 7.0: Cross-platform PATH lookup - `opener` 0.7: Open URLs in default browser ## Test Plan All 19 tests passing: - ✅ Backend availability detection (installed/missing commands) - ✅ Backend metadata (command_name, install_url) - ✅ Install prompt state transitions (show, navigate, confirm, cancel) - ✅ Existing functionality (streaming, state machine, rendering) Manually tested: - Install prompt displays correctly with proper styling - Arrow keys navigate between options - Enter opens browser when "Open Installation Page" selected - Esc cancels prompt - Unavailable backends show visual indication in agent router ## Breaking Changes None. This is a purely additive feature that enhances the existing UX when backends are missing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Implements user-initiated stream cancellation via Escape key during streaming mode. When the user presses Escape while a backend subprocess is streaming, the stream is cancelled, the process is cleaned up, and "Interrupted" displays in red in the conversation history. - Add CancellationToken-based cancellation coordinated between UI and streaming task - Update AgentBackend trait to accept cancellation token parameter - Implement tokio::select! multiplexing in spawn_and_stream for cancellation - Add CancelStream message and StreamCancelled event with red "Interrupted" rendering - Track active streams via Model.current_stream_token field - Update all backends (Claude, Codex, Mock) to support cancellation - Add comprehensive test coverage for cancellation behavior Closes the limitation documented in docs: "Process cancellation not implemented - subprocess continues running even if user presses Esc" ## Test Plan - [x] Unit tests pass (28 tests total, including new test_cancel_stream_during_streaming) - [x] cargo fmt applied successfully - [x] cargo clippy passes with no warnings - [ ] Manual testing: Start stream, press Escape, verify "Interrupted" appears and process terminates - [ ] Manual testing: Verify subprocess is killed (ps aux | grep claude after cancellation) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Fixed cursor positioning issue when exiting the TUI with `/exit` command. The cursor now appears at the bottom of the painted TUI area instead of remaining in the middle. **Changes:** - Added `MoveToNextLine(1)` cursor command in terminal cleanup sequence - Cursor moves down one line before `disable_raw_mode()` is called - Shell prompt now appears cleanly below TUI content ## Test Plan - [x] All 28 existing automated tests pass - [ ] Manual test: Run `cargo run`, type `/exit`, verify cursor at bottom - [ ] Manual test: Verify shell prompt appears below TUI content, not in middle - [x] cargo fmt passes - [x] cargo clippy passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Added text wrapping for long conversation event lines in terminal scrollback. - Implements word-level wrapping with character-level fallback for very long words (JSON, URLs) - Preserves span styling across wrapped lines - Uses unicode-width for accurate multi-byte character width calculation ## Changes - `src/main.rs`: Added `wrap_text_to_width()` function with word and character-level wrapping logic - `src/main.rs`: Modified `StreamEvent` handling to wrap before `insert_before()` - `Cargo.toml`: Added `unicode-width = "0.2"` dependency ## Technical Details The solution wraps text manually before calling `insert_before()` because: - `insert_before()` only supports single-line insertion - Ratatui's `Paragraph::wrap()` applies during render, after `insert_before()` captures - This ensures no text is lost and all content wraps properly within terminal width ## Test Plan - [x] All 28 existing tests pass - [x] Manual testing with long responses confirms proper wrapping - [x] Long JSON strings (`[unknown]` events) wrap character-by-character - [x] Regular text wraps at word boundaries - [x] No compiler warnings 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Clear the prompt textarea immediately when the user submits a message, providing instant visual feedback and enabling immediate composition of follow-up messages. This aligns with modern chat UX patterns. - Textarea clears immediately in `SubmitInput` handler after capturing user text - Removed redundant clearing from `StreamComplete` and `CancelStream` handlers - Added comprehensive test suite (6 new tests) covering immediate clearing behavior and edge cases - Updated existing test to properly set up state via `SubmitInput` instead of manual manipulation ## Test Plan - [x] All 34 tests pass (including 6 new textarea clearing tests) - [x] cargo fmt (no changes needed) - [x] cargo clippy (no warnings) - [x] Manual testing: textarea clears immediately on submit, can type follow-up while streaming - [ ] Verify CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Fixed bug where user messages disappeared after submission instead of appearing in conversation history - User messages are now rendered to terminal scrollback using `insert_before()` following the same pattern as StreamEvent messages - Updated documentation to reflect the rendering flow ## Test Plan - [x] All 34 existing tests pass - [x] rustfmt applied - [x] clippy passed with no warnings - [x] Manual testing: user messages now appear in scrollback with [user] prefix before agent responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Implement two-stage Ctrl-C keyboard interrupt handling for quick textarea clearing and application exit. - First Ctrl-C press clears textarea and displays hint message - Second Ctrl-C within 2 seconds exits application (like /exit) - After timeout, behavior resets to first-press pattern - Works globally - even with overlays or install prompts open ## Implementation Details - Added `Message::ClearTextarea` variant and `last_ctrl_c_time` state field - Timeout logic in `Model::update()` distinguishes first vs second press - State synced to event handler via mpsc channel - Ctrl-C detection prioritized before all other key handling - Quit triggered by monitoring timestamp transition Some → None - Visual feedback via existing `error_message` field ## Test Plan - [x] All 37 existing tests pass - [x] 3 new unit tests for Ctrl-C behavior: - First press clears textarea and shows hint - Second press within timeout signals quit - Press after timeout resets to first press - [x] cargo fmt applied - [x] cargo clippy passes with no warnings 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Fixed cursor positioning on exit that was broken by recent text wrapping and user message scrollback features. The cursor now appears cleanly below the TUI content instead of in the middle or at the text area. **Root cause:** Multiple `insert_before()` calls from text wrapping (f7a191e) and user message scrollback (624511d) caused unpredictable cursor positions. The previous relative cursor movement approach (`MoveToNextLine(8)`) no longer worked correctly because `ratatui::restore()` was resetting the cursor to the last draw position. **Solution:** Switched to absolute cursor positioning by: - Capturing viewport starting position before terminal initialization - Using `MoveTo(0, viewport_start + 8)` for absolute positioning after restore - Ensures cursor is always exactly 8 lines below viewport start, regardless of rendering state ## Changes - `src/main.rs:4`: Import `MoveTo` instead of `MoveToNextLine` - `src/main.rs:20`: Capture `viewport_start_row` before creating terminal - `src/main.rs:36`: Use absolute positioning with `MoveTo(0, viewport_start_row + 8)` ## Test Plan - [x] All 34 existing tests pass - [x] cargo fmt passes - [x] cargo clippy passes (no warnings) - [x] Manual testing: cursor appears below TUI on `/exit` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Move agent info from bordered title block to plain text below prompt input - Remove 'Prompt' label from input border - Remove 'Enter: send' from instructions line - Adjust layout constraints to accommodate new positioning ## Test Plan - [x] All 38 existing tests pass - [x] cargo fmt, clippy, and check pass - [x] Manual verification of UI changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
## Summary Replaced centered modal overlays with fullscreen mode switching to fix rendering issues in inline terminal viewports. - Convert agent selection and install prompt from overlay modals to fullscreen UIs - Remove percentage-based `centered_rect()` positioning that failed in constrained inline viewports - Use flexible layout constraints (`Constraint::Min`) that adapt to varying viewport heights - Add text wrapping to install prompt messages for automatic sizing ## Test Plan - [x] All 38 existing tests pass - [x] cargo fmt and cargo clippy pass - [ ] Manual testing in inline viewport (Claude Code inline mode) - [ ] Manual testing in fullscreen mode - [ ] Verify agent selection UI renders correctly - [ ] Verify install prompt UI renders correctly - [ ] Verify navigation works (↑/↓/Enter/Esc) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Remove absolute cursor positioning that became invalid after terminal scrolling - Use relative positioning (`MoveToColumn(0)`) after `ratatui::restore()` - Add screen clearing (`Clear::FromCursorDown`) to remove visual artifacts - Swap cleanup order: `ratatui::restore()` before `disable_raw_mode()` This fixes the issue where the cursor would appear in the middle of the screen after exiting the TUI, which occurred because the absolute positioning calculation didn't account for terminal scrolling that happened as content was added to the scrollback buffer via `insert_before()`. ## Test Plan Manual testing performed: - [x] Exit with few messages (3-5) - cursor at bottom ✓ - [x] Exit with many messages (20+) - cursor at bottom ✓ - [x] Exit via `/exit` command - cursor at bottom ✓ - [x] Exit via Ctrl-C twice - cursor at bottom ✓ - [x] All 34 automated tests pass ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Remove duplicate import of `execute` macro (was imported on both line 3 and line 5) - Remove unused `MoveTo` import leftover from commit 8d6e636 - Remove unused `viewport_start_row` variable leftover from refactoring in commit fec6099 ## Root Cause The duplicate `execute` import was introduced in commit fec6099 when adding the import on line 3, but line 5 already had it from commit 8d6e636. The `viewport_start_row` variable was added in commit 8d6e636 for absolute cursor positioning, but its usage was removed in commit fec6099 when switching to relative positioning with `MoveToColumn`. ## Test Plan - [x] `cargo build` compiles with 0 errors, 0 warnings - [x] All 38 tests pass (`cargo test`) - [x] `cargo clippy` passes with no warnings - [x] `cargo fmt` applied 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
## Summary Implemented autocomplete dropdown for slash commands with real-time filtering and keyboard navigation. - Autocomplete appears when "/" is typed at the start of input - Filters commands as you type (case-insensitive prefix matching) - Navigate with Up/Down/j/k arrow keys - Select with Tab or Enter - Dismiss with Escape - Dropdown positioned below prompt textarea ## Implementation - Added `get_all_command_names()` to CommandRegistry - Created `filter_commands()` for prefix-based filtering - Built autocomplete module with state management - Extended Model with autocomplete state fields - Added 5 new messages for autocomplete navigation - Integrated keyboard event handling in TEA pattern - Added dropdown rendering using existing overlay pattern ## Testing - 48 tests passing (5 new autocomplete tests) - All existing functionality preserved - Follows TDD methodology throughout ## Test Plan - [x] Type "/" - dropdown appears with all commands - [x] Type "/e" - filters to "exit" - [x] Type "/sw" - filters to "switch-model" - [x] Press Up/Down - navigates through filtered list - [x] Press Tab/Enter - completes selected command - [x] Press Escape - closes dropdown - [x] All existing tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Implemented dynamic prompt window resizing so the textarea grows vertically as users type more lines. - Added `calculate_textarea_height()` function that calculates height based on line count and wrapping - Accounts for text wrapping when lines exceed terminal width using unicode-width - Enforces minimum height (3 lines) and maximum height (10 lines) bounds - Updated viewport from Inline(14) to Inline(20) to accommodate larger prompts - Added comprehensive tests for minimum height, multi-line, wrapping, and maximum bounds ## Test Plan - [x] All 52 existing tests pass - [x] Added 4 new tests for dynamic height calculation - [x] Clippy passes with no warnings - [x] Manual testing: typing single lines, multi-line input, very long lines, and maximum height scenarios Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
## Summary - Removed underline styling from the TextArea prompt input field - Created `create_textarea()` helper function for consistent styling - Updated all 6 TextArea creation sites to use the new styling 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Test Plan - [x] All 48 tests pass - [x] cargo fmt passes - [x] cargo clippy passes with no warnings - [x] Visual verification: underline removed from prompt input - [x] Cursor remains visible with reversed-color styling - [x] All TextArea operations work correctly (submit, clear, autocomplete) Share Claude Code with your team: https://claude.com/claude-code Co-authored-by: Claude <noreply@anthropic.com>
…ation (#42) ## Summary - Replaced external `tui-textarea` crate with internal `tui_components::textarea` implementation - Updated all API calls to use new TextArea interface (.text(), .handle_key(), .is_empty()) - Consolidated dependencies within tui-components library for better component consistency ## Test Plan - [x] All 87 tests pass - [x] Cargo clippy clean (no warnings) - [x] Cargo fmt applied - [x] Manual verification of TextArea functionality - [x] Documentation updated for new API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
Add background, padding, prefix symbol, and border support to TextArea: - Background styling with customizable colors - Independent padding control (top/bottom/left/right) - Optional prefix symbol (›, >, •, ▸) vertically centered on left - Optional borders using ratatui Block widget Updated UI to use styled textarea: - Gray background for visual distinction - › prefix symbol for input affordance - Removed external Block wrapper (TextArea now self-contained) - Height calculation accounts for padding and prefix width All 27 tests passing including new snapshot tests for 4 style variations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…es (#44) ## Summary This PR adds command-line argument parsing to nori-cli, allowing users to specify the agent and initial message from the command line. ### Changes - **Add clap dependency**: Added clap v4 with derive feature for CLI argument parsing - **New CLI module**: Created `src/cli.rs` with `Cli` struct and agent name mapping functions - **Main function updates**: Modified `main.rs` to parse CLI args, validate agent names, and read from stdin when piped - **Run app updates**: Updated `run_app` to accept optional `agent_index` and `initial_message` parameters - **Comprehensive tests**: Added `tests/cli_args_test.rs` with tests for all CLI parsing scenarios - **Documentation updates**: Updated docs in both `src/docs.md` and `tests/docs.md` ### Usage Examples ```bash # Use specific agent nori-cli --agent claude # Use specific agent with initial message nori-cli --agent codex "Hello world" # Pipe input as initial message echo "Hello from stdin" | nori-cli # Short flag for agent nori-cli -a mock "Test message" ``` ### Features - Agent selection via `--agent` or `-a` flag (claude, codex, claudecode, mock) - Initial message as positional argument or via stdin piping - Case-insensitive agent name matching - Input validation with helpful error messages - CLI arguments take precedence over stdin when both provided
## Summary - Add comprehensive blackbox TUI tests with snapshot verification to ensure the terminal interface renders correctly across different input scenarios including initial state, user input, text wrapping, empty submission handling, unicode support, and multiline input
## Summary This PR refactors the CLI app to use the SelectionList component from tui-components for both the autocomplete experience and agent selection dropdown, providing cleaner and more consistent UI styling. ## Changes - **Model Updates**: Replace manual List state management with SelectionList instances - **UI Rendering**: Use SelectionList for both autocomplete and agent selection rendering - **Event Handling**: Update navigation and selection logic to work with SelectionList API - **Autocomplete Logic**: Populate SelectionList with properly formatted SelectionItems - **Tests**: Update test assertions to work with the new SelectionList API ## Benefits - Consistent styling and behavior across selection interfaces - Better keyboard navigation and visual feedback - Reduced code duplication and improved maintainability - Easier to extend with additional features like search filtering ## Testing - All existing autocomplete tests pass - Application builds and runs successfully - Manual testing confirms proper selection behavior
## Summary 🤖 Generated with [Nori](https://www.npmjs.com/package/nori-ai) Modernizes TUI code to consistently use `tui_components` library APIs, replacing custom implementations with standardized components. ### Key Changes - **TextArea Height**: Replace custom `calculate_textarea_height()` function (36 lines) with `TextArea::desired_height()` API - **Loading Animation**: Remove legacy spinner code, always use `Shimmer` component - **Code Cleanup**: Delete `use_codex_components` and `loading_frame` fields that toggled between implementations ### Benefits - **90 lines removed** (net code reduction) - **Single code path** for loading animations (no more conditionals) - **Better consistency** with tui_components patterns throughout codebase - **Reduced maintenance** by eliminating duplicate height calculation logic ## Test Plan - [x] All 100+ existing tests pass - [x] Added `test_shimmer_renders_during_streaming()` with snapshot verification - [x] Updated `dynamic_textarea_height_test.rs` to test TextArea's built-in method - [x] Verified UI behavior unchanged from user perspective - [x] Clippy and rustfmt checks pass - [x] Build succeeds without warnings ## Notes Task 4 (converting install prompt to SelectionList) was deferred as Tasks 1-2 provide the core value and Task 4 would require extensive additional changes across multiple handlers and UI code. Share Nori with your team: https://www.npmjs.com/package/nori-ai Co-authored-by: Claude <noreply@anthropic.com>
## Summary This PR introduces a new inline streaming system for ACP agents that displays assistant messages incrementally with proper word wrapping before committing them to the scrollback buffer. ### Key Changes - **New BackendEvent enum**: Wraps `ConversationEvent` and adds inline events (`InlineBegin`, `InlineUpdate`, `InlineCommit`, `InlineAbort`) - **InlineEntryState tracking**: Manages streaming text with dynamic word wrapping in `src/history.rs` - **Text wrapping utility**: New `text_utils` module with `wrap_text_to_width` function for proper text reflow - **Backend updates**: All backends now emit `BackendEvent` instead of `ConversationEvent` - **UI integration**: Model tracks inline entries and renders them separately from committed scrollback - **Test updates**: All tests updated to handle new `BackendEvent` structure with inline event tracking ### Benefits ✨ Assistant messages appear immediately as they stream 📐 Proper word wrapping adapts to terminal width changes 🎯 Cleaner separation between streaming and committed content 🧪 All tests passing with comprehensive inline event handling ## Test Plan - [x] All existing tests pass - [x] New inline event tracking tested in `acp_runner_test.rs` - [x] Word wrapping behavior verified - [x] Terminal resize handling tested 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
- Modified InlineEntryState.rewrap() to preserve all whitespace and newlines - Changed from using wrap_text_to_width (which normalizes spaces) to splitting on newlines - Each line now preserves exact formatting including multiple spaces - Long lines are displayed as-is (may overflow terminal width) - This ensures ACP agents' text formatting is preserved in the UI
## Summary 🤖 Generated with [Nori](https://www.npmjs.com/package/nori-ai) - Added GeminiAcpBackend for Google's `@google/gemini-cli` npm package - Refactored backend availability checking into single `compute_backend_availability()` method - Fixed existing bug where backend_availability array had mismatched indices with actual backends ## Implementation Details **New Backend:** - Created `src/backends/gemini_acp.rs` following the exact pattern from Codex/Claude Code ACP backends - Uses JavaScript runtime detection (bunx/npx) - Delegates to AcpAgentRunner for protocol handling - Install command: `npm install -g @google/gemini-cli` **Refactoring:** - Extracted `Model::compute_backend_availability()` method to eliminate duplication - Fixed off-by-one bug in backend_availability array **Backend Ordering:** - Index 0: Claude Code ACP - Index 1: Codex ACP - Index 2: Mock ACP Agent - Index 3: Gemini ACP (new) ## Test Plan - [x] All 114 tests passing (110 existing + 4 new Gemini backend tests) - [x] cargo fmt (no formatting issues) - [x] cargo clippy (no warnings) - [x] cargo build successful - [x] CI tests passing Share Nori with your team: https://www.npmjs.com/package/nori-ai --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary 🤖 Generated with [Nori](https://www.npmjs.com/package/nori-ai) - Created a single `BACKEND_OPTIONS` constant containing all backend metadata (name, availability check, factory function) - Eliminated disconnected `agents` and `backend_availability` vectors that could get out of sync - Updated `get_backend()` to use centralized options instead of hardcoded match statement - Updated UI and installation handling to use the centralized system - Added comprehensive tests to verify backend ordering and instantiation ## Test Plan - [x] All existing tests pass - [x] New tests verify backend ordering consistency - [x] New tests verify correct backend instantiation for each index - [x] Integration tests verify UI displays backends correctly Share Nori with your team: https://www.npmjs.com/package/nori-ai
## Summary 🤖 Generated with [Claude Code](https://claude.com/claude-code) - Implemented Drop trait for AcpAgentRunner to prevent orphaned processes - Uses libc::kill with SIGTERM for synchronous process termination - Added comprehensive integration tests verifying actual OS-level cleanup - Added tracing logs for debugging process lifecycle ## Technical Approach The implementation uses `libc::kill` instead of `tokio::process::Child::kill()` because Drop requires synchronous execution and `block_on` cannot be called from within an existing tokio runtime. ## Test Plan - [x] All existing tests pass (112 tests) - [x] New integration tests verify process cleanup on: - Runner drop - Stream reuse (spawning new stream kills old process) - Initialization failure - [x] Tests use OS-level verification via `kill -0 <pid>` - [x] cargo fmt passes - [x] cargo clippy passes (warnings are pre-existing in other tests) ## Files Changed - `src/acp_runner.rs`: Added Drop impl and agent_pid() method - `Cargo.toml`: Added libc dependency - `tests/acp_process_cleanup_test.rs`: New integration tests - `src/backends/docs.md`: Updated documentation - `tests/docs.md`: Updated test documentation Share Nori with your team: https://www.npmjs.com/package/nori-ai --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Added file-based logging via tuicore's `.use_disk_logs(true)` configuration - Instrumented ACP runner with tracing macros (info/debug/warn) throughout lifecycle - Logs written to `~/.nori-cli/logs/` with automatic daily rotation - Fixed pre-existing test failure in `model_backend_ordering_test.rs` ## Changes **src/main.rs**: Enabled disk logs in TuiApp builder **src/acp_runner.rs**: Added ~15 tracing calls covering: - ACP initialization and handshake - Session creation and management - Prompt handling - File operations (read/write) - Permission requests - All error conditions **tests/model_backend_ordering_test.rs**: Fixed test to use `BACKEND_OPTIONS` constant ## Test Plan - [x] All 107 existing tests pass - [x] Cargo build succeeds - [x] Cargo clippy passes (pre-existing warnings only) - [x] Manual verification: logs created in `~/.nori-cli/logs/` - [x] Manual verification: ACP lifecycle events logged with appropriate levels 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
## Summary - Store backend in Model to reuse across multiple prompts to the same agent - Backend only replaced after the conversation stream ends - Agent selection change alone doesn't affect running backend (allows safe browsing during active streams) ## Changes - Added `current_backend` and `current_backend_agent_index` fields to `Model` - Implemented `ensure_backend_for_current_agent()` to manage backend lifecycle - Modified `spawn_and_stream()` to accept stream instead of consuming backend - Updated documentation to reflect subprocess persistence behavior ## Test Plan - [x] All existing tests pass (104 tests) - [x] Code formatted with `cargo fmt` - [x] Linted with `cargo clippy` - [x] Added persistence test structure (tests verify behavior will work correctly once ACP runners expose PIDs) ## Testing The implementation enables efficient multi-turn conversations where the agent subprocess persists between turns. The subprocess is only killed and recreated when: 1. User switches to a different agent AND submits a new prompt 2. Application exits 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Shortens the command name for better usability while maintaining the same functionality for opening the agent router overlay. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
When pulldown-cmark retroactively reclassifies content (e.g., partial text `[foo` becomes a link reference definition `[foo]: url` producing 0 rendered lines), the committed_line_count could stay above the actual rendered count, permanently blocking new streaming output. Reset the counter to the current render count when the guard fires, so future commits are not blocked after a regression. 🤖 Generated with [Nori](https://usenori.ai) Co-Authored-By: Nori <contact@tilework.tech>
…ng-freezes-stalls-bug-20260225-033452 # Conflicts: # .claude/settings.json # .gitignore # README.md # docs.md
…ace fix The instructions footer line had one extra trailing space compared to the terminal width, causing all 7 blackbox_tui_test snapshots to be stale. Regenerated snapshots to match current rendering output. 🤖 Generated with [Nori](https://usenori.ai) Co-Authored-By: Nori <contact@tilework.tech>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
🤖 Generated with Nori
[footext later becomes a link reference definition producing 0 rendered lines)committed_line_countto the current render count when a line count regression is detected, preventing the counter from permanently blocking new outputTest Plan
just fix -p nori-tuipasses with no clippy warningsjust fmtpasses cleanlyShare Nori with your team: https://www.npmjs.com/package/nori-ai