fix(build): stable-Rust compatibility + silence deferred-code warnings
The match guard at tui/ui.rs:1603 used `&& let Some(...) = ...` inside an `if` guard, which requires the `if_let_guard` nightly feature on Rust < 1.94. Reported by an external user attempting `cargo install deepseek-tui` on stable rustc — it failed with E0658. Rewrite as a plain match guard with a nested `if let` inside the arm body so the language-picker hotkeys compile on every supported rustc. Workspace also now declares `rust-version = "1.88"` to match the codebase's actual reliance on `let_chains` in if/while conditions, so users on too-old toolchains see a clear cargo error instead of a confusing rustc one. `AGENTS.md` and `CLAUDE.md` gain a "stable Rust only" section documenting the trap and how to rewrite around it. Also annotate the deferred `TuiPrefs` (#657) and `handoff::THRESHOLDS` (#667) APIs with `#[allow(dead_code)]` so CI's `-D warnings` flag stays green while the call sites are staged for v0.8.13. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+18
-14
@@ -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<String>,
|
||||
}
|
||||
|
||||
#[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"));
|
||||
}
|
||||
|
||||
|
||||
+24
-19
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user