Both `render_line_with_links` (paragraphs, list items) and the
standalone `wrap_text` (code blocks) were word-based wrappers: when
a single word's display width exceeded the available column budget
they placed the word alone on a line and let it overflow the right
edge of the transcript silently. Long URLs, file paths, commit
hashes, JWTs, and any no-whitespace CJK run all hit this in #1344
and #1351 reports.
The fix mirrors the v0.8.25 table-cell fix (`wrap_cell_text`):
extract the per-character width-aware splitter as a free helper
`push_word_breaking_chars`, and call it from `wrap_text`,
`wrap_cell_text`, and the new char-break branch in
`render_line_with_links`. Each rendered line is now guaranteed to
fit in the requested width; full content is preserved across the
wrapped segments.
Snapshot-style regression tests pin the invariant at widths 40, 60,
80, and 120 — covering 200-char `a`-runs, long URL fixtures,
mixed-short+overlong-word fixtures, and the existing table-cell
property. A regression guard also confirms short words still break
on whitespace (no mid-word breaks for ordinary prose).
Closes#1344 (output-side overflow). Partial fix for #1351 (the
table-cell concern was already fixed in v0.8.25; the long-prompt
input-area concern is a separate visible-window issue, not a wrap
bug — the composer already uses a grapheme-based wrapper).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pager intercepts mouse capture, so terminal-native selection is
disabled inside it. Until now there was no in-app way to copy the
content the user came specifically to see — high-frustration UX gap
for the Alt+V (tool details), Ctrl+O (thinking), shell-job, task,
MCP-manager, and selection pagers.
Both `c` (clipboard convention) and `y` (vim-yank convention) now
emit a `ViewEvent::CopyToClipboard` carrying the full pager body.
The host dispatcher in `ui.rs` writes through `app.clipboard` and
toasts a status confirmation ("Pager content copied" /
"Copy failed"). Empty-body pagers report the empty state instead of
silently no-op'ing.
Footer hint updated to surface the new keys:
j/k scroll Space page Ctrl+D/U half g/G top/bottom / search c copy q/Esc close
Mouse selection inside the pager remains intercepted (the alternative
— releasing capture inside the modal — would break vim-style
navigation), so this is the supported copy path.
Closes#1354.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The user-facing error path already formatted the underlying anyhow
chain with `{err:#}`, but reqwest chains alone read as opaque
fragments ("error sending request: tcp connect: connection refused"
etc.) for users without low-level network experience.
`format_registry_error` now inspects the formatted chain for common
failure signatures and appends a one-line hint:
- DNS lookup / `getaddrinfo` failures
- connection refused / reset / aborted
- TLS handshake / certificate / SSL
- HTTP 404 / 401 / 403 / 429
- request timed out
Each hint points at the most likely cause (network reachability,
trust store, registry URL, rate limit) and a concrete next step.
The original chain is still rendered verbatim above the hint, so
power users keep their detail and casual users get a starting
point.
Closes#1329 (the diagnostic side; the actual root cause is now
diagnosable from the surfaced chain + hint).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The forced-repaint sequence written before each TurnComplete /
focus-gain / resize used to be:
\x1b[r\x1b[?6l\x1b[H\x1b[2J\x1b[3J
which combined with the immediately-following ratatui
`terminal.clear()` produced a double-clear. Terminals that don't
optimize successive clears against the alt-screen buffer (Ghostty,
VSCode integrated terminal, Win10 conhost) rendered the second
clear as a visible blank-then-repaint flicker on every redraw
trigger.
The lighter sequence `\x1b[r\x1b[?6l\x1b[H` resets DECSTBM and DECOM
and homes the cursor (still solving the original viewport-drift fix
that 0.8.22 added) but leaves the pixel-clear to ratatui's diff
renderer. The alt-screen buffer's double-buffering absorbs that
single clear without flicker on every terminal we tested. Terminals
that were already flicker-free (macOS Terminal.app, iTerm2,
alacritty) remain so.
Closes#1119, #1260, #1295, #1352, #1356, #1363, #1366.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously OPENAI_MODEL only set default_text_model which was lower
priority than the provider config model. Now it directly overrides
the openai provider's model field.
Other providers (openai, ollama) should get their model from
config.toml / env vars (e.g. OPENAI_MODEL), not from the global
default_model setting which is DeepSeek-centric.
- Save provider choice to settings.default_provider on switch
- Save model per-provider to settings.provider_models
- On startup, load provider-specific model instead of global default
- Hide DeepSeek models from picker on pass-through providers
- Show friendly message for /models on unsupported providers
Wraps ExecCell and GenericToolCell rendered output with card-rail
glyphs for visual structure, similar to Claude Code's card-style
tool rendering.
- wrap_card_rail(): adds ╭/│/╰ glyphs to rendered lines
- Applied to ExecCell::render and GenericToolCell::lines_with_mode
- Pre-computed caches (output_summary, is_diff) kept from previous
commit for per-frame performance
- Live mode output remains visible (head+tail+omitted), not collapsed
- Card-rail glyphs reused from existing tool_card.rs CardRail enum
Test plan: cargo test -p deepseek-tui (2380 passed, 0 failed)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-compute render caches to avoid re-parsing every frame:
- Add output_summary: Option<String> to ExecCell and GenericToolCell
- Add is_diff: bool to GenericToolCell (cached after first detection)
- Populate caches once in handle_tool_call_complete / orphan path
Live mode rendering simplified to one-line summary + expand affordance:
- GenericToolCell and ExecCell now show a single muted summary line
with "Enter to expand tool output" affordance in Live mode
- Transcript mode still emits full output
- render_tool_output_summary_line truncates to fit terminal width
- Make output_looks_like_diff pub(crate) for pre-computation access
Test plan:
- cargo test -p deepseek-tui (2379 passed)
- config_ui::build_document_reflects_app_state is a pre-existing failure
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Promote COST_EQ_TOLERANCE from a function-local const to a module-level
constant in sidebar.rs.
Add SessionCostSnapshot::total_usd() and total_cny() helpers that
encapsulate session+subagent cost summation, used during session restore.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
# crates/tui/src/session_manager.rs
# crates/tui/src/tui/sidebar.rs
# crates/tui/src/tui/ui.rs
Instead of unconditionally changing Up/Down behavior, gate the
empty-composer-scroll path behind a new `tui.composer_arrows_scroll`
config option (default false). Users whose terminals map trackpad
gestures to arrow keys can opt in via:
[tui]
composer_arrows_scroll = true
When enabled, empty-composer Up/Down scroll the transcript; otherwise
plain arrows always navigate input history (preserving #1117 default).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The CLI dispatcher accepted --yolo but only passed it to Exec(TuiPassthroughArgs),
not to the plain Run(RunArgs) path used for interactive sessions.
Fix: pass DEEPSEEK_YOLO=true env var to the TUI binary. The TUI already
reads this env var (matching DEEPSEEK_SANDBOX_MODE pattern) and sets
allow_shell + start_in_agent_mode + yolo.
Also adds yolo field to CliRuntimeOverrides and ResolvedRuntimeOptions
so the flag propagates through the full resolve chain.
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.
Common footgun: users set api_provider = \"ollama\" (or vllm /
openrouter / etc.) at the top of config.toml and add a top-level
base_url = \"http://my-server\" alongside it. The root base_url field
is only read for DeepSeek/DeepseekCN (and a back-compat sniff for
NvidiaNim) — for every other provider it's silently ignored, and the
user can't figure out why their override doesn't apply.
Add a one-line tracing::warn at config load time pointing the user at
the matching `[providers.<name>]` table or the corresponding
`*_BASE_URL` env var. Skipped if the per-provider table already has
its own `base_url` (which would win anyway).
No behavior change to URL resolution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous one-line error told users to set
DEEPSEEK_ALLOW_INSECURE_HTTP=1 but the env var name is easy to typo
when you're staring at it in a terminal (sam43b in #1303 wrote
"DEEPSEEKALLOWINSECURE_HTTP"). Reformat the message to:
- Note that loopback hosts are auto-allowed (no env var needed)
- Show the env var with underscores explicit and prominent
- Include a one-line copy-pasteable example
No behavior change; same `validate_base_url_security` decisions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The skills prompt renderer was re-sorting every discovered skill by name,
which discarded workspace/source precedence at the last mile. Under a large
global skills set, higher-priority workspace skills from directories such as
`.claude/skills` could be pushed past the prompt budget and disappear from the
model-visible skills list even though discovery had found them correctly.
This keeps stable ordering in discovery and preserves registry order during
rendering, then adds a regression test that proves a workspace-priority skill
survives when lower-priority global skills overflow the prompt budget.
Constraint: Session-time skill rendering must preserve cross-tool/workspace precedence
Rejected: Raise the prompt budget cap | would hide the ordering bug and bloat prompts
Rejected: Special-case `.claude/skills` during rendering | precedence belongs to registry order, not path-specific branches
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not re-sort rendered skills without re-proving precedence behavior under prompt truncation
Tested: cargo test --all-features; cargo fmt --all -- --check; cargo clippy --all-targets --all-features
Not-tested: Manual TUI interaction beyond automated skills prompt and QA PTY coverage
Route DEEPSEEK_BASE_URL through the active provider config instead of leaving
self-hosted providers on their localhost defaults. This makes --base-url work
for Ollama and vLLM while preserving provider-specific env overrides.
On WSL2 with Windows drives mounted at /mnt/c, git snapshot operations
can take 30+ seconds due to the slow 9P filesystem bridge. The original
code sent TurnStarted *after* the snapshot, causing the UI's 30-second
dispatch watchdog to fire with 'Turn dispatch timed out; the engine may
have stopped' before the turn ever appeared to start.
This commit sends TurnStarted immediately before the snapshot, so the
UI shows progress while the snapshot runs in the background.
- Check for both .deepseek and .deepseek/ to prevent duplicates
- Ensure trailing newline before appending to avoid joining with unterminated line
- Add tests for both edge cases
When /init runs inside a git repository, automatically append .deepseek/
to .gitignore so that workspace-local state (instructions, snapshots,
pastes) is not accidentally committed.
The helper ensure_deepseek_gitignored() checks for an existing .git
directory, reads the current .gitignore (if any), and appends the entry
only when it is not already present. Non-fatal if the file cannot be
written (e.g. read-only filesystem).
Includes four tests covering creation, append, idempotency, and
non-git-repo skip behaviour.
Fixes#1326
Sort discovered tools by name in three places so the prompt prefix
the model sees is deterministic across runs regardless of server
pagination order:
- McpConnection::discover_tools — after all pages collected
- McpPool::all_tools — after iterating connections
- McpPool::to_api_tools — final block sent to the model
Adds a regression test that exercises a 2-page paginated discovery
with reverse-ordered tools and asserts the result is sorted.
Adapts the production sort idea from @hxy91819's PR; the test
infrastructure here uses the existing ScriptedValueTransport rather
than introducing a parallel MockTransport with a different trait
signature.
Co-Authored-By: hxy91819 <hxy91819@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Error messages containing environment variable names like
DEEPSEEK_ALLOW_INSECURE_HTTP were mangled by markdown rendering:
the inline _italic_ parser consumed underscored segments (e.g.
_ALLOW_ → italic ALLOW without underscores), resulting in the
illegible DEEPSEEKALLOWINSECURE_HTTP displayed to the user.
Switch HistoryCell::Error from render_message (which routes through
markdown_render::render_markdown_tagged) to wrap_plain_line, which
renders the message body verbatim while preserving the existing
severity label, bold prefix, and continuation-rail layout.
Closes#1303