Wires the previously-dormant `theme` setting (#657 follow-up) into the
live Settings struct so the choice survives restart. `/theme` opens an
interactive picker with live preview; `/theme <name>` switches and
persists non-interactively.
Most render sites use bare `palette::TEXT_BODY` / `DEEPSEEK_INK` /
`BORDER_COLOR` constants rather than reading `app.ui_theme`, so simply
adding new UiTheme variants only repaints ~20% of surfaces. The fix is
a third stage in ColorCompatBackend (alongside the existing dark<->light
and truecolor<->256 stages) that rewrites every well-known dark-palette
constant to the corresponding UiTheme slot for the active preset. The
remap is a no-op for System / Whale / WhaleLight, so legacy dark/light
flows stay byte-identical.
Settings: theme = system | dark | light | catppuccin-mocha |
tokyo-night | dracula | gruvbox-dark. Unknown values normalise to
system. background_color still overlays on top.
Tests: new coverage in theme_picker and palette; pinned make_app() in
footer tests to ThemeId::System after App::new (matching the existing
default_model pin) since App::new now honors settings.theme.
Two-layer design for users whose UI locale is not English:
1. **System-prompt directive (primary)**: when the user enables
translation via `/translate`, a `## Language Output Requirement`
block is appended to the system prompt instructing the model to
reply in the resolved session locale (Simplified Chinese,
Traditional Chinese, Japanese, or Brazilian Portuguese). Code
identifiers, technical terms without an established translation,
and code blocks the user explicitly requests in English are
exempt. The block is gated on `PromptSessionContext.translation_
enabled`, so it adds zero tokens for installs that don't opt in.
2. **Post-hoc heuristic (fallback)**: a lightweight detector in
`tui::translation` compares Latin-letter count against weighted
CJK characters (CJK chars carry ~3× the information per glyph,
so the ratio comparison stays fair across mixed code+prose).
When a reply still surfaces English despite the directive, the
detector flags it and a focused per-message `client.translate()`
call renders the localised version before display. The dedicated
translation request runs without conversation history, tool
calls, or streaming — the only role is translate-and-return.
Adds the `/translate` slash command, locale strings for the new UI
states, the post-hoc fallback module, the per-message
`TranslationStatus`, and threading through `core::ops`,
`core::engine`, `runtime_threads`, and the TUI app/UI surface.
Trust-boundary check: opt-in only — `translation_enabled` defaults
to false everywhere, so English-locale installs see zero behaviour
change. The system prompt addition is conditional on the runtime
flag, not the contributor's earlier always-on form. Threaded the
new `Locale::ZhHant` arm through the v0.8.32 `/change` slash
command match to keep the pattern exhaustiveness check passing.
Harvested from PR #1462 by @YaYII
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`/change` reads the most recent `## [version]` section from the
workspace `CHANGELOG.md` (or the bundled release-notes copy when
no workspace changelog is available) and renders it inline in the
TUI. On non-English locales the command also queues a model-side
translation request so localised users see the changelog text in
their UI language; with no API key configured, the offline path
returns the section verbatim with a brief explanatory header.
Lets users discover what changed in the version they just upgraded
into without leaving the chat — and keeps the v0.8.32 release-notes
flow consistent with `deepseek update`'s newly-fixed sibling-TUI
refresh: now both binaries match the version, and `/change` shows
what that version actually delivered.
Resolved a `clippy::needless_range_loop` warning in the section
extractor (idiomatic `iter().enumerate().skip(...).find(...)` instead
of an indexed range loop) so the harvest passes the workspace's
`-D warnings` clippy gate without touching the contributor's design.
Harvested from PR #1416 by @zhuangbiaowei
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend /note beyond append-only usage with list, show, edit, remove,
clear, path, and explicit add subcommands.
Keep existing /note <text> behavior compatible, preserve the existing
--- separated file format, and number notes only at display time so the
stored notes stay clean.
Update command help, localization, docs, and tests.
On top of v0.8.26's inter-row spacing for /skills (#1328 from
@reidliu41), the list now also accepts an optional name-prefix
argument so users with crowded skill folders can narrow the view
without scrolling.
/skills → full list (unchanged)
/skills git → only skills whose name starts with "git"
/skills GIT → same (case-insensitive)
/skills nope → "No skills match prefix `nope` (out of 12 …)"
/skills --remote → unchanged
/skills sync → unchanged
/skills --bogus → "Usage: …" error (rejected so future flags
don't silently turn into no-match prefixes)
The match-count header reflects both the matched count and the
registry total, so the user can see at a glance how aggressive
the filter is. Empty match sets explicitly say so and point back
at unfiltered `/skills`. Skill names that start with `-` aren't
allowed by the loader, so reserving the dash prefix for flags is
safe.
Plus the matching usage / description updates in the command
metadata + all four shipping locales (en / ja / zh-Hans / pt-BR)
so /help shows the new argument.
Closes#1318. Thanks @simuusang for the report.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a /feedback command for opening project feedback links.
The command shows a picker when run without arguments and supports direct
bug, feature, and security targets. Bug and feature options open the matching
GitHub issue templates, while security opens the repository security policy.
Add a dedicated /status command that reports the current runtime session state.
The new report shows provider, model, workspace, mode, permissions, session,
context usage, token telemetry, cache telemetry, cost, transcript counts, and
rate-limit availability. /statusline remains available for footer configuration.
Replace the separate /agent, /plan, and /yolo commands with a single
/mode command that can either open a picker or switch directly by name
or number.
This keeps mode switching in one command surface and avoids duplicating
similar commands for each mode.
Merge of PR #1196 by wplll. Adds:
Cache-aware prompt layering:
- PromptBuilder struct separates prompt construction from inspection
- System prompt split into named layers with stability classification
- Layers classified as static/history/dynamic for cache debugging
/cache inspect command:
- SHA-256 hashes of each rendered prompt layer
- Base static prefix hash vs full request prefix hash
- Static prefix stability status across turns
- First-divergence tracking from previous request
Wire payload optimization:
- Tool result budget: large outputs compacted before API request
- Tool result dedup: repeated outputs replaced by compact refs
- Turn metadata dedup: repeated <turn_meta> blocks deduplicated
- Wire-only: local session messages remain unchanged
Project context pack:
- Deterministic workspace summary injected into stable prefix
- Configurable via [context] project_pack = false
Cache warmup and improved footer cache display.
Thanks to wplll for the contribution.
Slash command toggles between the dark and light `UiTheme` presets without round-tripping through `/config`. Useful for quickly matching the editor or terminal theme.
Thanks to @MengZ-super — small, scoped, exactly the right surface for a quick toggle.
Integrates source PR #525 by @shentoumengxin.
Adds `/anchor` for critical user facts that should survive compaction via `.deepseek/anchors.md`. Keeps `/pin` unregistered so the resident-context `/pin` lane remains available, and renders anchors as structured bullets in compaction summaries instead of raw separator text.
Local verification:
- cargo fmt --all -- --check
- cargo test -p deepseek-tui anchor --all-features
- cargo build
CI: all required checks passed on #930.
Co-authored-by: ZZHAsus <3075047037@qq.com>
* feat(commands): add /rename command to set a custom session title
Adds a `/rename <new title>` slash command that lets users set a
human-readable name for the current session. The new title is
persisted immediately to the session JSON file so it appears in
the session picker on the next open.
- Max title length capped at 100 characters (char-count aware, handles CJK)
- Errors on missing/empty arg or no active session
- Inner `rename_with_manager` helper keeps unit tests fully isolated
from ~/.deepseek/sessions
- Localized descriptions in en, ja, zh-Hans, pt-BR
* fix(rename): sync App state before saving to prevent data loss
Use update_session() to merge current in-memory messages and tokens
into the session before writing the renamed title, preventing stale
disk data from overwriting unsaved App state.
* style: format rename command
---------
Co-authored-by: Hunter Bown <hmbown@gmail.com>
Keep auto as a local routing mode, resolve concrete model/thinking before API requests, and wire auto routing through CLI, TUI, runtime threads, and subagents.
Three sources share the /foo namespace with clear precedence:
1. Native built-ins (match block in mod.rs)
2. User-config commands (~/.deepseek/commands/*.md) — checked first
3. Skills (~/.deepseek/skills/) — new fallback in the _ arm
Template substitution: $1, $2, $ARGUMENTS are replaced in user-command
and skill content before the message is sent. Existing exact-match and
alias behavior is unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `sync_registry` to `skills/install.rs` that pulls `index.json` from the
configured `[skills] registry_url`, resolves each entry to a download URL,
checks ETag + SHA-256 for freshness, and writes SKILL.md (or the full
unpacked tarball) into `~/.deepseek/cache/skills/<name>/`. A `.cache-meta.json`
sidecar records the ETag and hash so subsequent syncs skip unchanged skills in
one round-trip.
Wire the new `/skills sync` slash-command in `commands/skills.rs` (dispatched
from `list_skills`) and update the `COMMANDS` usage string in `mod.rs` to
`/skills [--remote|sync]`. The per-skill failure model is non-fatal: the
command prints a per-entry `[+]`/`[=]`/`[!]` summary and returns a final
tally. `default_cache_skills_dir` and the new outcome types are re-exported
from `skills/mod.rs` for downstream consumers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- add /memory help and clearer invalid-subcommand guidance
- register /memory in shared slash-command help
- align memory docs with current behavior and config
- add focused tests for help and discovery
Resolves the post-#514/#517/#518 conflicts:
- CHANGELOG.md: kept both polish-stack and Linux ARM64 entries under
[Unreleased]; reordered so the ARM64/install-message Changed/Docs
sections precede the Releases footer.
- config.example.toml: kept both the `instructions = [...]` example
and the `[memory]` opt-in stanza in sequence.
- crates/tui/src/config.rs: kept both `instructions_paths()` (#454)
and `memory_enabled()` (#489) on the Config impl.
- crates/tui/src/prompts.rs: extended
`system_prompt_for_mode_with_context_and_skills` to take BOTH
`instructions: Option<&[PathBuf]>` and `user_memory_block:
Option<&str>`. Section 2.5a renders instructions; 2.5b renders the
memory block — both above the skills block so KV prefix caching
still wins.
- crates/tui/src/core/engine.rs: thread both args through the two
call sites.
- crates/tui/src/prompts.rs: update the `system_prompt_for_mode_with_context`
forwarder and the test caller to pass `None` for the new arg.
- .gitignore: ignore `.claude/*.local.md` and `*.local.json` so
local ralph / Claude-Code notes can't leak into commits.
Folds in two valid suggestions from the gemini-code-assist review on #519:
- `client.rs`: collapse the duplicated `LlmError → label` match and the
`human_retry_reason` body into a single
`retry_reason_label_and_human(err) -> (&'static str, String)` helper.
- `widgets/footer.rs::retry_banner_spans`: merge the two separate
`match &props.retry` blocks into one that returns both `(label, color)`.
Behavior is unchanged; refactor is a pure DRY win.
The shipped `/hooks list` told users WHAT was configured but
not WHAT they could configure. Without this, the only way to
learn the supported `HookEvent` values is to grep source — not
ideal when most users just want to wire up a notification on
session_end.
Adds `/hooks events` (aliases `event` / `list-events`) which
prints every `HookEvent` variant alongside a short descriptive
blurb (when it fires, current observability-vs-mutation status).
Ordered lifecycle → per-tool → situational so the listing reads
naturally and stays stable across releases.
Updates `CommandInfo::usage` to `/hooks [list|events]` so the
fuzzy autocomplete shows the new subcommand.
Tests:
1 new test (`events_subcommand_lists_every_event_variant_in_documented_order`)
pins the order, the per-event descriptive blurb format, and
exhaustive variant coverage. The existing 6 hooks tests pass
unchanged.
Slash command enumerates configured lifecycle hooks from the
user's `[hooks]` table, grouped by event. The full picker /
persisted enable-disable surface in #460 is still M-sized work;
this MVP gives users a no-typing view of what's actually loaded
— the most-asked question once hooks start firing.
Implementation:
* `crates/tui/src/commands/hooks.rs` formats the hook list with
per-event headings, hook name (or `(unnamed)`), background
marker, timeout, condition summary, and a 60-char shell
command preview.
* `condition_summary` covers every `HookCondition` variant
(Always/ToolName/ToolCategory/Mode/ExitCode/All/Any) so the
listing stays informative for compound conditions too.
* `event_label` maps each `HookEvent` to its config-file string
so the listing matches what the user wrote in TOML.
* New `HookExecutor::config()` accessor exposes the underlying
`HooksConfig` for read-only callers; doesn't open the door
to mutation, which still belongs to the broader #460 work.
* Registered in `commands::COMMANDS` with `aliases: &["hook"]`,
usage `/hooks [list]`, and `MessageId::CmdHooksDescription`
localized in en, ja, zh-Hans, pt-BR.
* Wired into `command_palette::command_runs_directly` so
pressing Enter from Ctrl+K runs `/hooks list` straight.
Tests:
6 unit tests covering preview-cap truncation, newline
stripping, condition-summary variants, event-label
exhaustiveness, and BTreeMap-grouping ordering.
Pairs with `/stash list` and `/stash pop` so the user can fully
manage the stash from inside the TUI without reaching for `rm`.
* New `composer_stash::clear_stash()` returns the number of
entries dropped so the slash command can report it.
Atomic-write replaces the file with empty content; missing /
empty files return `Ok(0)` without erroring.
* `clear` / `wipe` / `drop` are accepted as the subcommand
alias. The "unknown subcommand" hint now lists the three live
subcommands explicitly.
* CommandInfo usage updated to `/stash [list|pop|clear]` so
`/help` and the autocomplete reflect the new option.
* 3 new tests in `composer_stash`: returns-0 when file absent,
returns-0 when file is empty, drops entries and reports count
on a populated stash.
No new dependency; reuses `crate::utils::write_atomic` for the
truncate-and-rewrite.
`deepseek pr 1234` fetches the PR's title, body, base/head, URL,
and full diff via `gh`, then launches the interactive TUI with a
review prompt already typed in the composer. The user can edit
before sending or hit Enter to fire as-is. Falls back gracefully
with an actionable error when `gh` is not on PATH.
Implementation:
* `Commands::Pr { number, repo, checkout }` subcommand. Optional
`--repo <owner/name>` mirrors `gh pr view`'s flag. Optional
`--checkout` opt-in for `gh pr checkout`; default is to leave
the working tree alone since `gh pr checkout` errors out on
dirty trees.
* `run_pr` helper drives three best-effort gh shell-outs
(`pr view --json`, `pr diff`, optional `pr checkout`) and
formats a structured prompt: PR header → URL → branches →
description → fenced ```diff block.
* `format_pr_prompt` caps the diff at 200 KiB with codepoint-
safe truncation so a massive PR doesn't blow the model's
context window before the user even hits Enter.
* New `TuiOptions::initial_input: Option<String>` plumbs the
pre-typed text into `App::new` (which now branches its
composer-state init around the option). Cursor lands at the
end of the seed text. Future callers (welcome screens, share-
link landing pages, etc.) can reuse the same channel.
* `run_interactive` gains an `initial_input: Option<String>`
parameter; existing callers pass `None`.
Tests:
3 new tests in `pr_prompt_tests` cover the happy path
(title/url/branches/body/diff render correctly), empty-input
fallbacks (placeholder for missing title/body/branches/url),
and codepoint-safe truncation when the diff exceeds the
200 KiB cap.
Bulk update: every other `TuiOptions { ... }` test-builder
across the workspace (~21 sites) gains `initial_input: None`
so the new field doesn't break the existing test suite.
The slash command landed in 6fb87 but only via the dispatch
match arm — `/help` and the fuzzy autocomplete consult
`COMMANDS: &[CommandInfo]` to enumerate available commands, and
without a `CommandInfo` entry the new `/stash` was effectively
hidden from discovery.
Adds a `CommandInfo` row with `aliases: &["park"]`, a
`/stash [list|pop]` usage hint, and a new
`MessageId::CmdStashDescription` localized in en, ja, zh-Hans,
pt-BR. The description reminds users that Ctrl+S is the
matching push entry point — both surfaces should reinforce each
other in the help overlay.
No behavior change on the dispatch path; this is pure
discoverability.
A stash is a side-channel from history: it holds drafts the user
parked deliberately instead of submissions made in the past
(which live in `composer_history.rs`).
* `crates/tui/src/composer_stash.rs` — JSONL-backed store at
`~/.deepseek/composer_stash.jsonl`. One JSON object per line
with `ts` (RFC 3339) and `text`. Self-healing parser drops
malformed lines instead of poisoning the file. Multi-line
drafts round-trip intact via JSON's newline escaping. Capped
at 200 entries; oldest pruned at push time. Empty /
whitespace-only text is silently dropped.
* `crates/tui/src/commands/stash.rs` — `/stash list` renders the
stash with one-line previews and timestamps; `/stash pop`
restores the most recently parked draft into the composer
(LIFO) and rewrites the file. `/park` aliases `/stash`.
* Composer Ctrl+S handler in `tui/ui.rs` — pushes the current
draft onto the stash, clears the composer, and surfaces a
toast confirming the action so the no-op-feel doesn't fool
users into thinking nothing happened. Empty composers are a
no-op so a stray Ctrl+S can't pollute the file.
* New `KbStashDraft` keybinding entry registered in the help
overlay; localized in en, ja, zh-Hans, pt-BR.
Tests:
7 unit tests in `composer_stash.rs` cover round-trip, LIFO pop,
empty-on-pop, drop-empty-text, multi-line preservation,
malformed-line resilience, and cap pruning. 4 unit tests in
`commands/stash.rs` cover the preview helper's truncation,
multi-line first-line behavior, and empty-input handling.
The previous commit shipped \`SessionManager::prune_sessions_older_than\`
as a bare helper marked \`#[allow(dead_code)]\` pending phase-2 wiring.
This commit wires it into a user-callable slash command so users can
clean up stale sessions today, and removes the dead-code allow.
### Surface
\`\`\`
/sessions → open the picker (existing)
/sessions show | list | picker → alias for the picker
/sessions prune <days> → drop sessions older than N days
\`\`\`
\`/sessions prune 30\` returns "pruned N sessions older than 30d" or
"no sessions older than 30d to prune". Errors:
- missing arg → usage hint
- non-positive / non-integer arg → typed error
- unknown subcommand → typed error with usage
The prune handler builds a fresh \`SessionManager\` from
\`default_location\` so it reads the same \`~/.deepseek/sessions/\`
directory the persistence layer writes; doesn't take a lock since
it's a one-shot CLI-style operation that runs to completion.
### What changed
- \`commands::session::sessions\` now takes \`arg: Option<&str>\`
and dispatches \`show\` / \`prune\` / unknown.
- New \`prune\` private fn parses the days argument, opens
\`SessionManager::default_location\`, calls
\`prune_sessions_older_than\` with the corresponding \`Duration\`.
- \`commands::COMMANDS\` table updated: usage now reads
\`/sessions [show|prune <days>]\`.
- \`commands::mod\` dispatch arm passes \`arg\` through.
- \`SessionManager::prune_sessions_older_than\` doc comment updated
to reflect the live wiring; \`#[allow(dead_code)]\` removed.
### Tests
5 new tests in \`commands::session::tests\`:
- existing \`test_sessions_pushes_picker_view\` updated to the new
signature
- \`test_sessions_show_subcommand_pushes_picker_view\` —
\`/sessions show\` is an explicit alias for the picker
- \`test_sessions_prune_requires_days_argument\` — missing arg
produces usage hint
- \`test_sessions_prune_rejects_non_positive_days\` — \`0\`,
negative, non-numeric, and decimal inputs are all rejected
- \`test_sessions_unknown_subcommand_errors\` — typo path errors
with subcommand list
### Verification
cargo fmt --all -- --check ✓
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings ✓
cargo test --workspace --all-features --locked ✓ 1865 + supporting
Refines #406 — phase 1.5 (user-callable surface) shipped on top of
phase 1 (helper). Phase 2 (boot-prune + retention policy) stays open
for v0.8.9 once the policy is decided.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a small, opt-in user-memory layer so the model has access to durable
preferences and conventions across sessions, and the user can dump quick
notes without leaving the TUI.
### What ships
- **Hierarchy loader** (#490): on every prompt assembly the engine reads
`Config::memory_path()` (defaults to `~/.deepseek/memory.md`, override via
`memory_path` in config or `DEEPSEEK_MEMORY_PATH`) and injects the file
as a `<user_memory>` block alongside the existing `<project_instructions>`
block. Goes above the volatile-content boundary so prefix-cache stays warm.
Oversize files (>100 KiB) are truncated with a marker.
- **`# foo` composer quick-add** (#492): typing a single line that starts
with `#` (but not `##` / `#!`) appends a timestamped bullet to the memory
file and consumes the input — no turn fires. The composer status line
surfaces the path that was written. Multi-`#` prefixes deliberately fall
through so users can paste Markdown headings.
- **`/memory` slash command** (#491): `/memory` (or `/memory show`) prints
the resolved path and contents inline; `/memory path`, `/memory clear`,
and `/memory edit` (prints `${VISUAL:-${EDITOR:-vi}} <path>`) cover the
rest of the manual-curation surface.
- **`remember` tool** (auto-update): model-callable tool that takes a
`note` string and appends it as a bullet — the same persistence path as
`# foo`. Auto-approved (writes only the user's own memory file). Only
registered when memory is enabled, so it doesn't pollute the catalog when
the feature is off.
- **Opt-in toggle** (#493): default behaviour is off. Enable with
`[memory] enabled = true` in `config.toml` or `DEEPSEEK_MEMORY=on` in
the environment.
### What's wired
- New `crates/tui/src/memory.rs` module (`load`, `as_system_block`,
`compose_block`, `append_entry`).
- New `crates/tui/src/tools/remember.rs` (`RememberTool` + 3 tests).
- New `crates/tui/src/commands/memory.rs` (`memory(app, arg)` handler).
- `EngineConfig` gains `memory_enabled: bool` + `memory_path: PathBuf`.
- `ToolContext` gains `memory_path: Option<PathBuf>`.
- `App` exposes `memory_path` + `use_memory` from `AppOptions` (previously
destructured-and-dropped); `main.rs` populates `use_memory` from
`config.memory_enabled()`.
- `system_prompt_for_mode_with_context_and_skills` accepts an optional
`user_memory_block` parameter; the engine computes it via
`memory::compose_block(...)` and threads it through.
- Composer Enter handler intercepts `# foo` only when
`config.memory_enabled()` is true; otherwise falls through to existing
turn-submission path.
- `MemoryConfig` table (`[memory] enabled`) added to `Config`, surfaced
in `config.example.toml`, plumbed through `merge_config`.
### Tests
- 8 unit tests in `memory::tests` covering `load` (missing / whitespace /
real), `as_system_block` (xml shape, empty input, oversize truncation),
and `append_entry` (creation, repeated append, empty-after-strip rejection).
- 3 unit tests in `tools::remember::tests` covering disabled-state error,
successful append, and missing-`note`-field validation.
### Verification
cargo fmt --all -- --check ✓
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings ✓
cargo test --workspace --all-features --locked ✓ (1821 + supporting; was 1809 on main)
Closes#490#491#492#493
Refines #489 (EPIC parent — phase-1 MVP delivered; phase-2 items
494–497 stay on the v0.9.0 board)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merge the v0.8.6 feature batch and release hardening.\n\nIncludes the full #373-#380/#382-#402 milestone scope, version bump to 0.8.6, secure /share temp-file handling, Windows-safe self-update replacement, and CI portability fixes.\n\nRemote PR checks passed on the final head before merge.
* feat: add config UI support for TUI and web modes
- Introduced a new `config_ui.rs` module to handle configuration UI for TUI and web.
- Updated `TuiOptions` and `App` structures to include `config_path` and `config_profile`.
- Implemented functions to build and apply configuration documents.
- Added tests to ensure the new configuration UI behaves as expected.
- Integrated web configuration session handling into the event loop.
- Updated various modules to accommodate the new configuration options and UI.
* refactor(tui): remove local path reference for schemaui dependency
Remove the local file system path reference for schemaui in favor of
using the published crate from the registry. This change updates the
Cargo.toml to use only the version specification and adds the source
and checksum information to Cargo.lock.
* fix: add AGENTS.md guide and improve config error handling
- Add comprehensive AGENTS.md file with project instructions for AI
assistants, including build commands, dependencies, and GitHub
operations guidance
- Introduce is_error field to CommandResult struct for better error
tracking
- Refactor config application logic to properly handle errors using
the new is_error flag
- Add test utilities for WebConfigSession to support testing
- Optimize web config event polling by extracting drain logic into
separate function
- Add unit tests for session-only config application and engine sync
requirements
* fix(security): add SSRF protection to fetch_url (#261)
Block private, link-local, and cloud metadata IPs in fetch_url HTTP requests. Co-authored-by: JasonOA888
* test(portability): inject paths instead of mutating HOME (Windows fix)
CI's `Test (windows-latest)` job failed because both my new tests
(composer_history and the spawn_supervised crash-dump test) mutated
HOME to redirect `dirs::home_dir()`. That works on macOS / Linux but
not on Windows, where dirs::home_dir() reads USERPROFILE / queries
SHGetKnownFolderPath rather than HOME.
Fix: refactor both modules to expose path-injecting helpers so tests
never need to touch the env var:
- composer_history: split load_history / append_history into thin
wrappers around load_history_from(&Path) / append_history_to(&Path).
Tests use the *_to / *_from form with a tempdir path.
- utils::write_panic_dump: same pattern — write_panic_dump_to(&Path)
takes the crash dir directly. The spawn_supervised end-to-end test
splits into two: one verifies panic-doesn't-propagate (no on-disk
side effect needed), one verifies write_panic_dump_to writes the
expected log format.
Production callers continue to use the env-driven default (`HOME`/
`USERPROFILE` via `dirs::home_dir()`) so no behavior change. Tests
work identically on every platform now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tui): clear chat area each frame so stale cells don't bleed into sidebar
ChatWidget's render path was `Paragraph::new(lines).render(content_area, buf)`
with no Block and no Clear — ratatui's Paragraph only writes cells that
contain text, leaving any cell the current frame's paragraph doesn't
touch holding the *previous* frame's contents. With wide tool output
(`gh pr list`, `git log`) emitting ISO-8601 timestamps like
`2026-05-02T07:29:24Z`, then a subsequent shorter-paragraph frame, the
old timestamp tails (`:24Z`, `7:29:24Z`, etc.) persisted on the right
edge of the chat area, visually colliding with the section headers in
the sidebar (`Plan` rendering as `:24Zan`, `Agents` as `:24Zents`).
Fix: render `Clear` over the full content_area before drawing the
Paragraph. Cheap (one buffer-fill per frame) and guarantees stale cells
can never persist into the next frame's render.
Reported in v0.8.5 testing right after install. The other v0.8.5
bordered widgets (composer, sidebar sections, footer) already render
into a Block with a solid background style, so they were never
affected — only the chat area used a bare Paragraph.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(theme): vendor + theme schemaui to deepseek navy palette (config UI)
The schemaui-0.12.0 crate the contributor brought in via #365 ships
hardcoded Color::Gray / Color::DarkGray / Color::White / Color::Yellow
references across its rendering components. Visually it clashed with
the rest of deepseek-tui — the editor area read as gray-on-black on a
TUI that's otherwise navy ink + sky accents. Two ship-day options
weren't acceptable: defaulting back to the legacy modal lost the new
editor's UX, and living with gray was off-brand.
This commit forks schemaui at 0.12.0 into vendor/schemaui-0.12.0 and
themes the rendering layer to match deepseek-tui's palette. The patch
is wired in via a workspace-level [patch.crates-io] override so the
deepseek-tui Cargo.toml continues to depend on `schemaui = "0.12.0"`
and would automatically resolve back to crates.io if we ever drop the
override (e.g. once upstream lands a ColorTheme API).
Changes inside the vendored fork:
- New `src/deepseek_palette.rs` with the brand RGB values:
SURFACE_INK / SURFACE_RAISED for backgrounds, BORDER_DIM /
BORDER_ACTIVE for chrome, TEXT_PRIMARY / TEXT_MUTED / TEXT_DIM,
ACCENT_SKY / ACCENT_BLUE / ACCENT_PURPLE, and STATUS_OK / WARN /
ERROR. Values mirror crates/tui/src/palette.rs in the workspace.
- `src/lib.rs` exposes the palette module under `cfg(feature = "tui")`.
- `src/tui/view/frame.rs::draw` paints a navy backdrop across the
full frame area before any child widget renders, so any cell that
doesn't get explicitly written reads as ink instead of the terminal
default.
- `tabstrip.rs`, `overlay.rs`, `popup.rs`, `body.rs`, `sections.rs`,
`footer.rs`, `help.rs`, `fields.rs`: every Color::Gray / DarkGray /
White / Yellow / Cyan / Blue / Magenta / Red / Green / LightBlue
swapped out for a deepseek_palette token, plus explicit `bg(...)`
fills on the top-level Block styles and Paragraph wrappers.
- `Cargo.toml` adds an empty `[workspace]` so the vendored crate
builds standalone (its dev-deps don't drift into ours).
Workspace-level changes:
- `Cargo.toml` adds `[patch.crates-io] schemaui = { path =
"vendor/schemaui-0.12.0" }`. Production deepseek-tui builds pick up
the themed fork transparently.
- `.gitignore` excludes `vendor/.../web/ui/node_modules/` (15 MB of
npm artefacts the Rust build doesn't need) and the vendored
Cargo.lock (regenerated locally per build).
Verification:
- cargo build --workspace --all-features: clean
- cargo clippy --workspace --all-targets --all-features --locked: clean
- cargo test --workspace: 1777 passed, 0 failed
- /config inside `deepseek` now opens a navy-themed editor matching
the rest of the TUI; tabs, body panel, footer, popup, and help
overlay all read on brand.
Future work tracked separately: upstream a `with_theme(ColorTheme)`
builder API to schemaui so we can drop the fork. Until then, sync the
fork against new schemaui releases when we want their fixes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "feat(theme): vendor + theme schemaui to deepseek navy palette"
This reverts ed597ccc — vendoring 28,913 lines of schemaui to recolor
a config editor was the wrong tradeoff. Maintenance cost for a
cosmetic match wasn't worth it, and the recolor wasn't even fully
working (terminal-default bg kept bleeding through Style::default()
calls in the form fields).
The simpler path: keep the schemaui-driven editor available as
`/config tui` for users who want the form-style UX, but make bare
`/config` open the legacy native modal that already matches the
deepseek-tui navy chrome by inheritance. No fork, no vendored copy,
no ongoing sync burden.
Changes:
- `git rm -r vendor/schemaui-0.12.0/` (28,913 lines gone)
- Drop `[patch.crates-io]` from workspace Cargo.toml — schemaui
resolves back to crates.io v0.12.0 unmodified.
- Drop the corresponding `.gitignore` exclusions (no more vendor dir
to filter).
- `config_ui::parse_mode` default mode flipped from `Tui` to `Native`.
Bare `/config` → legacy navy modal. Explicit `/config tui` → the
contributor's schemaui editor (still available, gray-on-default
chrome, but opt-in). `/config web` and `/config <key>` /
`/config <key> <value>` unchanged.
- Help text updated to list `[native|tui|web]` in that order.
Verified: cargo build / clippy --workspace --all-features --locked
with -D warnings: clean.
The contributor's work (#365) ships and gets credit; users discover
the alternate editor via the help text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(tui): paint chat area with explicit navy ink instead of Clear
The Clear-instead-of-fill in 0ae2cead reset cells to the terminal's
default background, which read as a brown-gray on most user setups
even though the rest of the TUI chrome is navy. Replace the Clear
with an explicit Block fill at palette::DEEPSEEK_INK, and pass the
same bg through to the Paragraph itself so streamed text cells
inherit ink rather than bouncing back to terminal default.
Net effect: the chat area visually unifies with the sidebar /
composer / footer instead of showing as a contrasting brown-gray
panel in the middle of an otherwise navy frame.
Stale-cell guarantee from #372-followup is preserved — the Block
fills every cell in the area on each frame, so wide tool output
(`gh pr list` ISO timestamps, etc.) still can't bleed past the
current frame's actual text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(config): update tests for Native default + fix default_model override in session-only apply
- Update test_show_config_defaults_to_native and
execute_config_opens_config_view_action to expect
OpenConfigView (Native) instead of OpenConfigEditor(Tui),
matching the parse_mode default change from ce98f054.
- Fix apply_document bug where default_model was processed
in the main key-value loop after model, causing
set_config_value('default_model') to overwrite the
runtime model. default_model is now only applied when
persist=true, preventing session-only edits from being
silently reverted.
* style: cargo fmt
* chore: remove end-of-night report (session artifact)
---------
Co-authored-by: unic <yuniqueunic@gmail.com>
Co-authored-by: Jason <jason@aveoresearchlabs.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: YuniqueUnic <YuniqueUnic@users.noreply.github.com>
Add config_command(app, arg) that dispatches three paths:
/config (no args) -> opens interactive editor (existing behavior)
/config <key> -> shows current value of a single setting
/config <key> <value> -> sets value via existing set_config_value
Keys like model, approval_mode, locale, auto_compact, calm_mode,
show_thinking, mode, max_history, sidebar_width, sidebar_focus,
composer_density, composer_border, transcript_spacing are all read
live from App state for the /config <key> display path.
Unknown keys show a helpful error referencing /help config.
Closes the gate the maintainer set for v0.8.5: every / command, /help,
and /settings should look perfect in both English and Chinese before
multi-agent work begins. v0.8.4 shipped Phase 1a/b/c (88 MessageIds)
but four mixed-language gaps remained:
1. **Keybinding descriptions** (41 entries) — the help overlay showed
translated section labels (Phase 1c) over English description text.
`KeybindingEntry` now carries `description_id: MessageId` instead
of a raw `&'static str`; all 41 descriptions translated to
en/ja/zh-Hans/pt-BR.
2. **Settings: header** — `Settings::display` now takes a `Locale`
and resolves the title via `MessageId::SettingsTitle`. The
field-name keys (auto_compact, calm_mode, etc.) intentionally stay
English — they are the literal TOML keys users edit.
3. **/home dashboard** — entirely English before. ~25 lines of section
headers, mode tips, and quick-action hints translated. Path
interpolations route through `display_path` (privacy invariant).
4. **/help <topic>** text command — the inline labels `Usage:` and
`Aliases:` plus the `Unknown command:` fallback all use tr().
Also adds three buffer-render tests confirming the help overlay /
settings / home dashboard render in zh-Hans without missing markers
or English bleed-through.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>