fix(release): polish v0.8.34 review issues
This commit is contained in:
+4
-3
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 => "",
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
|
||||
@@ -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
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user