diff --git a/AGENTS.md b/AGENTS.md index 2861414d..c21570e4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,39 @@ This file provides context for AI assistants working on this project. - Local dev shorthand: after `cargo build --release`, run `./target/release/deepseek`. ### Build Dependencies -- **Rust** 1.85+ (for the workspace) +- **Rust** 1.88+ (the workspace declares `rust-version = "1.88"` because we + use `let_chains` in `if`/`while` conditions, which stabilized in 1.88). + +### Stable Rust only — no nightly features + +This crate must compile on stable Rust. **Never** introduce code that +requires `#![feature(...)]`, `cargo +nightly`, or any unstable language / +library feature. Common pitfalls to avoid: + +- **`if let` guards in match arms** (`if_let_guard`, tracking issue #51114) + — was nightly-only on Rust < 1.94. Rewrite as a plain match guard with a + nested `if let` inside the arm body. Example of what NOT to do: + ```rust + // BAD — fails on stable rustc < 1.94 with E0658 + match key { + KeyCode::Char(c) if cond && let Some(x) = find(c) => { … } + } + ``` + Rewrite as: + ```rust + // GOOD — works on every supported rustc + match key { + KeyCode::Char(c) if cond => { + if let Some(x) = find(c) { … } + } + } + ``` +- `let_chains` in `if`/`while` (`&& let Some(_) = …`) **is** stable as of + Rust 1.88 and is fine to use. +- Custom `#![feature(...)]` attributes — never. + +Before opening a PR, run `cargo build` (not `cargo +nightly build`) and +make sure the workspace's declared `rust-version` is enough to compile. ### Documentation See README.md for project overview, docs/ARCHITECTURE.md for internals. diff --git a/Cargo.toml b/Cargo.toml index 64c21d97..1af2c90c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,11 @@ resolver = "2" [workspace.package] version = "0.8.12" edition = "2024" +# Rust 1.88 stabilized `let_chains` in `if`/`while` conditions, which the +# codebase relies on extensively. Cargo enforces this so users on older +# toolchains get a clear "package requires rustc 1.88+" error instead of a +# confusing E0658 from rustc. +rust-version = "1.88" license = "MIT" repository = "https://github.com/Hmbown/DeepSeek-TUI" diff --git a/crates/tui/src/handoff.rs b/crates/tui/src/handoff.rs index 8fcf9825..69a68ddf 100644 --- a/crates/tui/src/handoff.rs +++ b/crates/tui/src/handoff.rs @@ -1,8 +1,19 @@ +// Used by the deferred context-limit handoff feature (#667). The implementation +// path is staged but not yet wired from the engine; suppress dead-code warnings +// rather than delete the table, since v0.8.13 will consume it. +#[allow(dead_code)] pub const THRESHOLDS: [(f32, &str); 3] = [ - (0.9, "Context at 90%: stop and write handoff to .deepseek/handoff.md now"), + ( + 0.9, + "Context at 90%: stop and write handoff to .deepseek/handoff.md now", + ), (0.8, "Context at 80%: draft handoff to .deepseek/handoff.md"), (0.7, "Context at 70%: consider wrapping current sub-task"), ]; +#[allow(dead_code)] pub fn threshold_message(ratio: f32) -> Option<&'static str> { - THRESHOLDS.iter().find(|(t,_)| ratio >= *t).map(|(_,m)| *m) + THRESHOLDS + .iter() + .find(|(t, _)| ratio >= *t) + .map(|(_, m)| *m) } diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index fd3b1a27..5191c0d9 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -34,6 +34,11 @@ use crate::localization::normalize_configured_locale; /// submit = "ctrl+enter" /// new_line = "enter" /// ``` +// +// NOTE: the loader is defined but not yet called from startup — wiring is +// deferred to v0.8.13 (#657). The `#[allow(dead_code)]` suppresses the CI +// `-D warnings` failure until the call site lands. +#[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct TuiPrefs { @@ -58,6 +63,7 @@ impl Default for TuiPrefs { } /// Per-action keybinding overrides stored inside [`TuiPrefs`]. +#[allow(dead_code)] // see TuiPrefs note above; deferred to v0.8.13 (#657). #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(default)] pub struct KeybindPrefs { @@ -78,6 +84,7 @@ pub struct KeybindPrefs { pub toggle_sidebar: Option, } +#[allow(dead_code)] // see TuiPrefs note above; deferred to v0.8.13 (#657). impl TuiPrefs { /// Return the canonical path of the TUI preferences file: /// `~/.deepseek/tui.toml`. @@ -98,9 +105,8 @@ impl TuiPrefs { } } - let home = dirs::home_dir().context( - "Failed to resolve home directory: cannot determine tui.toml path.", - )?; + let home = dirs::home_dir() + .context("Failed to resolve home directory: cannot determine tui.toml path.")?; Ok(home.join(".deepseek").join("tui.toml")) } @@ -127,14 +133,10 @@ impl TuiPrefs { let path = Self::path()?; if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).with_context(|| { - format!( - "Failed to create config directory {}", - parent.display() - ) + format!("Failed to create config directory {}", parent.display()) })?; } - let content = - toml::to_string_pretty(self).context("Failed to serialize TuiPrefs")?; + let content = toml::to_string_pretty(self).context("Failed to serialize TuiPrefs")?; std::fs::write(&path, content) .with_context(|| format!("Failed to write tui.toml to {}", path.display()))?; Ok(()) @@ -151,9 +153,7 @@ impl TuiPrefs { self.theme = theme; } other => { - anyhow::bail!( - "Invalid tui.toml theme '{other}': expected dark, light, or system." - ); + anyhow::bail!("Invalid tui.toml theme '{other}': expected dark, light, or system."); } } Ok(()) @@ -805,7 +805,9 @@ mod tests { theme: theme.to_string(), ..TuiPrefs::default() }; - prefs.validate().unwrap_or_else(|e| panic!("validate({theme}) failed: {e}")); + prefs + .validate() + .unwrap_or_else(|e| panic!("validate({theme}) failed: {e}")); assert_eq!(prefs.theme, theme); } } @@ -826,7 +828,9 @@ mod tests { theme: "solarized".to_string(), ..TuiPrefs::default() }; - let err = prefs.validate().expect_err("solarized is not a valid theme"); + let err = prefs + .validate() + .expect_err("solarized is not a valid theme"); assert!(err.to_string().contains("Invalid tui.toml theme")); } diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 737ee9a5..931c003a 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -1599,25 +1599,31 @@ async fn run_event_loop( app.status_message = None; } // Language picker hotkeys: 1-5 select + persist (#566). + // + // Note: this used to be a single match-guard with `&& let`, + // but `if_let_guard` is a nightly-only feature on Rust + // before 1.94. Rewriting as a plain guard + nested `if let` + // keeps `cargo install` working on stable. KeyCode::Char(c) - if app.onboarding == OnboardingState::Language - && c.is_ascii_digit() - && let Some((_, tag, _, _)) = - onboarding::language::LANGUAGE_OPTIONS - .iter() - .find(|(hotkey, _, _, _)| *hotkey == c) => + if app.onboarding == OnboardingState::Language && c.is_ascii_digit() => { - match app.set_locale_from_onboarding(tag) { - Ok(()) => { - app.push_status_toast( - format!("Language set to {tag}"), - StatusToastLevel::Info, - Some(2_500), - ); - advance_after_language(app); - } - Err(err) => { - app.status_message = Some(format!("Failed to save locale: {err}")); + if let Some((_, tag, _, _)) = onboarding::language::LANGUAGE_OPTIONS + .iter() + .find(|(hotkey, _, _, _)| *hotkey == c) + { + match app.set_locale_from_onboarding(tag) { + Ok(()) => { + app.push_status_toast( + format!("Language set to {tag}"), + StatusToastLevel::Info, + Some(2_500), + ); + advance_after_language(app); + } + Err(err) => { + app.status_message = + Some(format!("Failed to save locale: {err}")); + } } } } @@ -4719,8 +4725,7 @@ fn render(f: &mut Frame, app: &mut App) { // background before any sub-widgets render, so cells that end up // uncovered by layout splits (e.g. after file-tree toggle or // resize) don't retain stale content from a previous frame. - Block::default() - .render(chunks[1], f.buffer_mut()); + Block::default().render(chunks[1], f.buffer_mut()); let mut sidebar_area = None;