diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f53b693..cb3a9eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,8 +56,8 @@ mega-files that had grown around the agent loop and TUI. turn loop and tool execution code, making the agent-loop core easier to read end-to-end. - **Structured tracing on tool dispatch.** Tool entry, exit, duration, - and result/error are emitted through `tracing` spans so - `RUST_LOG=deepseek_cli::tools=debug` produces a coherent timeline + and result/error are emitted through `tracing` events so + `RUST_LOG=engine.tool_execution=debug` produces a coherent timeline instead of scattered ad-hoc prints. - **`/init` updates `AGENTS.md` in place** instead of refusing when the file already exists, so adding new project guidance does not @@ -4071,7 +4071,8 @@ Welcome — and thank you. - Hooks system and config profiles - Example skills and launch assets -[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.33...HEAD +[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.34...HEAD +[0.8.34]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.33...v0.8.34 [0.8.33]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.32...v0.8.33 [0.8.32]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.31...v0.8.32 [0.8.31]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.30...v0.8.31 diff --git a/README.md b/README.md index 1ba5a0ea..abeb7759 100644 --- a/README.md +++ b/README.md @@ -241,53 +241,34 @@ deepseek --provider ollama --model deepseek-coder:1.3b --- -## What's New In v0.8.33 +## What's New In v0.8.34 -A sub-agent and RLM renovation release. The model-facing delegation -surface is now session-oriented: `rlm_open` / `rlm_eval` / -`rlm_configure` / `rlm_close` for persistent RLM work, `agent_open` / -`agent_eval` / `agent_close` for named sub-agent sessions, and -`handle_read` for bounded retrieval from large results. Six tool -papercuts fixed, two community PRs landed, and the sidebar gets a -cleaner "Work" tab. [Full changelog](CHANGELOG.md). +A polish, terminal-protocol, and internal-cleanup release. The +model-facing tool surface stays stable while v0.8.34 improves first-run +skills, terminal notifications, prompt-cache visibility, MCP transport +compatibility, and the maintainability of the largest TUI files. +[Full changelog](CHANGELOG.md). -- **Persistent RLM sessions.** RLM work now uses `rlm_open` / - `rlm_eval` / `rlm_close` with bounded REPL helpers (`peek`, - `search`, `chunk`, `sub_query`, `sub_query_batch`, `finalize`) - — the model drives the REPL through tool calls instead of a - foreground loop. -- **Fork-aware sub-agent sessions.** `agent_open` supports named - sessions, `fork_context` for prompt-cache-friendly perspective - fanout, and bounded recursive depth. Sub-agent results and - transcripts can be parked behind `var_handle` references. -- **Shared `handle_read` tool.** Large structured results (RLM - finals, sub-agent transcripts, tool artifacts) return typed handles - with slice, range, count, and JSONPath projections — the model - reads back only what it needs. -- **Text selection now works during streaming.** The loading-state - mouse filter drops inert move events but allows transcript and - scrollbar drags to continue — the known issue from v0.8.32 is - resolved. -- **Theme presets.** Use `/theme` for a live picker, or `/theme grayscale`, - `/theme catppuccin-mocha`, `/theme tokyo-night`, `/theme dracula`, and - `/theme gruvbox-dark` to save a theme directly. -- **Session history picker.** `/sessions` and `Ctrl+R` now put full - session history on the left, the session list on the right, number keys - `1`-`9` open visible histories, and `PgUp` / `PgDn` scroll history. -- **Six tool papercuts fixed.** `file_search` safer excludes; - `grep_files` returns clean strings; `fetch_url` JSON field - projection and headers; `edit_file` indentation fuzz; - `exec_shell` merged stdout/stderr; `revert_turn` rejects no-ops. -- **CLI reasoning-effort honoured** on `--reasoning-effort high` - non-auto exec routes (PR #1511 from **@h3c-hexin**). -- **Sidebar "Work" tab.** The former "Plan" / "Todos" tabs are now - one "Work" panel for the active checklist, consistent across Plan, - Agent, and YOLO modes. -- **`/relay` command with CJK aliases** (`/接力`) for structured - multi-session handoff prompts. - -Thanks to **@reidliu41** and **@h3c-hexin** for community -contributions in this release. +- **Bundled DeepSeek-native workflow skills.** Fresh installs now get + first-party skills for delegation, skill creation, MCP/plugin setup, + documents, presentations, spreadsheets, PDFs, and Feishu/Lark. +- **User skills stay visible.** `/skills` separates user-created skills + from built-ins so workspace and global skills do not disappear behind + the bundled catalog. +- **MCP HTTP defaults are more compatible.** Streamable HTTP requests + default to `Accept: application/json, text/event-stream` and preserve + `Mcp-Session-Id` across requests. +- **Terminal notifications cover more terminals.** Kitty `OSC 99` and + Ghostty `OSC 777` join the existing notification paths. +- **Prefix-cache stability is visible.** The footer surfaces cache + stability so users can notice cache-busting changes before cost climbs. +- **`edit_file` handles typographic punctuation drift.** With + `fuzz: true`, smart quotes, en/em dashes, and non-breaking spaces no + longer prevent a safe replacement when the file uses ASCII punctuation. +- **Large internals are getting smaller.** Focused modules now own + auto-routing, Vim-mode handling, workspace context, streaming thinking, + notifications, file-picker relevance, formatting helpers, and key + shortcut predicates. --- diff --git a/crates/tui/CHANGELOG.md b/crates/tui/CHANGELOG.md index 1f53b693..cb3a9eb8 100644 --- a/crates/tui/CHANGELOG.md +++ b/crates/tui/CHANGELOG.md @@ -56,8 +56,8 @@ mega-files that had grown around the agent loop and TUI. turn loop and tool execution code, making the agent-loop core easier to read end-to-end. - **Structured tracing on tool dispatch.** Tool entry, exit, duration, - and result/error are emitted through `tracing` spans so - `RUST_LOG=deepseek_cli::tools=debug` produces a coherent timeline + and result/error are emitted through `tracing` events so + `RUST_LOG=engine.tool_execution=debug` produces a coherent timeline instead of scattered ad-hoc prints. - **`/init` updates `AGENTS.md` in place** instead of refusing when the file already exists, so adding new project guidance does not @@ -4071,7 +4071,8 @@ Welcome — and thank you. - Hooks system and config profiles - Example skills and launch assets -[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.33...HEAD +[Unreleased]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.34...HEAD +[0.8.34]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.33...v0.8.34 [0.8.33]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.32...v0.8.33 [0.8.32]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.31...v0.8.32 [0.8.31]: https://github.com/Hmbown/DeepSeek-TUI/compare/v0.8.30...v0.8.31 diff --git a/crates/tui/src/commands/skills.rs b/crates/tui/src/commands/skills.rs index 24229884..b1823d5f 100644 --- a/crates/tui/src/commands/skills.rs +++ b/crates/tui/src/commands/skills.rs @@ -154,8 +154,10 @@ pub fn list_skills(app: &mut App, arg: Option<&str>) -> CommandResult { let _ = writeln!(output, " /{} - {}", skill.name, skill.description); } } else { - let names: Vec = - bundled_skills.iter().map(|s| format!("/{}", s.name)).collect(); + let names: Vec = bundled_skills + .iter() + .map(|s| format!("/{}", s.name)) + .collect(); output.push_str(" "); output.push_str(&names.join(", ")); output.push('\n'); @@ -890,7 +892,10 @@ mod tests { .expect("user skills section header missing"); let alpha = msg.find("/alpha-skill").expect("alpha skill should render"); let beta = msg.find("/beta-skill").expect("beta skill should render"); - assert!(alpha > section, "alpha-skill should follow the header: {msg}"); + assert!( + alpha > section, + "alpha-skill should follow the header: {msg}" + ); assert!(beta > section, "beta-skill should follow the header: {msg}"); // Each entry on its own line with the description inline. assert!(msg.contains("/alpha-skill - First skill"), "got: {msg}"); diff --git a/crates/tui/src/core/engine.rs b/crates/tui/src/core/engine.rs index 243e6f34..32c251aa 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -1923,9 +1923,9 @@ pub(crate) fn mock_engine_handle() -> MockEngineHandle { } mod approval; -mod handle; mod capacity_flow; mod context; +mod handle; pub(crate) use context::compact_tool_result_for_context; use context::{ COMPACTION_SUMMARY_MARKER, MAX_CONTEXT_RECOVERY_ATTEMPTS, MIN_RECENT_MESSAGES_TO_KEEP, diff --git a/crates/tui/src/tools/file.rs b/crates/tui/src/tools/file.rs index 33a19102..42f1674c 100644 --- a/crates/tui/src/tools/file.rs +++ b/crates/tui/src/tools/file.rs @@ -601,7 +601,9 @@ impl ToolSpec for EditFileTool { } else { let fuzz_note = match fuzz_kind { Some("indentation") => " (fuzzy indentation match)", - Some("punctuation") => " (fuzzy punctuation match — typographic quotes/dashes normalized)", + Some("punctuation") => { + " (fuzzy punctuation match — typographic quotes/dashes normalized)" + } Some(other) => other, None => "", }; diff --git a/crates/tui/src/tui/auto_router.rs b/crates/tui/src/tui/auto_router.rs index 72021390..89bcbf7e 100644 --- a/crates/tui/src/tui/auto_router.rs +++ b/crates/tui/src/tui/auto_router.rs @@ -145,7 +145,12 @@ mod tests { // Eight messages; final one (the draft being routed) is skipped, // so we expect at most six of the remaining seven. let msgs: Vec = (0..8) - .map(|i| make_msg(if i % 2 == 0 { "user" } else { "assistant" }, &format!("turn {i}"))) + .map(|i| { + make_msg( + if i % 2 == 0 { "user" } else { "assistant" }, + &format!("turn {i}"), + ) + }) .collect(); let context = recent_auto_router_context(&msgs); assert!(!context.contains("turn 7"), "final draft must be skipped"); diff --git a/crates/tui/src/tui/file_picker_relevance.rs b/crates/tui/src/tui/file_picker_relevance.rs index f263ae8c..a7fde2e0 100644 --- a/crates/tui/src/tui/file_picker_relevance.rs +++ b/crates/tui/src/tui/file_picker_relevance.rs @@ -19,17 +19,19 @@ use std::path::{Path, PathBuf}; use std::process::Command; use crate::tui::app::App; +use crate::tui::app::ToolDetailRecord; +use crate::tui::file_mention::{ContextReferenceKind, ContextReferenceSource}; use crate::tui::file_picker::FilePickerRelevance; use crate::tui::file_picker::FilePickerView; -use crate::tui::file_mention::{ContextReferenceKind, ContextReferenceSource}; -use crate::tui::app::ToolDetailRecord; /// Push the `/files` picker onto the view stack, pre-populated with /// per-session relevance ranks (modified, @-mentioned, tool-touched). pub(super) fn open_file_picker(app: &mut App) { let relevance = build_relevance(app); - app.view_stack - .push(FilePickerView::new_with_relevance(&app.workspace, relevance)); + app.view_stack.push(FilePickerView::new_with_relevance( + &app.workspace, + relevance, + )); } pub(super) fn build_relevance(app: &App) -> FilePickerRelevance { diff --git a/crates/tui/src/tui/mod.rs b/crates/tui/src/tui/mod.rs index 6d5a6e1a..d11cf0e2 100644 --- a/crates/tui/src/tui/mod.rs +++ b/crates/tui/src/tui/mod.rs @@ -15,8 +15,6 @@ pub mod active_cell; pub mod app; pub mod approval; pub mod auto_router; -pub mod vim_mode; -pub mod workspace_context; pub mod backtrack; pub mod clipboard; mod color_compat; @@ -31,11 +29,11 @@ pub mod file_frecency; pub mod file_mention; pub mod file_picker; pub mod file_picker_relevance; -pub mod format_helpers; -pub mod key_shortcuts; pub mod file_tree; +pub mod format_helpers; pub mod frame_rate_limiter; pub mod history; +pub mod key_shortcuts; pub mod keybindings; pub mod live_transcript; pub mod markdown_render; @@ -55,9 +53,9 @@ pub mod selection; pub mod session_picker; mod shell_job_routing; pub mod sidebar; -pub mod streaming_thinking; pub mod slash_menu; pub mod streaming; +pub mod streaming_thinking; mod subagent_routing; pub mod theme_picker; mod tool_routing; @@ -68,7 +66,9 @@ pub mod ui; mod ui_text; pub mod user_input; pub mod views; +pub mod vim_mode; pub mod widgets; +pub mod workspace_context; // === Re-exports === diff --git a/crates/tui/src/tui/streaming_thinking.rs b/crates/tui/src/tui/streaming_thinking.rs index 4232dfb8..70f17b54 100644 --- a/crates/tui/src/tui/streaming_thinking.rs +++ b/crates/tui/src/tui/streaming_thinking.rs @@ -220,11 +220,7 @@ pub(super) fn stash_reasoning_buffer_into_last_reasoning(app: &mut App) { /// duration. Returns `true` when a thinking entry was finalized (so the /// dispatch loop knows the transcript was touched). No-op if no thinking /// entry is currently streaming. -pub(super) fn finalize_active_entry( - app: &mut App, - duration: Option, - remaining: &str, -) -> bool { +pub(super) fn finalize_active_entry(app: &mut App, duration: Option, remaining: &str) -> bool { let Some(entry_idx) = app.streaming_thinking_active_entry.take() else { return false; }; diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index b3bb57c7..44dadd61 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -59,6 +59,7 @@ use crate::task_manager::{ }; use crate::tools::spec::RuntimeToolServices; use crate::tools::subagent::SubAgentStatus; +use crate::tui::auto_router; use crate::tui::color_compat::ColorCompatBackend; use crate::tui::command_palette::{ CommandPaletteView, build_entries as build_command_palette_entries, @@ -66,16 +67,12 @@ use crate::tui::command_palette::{ use crate::tui::context_inspector::build_context_inspector_text; use crate::tui::context_menu::{ContextMenuEntry, ContextMenuView}; use crate::tui::event_broker::EventBroker; -use crate::tui::live_transcript::LiveTranscriptOverlay; -use crate::tui::mcp_routing::{add_mcp_message, open_mcp_manager_pager}; -use crate::tui::auto_router; -use crate::tui::vim_mode; -use crate::tui::streaming_thinking; -use crate::tui::workspace_context; -use crate::tui::notifications; use crate::tui::file_picker_relevance; use crate::tui::format_helpers; use crate::tui::key_shortcuts; +use crate::tui::live_transcript::LiveTranscriptOverlay; +use crate::tui::mcp_routing::{add_mcp_message, open_mcp_manager_pager}; +use crate::tui::notifications; use crate::tui::onboarding; use crate::tui::pager::PagerView; use crate::tui::persistence_actor::{self, PersistRequest}; @@ -86,6 +83,7 @@ use crate::tui::session_picker::SessionPickerView; use crate::tui::shell_job_routing::{ add_shell_job_message, format_shell_job_list, format_shell_poll, open_shell_job_pager, }; +use crate::tui::streaming_thinking; use crate::tui::subagent_routing::{ active_fanout_counts, format_task_list, handle_subagent_mailbox, open_task_pager, reconcile_subagent_activity_state, running_agent_count, sort_subagents_in_place, @@ -99,6 +97,8 @@ use crate::tui::tool_routing::{ use crate::tui::ui_text::{history_cell_to_text, line_to_plain, slice_text, text_display_width}; use crate::tui::user_input::UserInputView; use crate::tui::views::subagent_view_agents; +use crate::tui::vim_mode; +use crate::tui::workspace_context; use super::app::{ App, AppAction, AppMode, OnboardingState, QueuedMessage, ReasoningEffort, SidebarFocus, @@ -1839,7 +1839,10 @@ async fn run_event_loop( && last_status_frame.elapsed() >= Duration::from_millis(status_animation_interval_ms(app)) { - if streaming_thinking::animate_pending_translation(app, pending_thinking_translations > 0) { + if streaming_thinking::animate_pending_translation( + app, + pending_thinking_translations > 0, + ) { app.mark_history_updated(); } if !app.low_motion && history_has_live_motion(&app.history) { @@ -2284,13 +2287,16 @@ async fn run_event_loop( app.delete_api_key_char(); onboarding::sync_api_key_validation_status(app, false); } - _ if key_shortcuts::is_paste_shortcut(&key) && app.onboarding == OnboardingState::ApiKey => { + _ if key_shortcuts::is_paste_shortcut(&key) + && app.onboarding == OnboardingState::ApiKey => + { // Cmd+V / Ctrl+V paste (bracketed paste handled above) app.paste_api_key_from_clipboard(); onboarding::sync_api_key_validation_status(app, false); } KeyCode::Char(c) - if app.onboarding == OnboardingState::ApiKey && key_shortcuts::is_text_input_key(&key) => + if app.onboarding == OnboardingState::ApiKey + && key_shortcuts::is_text_input_key(&key) => { app.insert_api_key_char(c); onboarding::sync_api_key_validation_status(app, false); @@ -2634,7 +2640,9 @@ async fn run_event_loop( app.view_stack.push(SessionPickerView::new(&app.workspace)); continue; } - KeyCode::Char('c') | KeyCode::Char('C') if key_shortcuts::is_copy_shortcut(&key) => { + KeyCode::Char('c') | KeyCode::Char('C') + if key_shortcuts::is_copy_shortcut(&key) => + { copy_active_selection(app); } KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { @@ -3150,7 +3158,8 @@ async fn run_event_loop( } KeyCode::Backspace => {} KeyCode::Char('h') - if key_shortcuts::is_ctrl_h_backspace(&key) && !app.remove_selected_composer_attachment() => + if key_shortcuts::is_ctrl_h_backspace(&key) + && !app.remove_selected_composer_attachment() => { app.delete_char(); } @@ -3363,7 +3372,6 @@ async fn run_event_loop( } } - fn apply_alt_4_shortcut(app: &mut App, _modifiers: KeyModifiers) { app.set_sidebar_focus(SidebarFocus::Agents); app.status_message = Some("Sidebar focus: agents".to_string()); @@ -3978,7 +3986,10 @@ async fn dispatch_user_message( .as_ref() .and_then(|selection| selection.reasoning_effort) .unwrap_or_else(|| { - auto_router::normalize_auto_routed_effort(crate::auto_reasoning::select(false, &message.display)) + auto_router::normalize_auto_routed_effort(crate::auto_reasoning::select( + false, + &message.display, + )) }); app.last_effective_reasoning_effort = Some(effort); Some(effort.as_setting().to_string()) @@ -4428,7 +4439,9 @@ async fn apply_command_result( match fetch_available_models(config).await { Ok(models) => { app.add_message(HistoryCell::System { - content: format_helpers::available_models_message(&app.model, &models), + content: format_helpers::available_models_message( + &app.model, &models, + ), }); app.status_message = Some(format!("Found {} model(s)", models.len())); } diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index ca6a094e..649ac859 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -1,10 +1,9 @@ use super::*; use crate::config::{ApiProvider, Config}; -use crate::tui::active_cell::ActiveCell; -use crate::tui::app::ToolDetailRecord; -use std::collections::HashSet; use crate::config_ui::{self, WebConfigSession, WebConfigSessionEvent}; use crate::core::engine::mock_engine_handle; +use crate::tui::active_cell::ActiveCell; +use crate::tui::app::ToolDetailRecord; use crate::tui::file_mention::{ apply_mention_menu_selection, find_file_mention_completions, partial_file_mention_at_cursor, try_autocomplete_file_mention, user_request_with_file_mentions, visible_mention_menu_entries, @@ -14,6 +13,7 @@ use crate::tui::history::{ }; use crate::tui::views::{ModalView, ViewAction}; use crate::working_set::Workspace; +use std::collections::HashSet; use std::ffi::OsString; use std::path::PathBuf; use std::process::Command; @@ -982,30 +982,31 @@ fn copy_shortcut_accepts_cmd_and_ctrl_shift_only() { KeyCode::Char('c'), KeyModifiers::CONTROL | KeyModifiers::SHIFT, ))); - assert!(!crate::tui::key_shortcuts::is_copy_shortcut(&KeyEvent::new( - KeyCode::Char('c'), - KeyModifiers::CONTROL, - ))); + assert!(!crate::tui::key_shortcuts::is_copy_shortcut( + &KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL,) + )); } #[test] fn file_tree_shortcut_does_not_steal_plain_ctrl_e() { - assert!(!crate::tui::key_shortcuts::is_file_tree_toggle_shortcut(&KeyEvent::new( - KeyCode::Char('e'), - KeyModifiers::CONTROL, - ))); - assert!(crate::tui::key_shortcuts::is_file_tree_toggle_shortcut(&KeyEvent::new( - KeyCode::Char('E'), - KeyModifiers::CONTROL, - ))); - assert!(crate::tui::key_shortcuts::is_file_tree_toggle_shortcut(&KeyEvent::new( - KeyCode::Char('e'), - KeyModifiers::CONTROL | KeyModifiers::SHIFT, - ))); - assert!(crate::tui::key_shortcuts::is_file_tree_toggle_shortcut(&KeyEvent::new( - KeyCode::Char('E'), - KeyModifiers::SUPER | KeyModifiers::SHIFT, - ))); + assert!(!crate::tui::key_shortcuts::is_file_tree_toggle_shortcut( + &KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL,) + )); + assert!(crate::tui::key_shortcuts::is_file_tree_toggle_shortcut( + &KeyEvent::new(KeyCode::Char('E'), KeyModifiers::CONTROL,) + )); + assert!(crate::tui::key_shortcuts::is_file_tree_toggle_shortcut( + &KeyEvent::new( + KeyCode::Char('e'), + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + ) + )); + assert!(crate::tui::key_shortcuts::is_file_tree_toggle_shortcut( + &KeyEvent::new( + KeyCode::Char('E'), + KeyModifiers::SUPER | KeyModifiers::SHIFT, + ) + )); } #[test] @@ -1061,7 +1062,9 @@ fn parse_git_status_path_handles_simple_and_renamed_entries() { Some("crates/tui/src/tui/ui.rs".to_string()) ); assert_eq!( - crate::tui::file_picker_relevance::parse_git_status_path("R old name.rs -> crates/tui/src/tui/file_picker.rs"), + crate::tui::file_picker_relevance::parse_git_status_path( + "R old name.rs -> crates/tui/src/tui/file_picker.rs" + ), Some("crates/tui/src/tui/file_picker.rs".to_string()) ); } @@ -2823,7 +2826,9 @@ fn api_key_paste_shortcut_is_not_plain_text_input() { let legacy_ctrl_v = KeyEvent::new(KeyCode::Char('\u{16}'), KeyModifiers::NONE); assert!(crate::tui::key_shortcuts::is_paste_shortcut(&legacy_ctrl_v)); - assert!(!crate::tui::key_shortcuts::is_text_input_key(&legacy_ctrl_v)); + assert!(!crate::tui::key_shortcuts::is_text_input_key( + &legacy_ctrl_v + )); let shifted = KeyEvent::new(KeyCode::Char('A'), KeyModifiers::SHIFT); assert!(crate::tui::key_shortcuts::is_text_input_key(&shifted)); @@ -3012,7 +3017,9 @@ fn activity_footer_hint_surfaces_visible_thinking_without_raw_tool_hint() { fn macos_option_v_glyph_is_treated_as_details_shortcut_only_on_macos() { let option_v = KeyEvent::new(KeyCode::Char('\u{221A}'), KeyModifiers::NONE); assert!(crate::tui::key_shortcuts::is_macos_option_v_legacy_key_for_platform(&option_v, true)); - assert!(!crate::tui::key_shortcuts::is_macos_option_v_legacy_key_for_platform(&option_v, false)); + assert!( + !crate::tui::key_shortcuts::is_macos_option_v_legacy_key_for_platform(&option_v, false) + ); let modified = KeyEvent::new(KeyCode::Char('\u{221A}'), KeyModifiers::SHIFT); assert!(!crate::tui::key_shortcuts::is_macos_option_v_legacy_key_for_platform(&modified, true)); @@ -3194,31 +3201,43 @@ fn alt_nav_modifiers_require_alt_and_exclude_ctrl_super() { // Alt, allow Shift for capital-letter forms, and block Ctrl/Super so // they don't collide with clipboard / window shortcuts. Bare and // Shift-only modifiers fall through to text insertion now. - assert!(!crate::tui::key_shortcuts::alt_nav_modifiers(KeyModifiers::NONE)); - assert!(!crate::tui::key_shortcuts::alt_nav_modifiers(KeyModifiers::SHIFT)); - assert!(crate::tui::key_shortcuts::alt_nav_modifiers(KeyModifiers::ALT)); - assert!(crate::tui::key_shortcuts::alt_nav_modifiers(KeyModifiers::ALT | KeyModifiers::SHIFT)); - assert!(!crate::tui::key_shortcuts::alt_nav_modifiers(KeyModifiers::CONTROL)); + assert!(!crate::tui::key_shortcuts::alt_nav_modifiers( + KeyModifiers::NONE + )); + assert!(!crate::tui::key_shortcuts::alt_nav_modifiers( + KeyModifiers::SHIFT + )); + assert!(crate::tui::key_shortcuts::alt_nav_modifiers( + KeyModifiers::ALT + )); + assert!(crate::tui::key_shortcuts::alt_nav_modifiers( + KeyModifiers::ALT | KeyModifiers::SHIFT + )); + assert!(!crate::tui::key_shortcuts::alt_nav_modifiers( + KeyModifiers::CONTROL + )); assert!(!crate::tui::key_shortcuts::alt_nav_modifiers( KeyModifiers::ALT | KeyModifiers::CONTROL )); - assert!(!crate::tui::key_shortcuts::alt_nav_modifiers(KeyModifiers::ALT | KeyModifiers::SUPER)); + assert!(!crate::tui::key_shortcuts::alt_nav_modifiers( + KeyModifiers::ALT | KeyModifiers::SUPER + )); } #[test] fn ctrl_h_is_treated_as_terminal_backspace() { - assert!(crate::tui::key_shortcuts::is_ctrl_h_backspace(&KeyEvent::new( - KeyCode::Char('h'), - KeyModifiers::CONTROL - ))); - assert!(!crate::tui::key_shortcuts::is_ctrl_h_backspace(&KeyEvent::new( - KeyCode::Char('h'), - KeyModifiers::NONE - ))); - assert!(!crate::tui::key_shortcuts::is_ctrl_h_backspace(&KeyEvent::new( - KeyCode::Char('h'), - KeyModifiers::CONTROL | KeyModifiers::ALT - ))); + assert!(crate::tui::key_shortcuts::is_ctrl_h_backspace( + &KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL) + )); + assert!(!crate::tui::key_shortcuts::is_ctrl_h_backspace( + &KeyEvent::new(KeyCode::Char('h'), KeyModifiers::NONE) + )); + assert!(!crate::tui::key_shortcuts::is_ctrl_h_backspace( + &KeyEvent::new( + KeyCode::Char('h'), + KeyModifiers::CONTROL | KeyModifiers::ALT + ) + )); } #[test] @@ -5238,16 +5257,26 @@ fn completed_turn_notification_falls_back_to_latest_assistant_message() { }], }); - let msg = - crate::tui::notifications::completed_turn_message(&app, "", false, Duration::from_secs(75), None); + let msg = crate::tui::notifications::completed_turn_message( + &app, + "", + false, + Duration::from_secs(75), + None, + ); assert_eq!(msg, "Latest reply"); } #[test] fn completed_turn_notification_falls_back_to_default_when_empty() { let app = create_test_app(); - let msg = - crate::tui::notifications::completed_turn_message(&app, "", false, Duration::from_secs(5), None); + let msg = crate::tui::notifications::completed_turn_message( + &app, + "", + false, + Duration::from_secs(5), + None, + ); assert_eq!(msg, "deepseek: turn complete"); } diff --git a/crates/tui/src/tui/workspace_context.rs b/crates/tui/src/tui/workspace_context.rs index 3e7f4692..4f9e44aa 100644 --- a/crates/tui/src/tui/workspace_context.rs +++ b/crates/tui/src/tui/workspace_context.rs @@ -32,7 +32,9 @@ pub(super) fn refresh_if_needed(app: &mut App, now: Instant, allow_refresh: bool if app .workspace_context_refreshed_at - .is_some_and(|refreshed_at| now.duration_since(refreshed_at) < Duration::from_secs(REFRESH_SECS)) + .is_some_and(|refreshed_at| { + now.duration_since(refreshed_at) < Duration::from_secs(REFRESH_SECS) + }) { return; }