fix(release): polish v0.8.34 review issues

This commit is contained in:
Hunter Bown
2026-05-13 12:08:46 -05:00
parent 5f1bf58cfc
commit 16114f3020
13 changed files with 172 additions and 135 deletions
+4 -3
View File
@@ -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
+26 -45
View File
@@ -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.
---
+4 -3
View File
@@ -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
+8 -3
View File
@@ -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<String> =
bundled_skills.iter().map(|s| format!("/{}", s.name)).collect();
let names: Vec<String> = 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}");
+1 -1
View File
@@ -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,
+3 -1
View File
@@ -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 => "",
};
+6 -1
View File
@@ -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<Message> = (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");
+6 -4
View File
@@ -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 {
+5 -5
View File
@@ -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 ===
+1 -5
View File
@@ -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<f32>,
remaining: &str,
) -> bool {
pub(super) fn finalize_active_entry(app: &mut App, duration: Option<f32>, remaining: &str) -> bool {
let Some(entry_idx) = app.streaming_thinking_active_entry.take() else {
return false;
};
+28 -15
View File
@@ -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()));
}
+77 -48
View File
@@ -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");
}
+3 -1
View File
@@ -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;
}