feat(tui): add configurable external status line (review fixes) - #330
feat(tui): add configurable external status line (review fixes)#330Nick768 wants to merge 8 commits into
Conversation
Add a statusLine feature (analogous to Claude Code's statusLine):
- Config in settings.json: "statusLine": { "command": "...", "pollIntervalMs": 5000, "padding": 1 }
- Backward compat: CLAUDE_STATUS_COMMAND env var still works (500ms poll)
- Session data (model, context, cost) is passed as JSON on stdin
- Rendered as a separate row between prompt input and footer
- ANSI escape sequences stripped for plain-text display
- /cc-statusline command shows current configuration
- #[serde(default)] on Config struct to allow partial config deserialization
- Tests for StatusLineConfig deserialization
- status_line in merge() now always uses base (global) config, never over (project), preventing RCE via project .claurst/settings.json - remove unrelated shell.nix from feature PR - add regression test project_cannot_override_global_status_line
…ately (PR Kuberwastaken#327) - clamp poll_interval_ms to 1000-300000 - timeout kills hung child processes - CancellationToken stops the task on app exit - output capped at 4 KB - initial empty session payload so status script runs on first poll - wait 500ms before first poll so session data is ready - sleep after command execution, so the status line appears immediately when the app starts - fix pre-existing settings_screen test bound (20 -> 21)
Child stderr was inherited while ratatui owns the terminal, so command failures could corrupt the TUI. Pipe stderr to null instead.
Clamp padding to half the available width, use saturating arithmetic, and ensure at least 1 character width to prevent u16 overflow or invalid rect creation.
- total_input_tokens now includes cache tokens (Claude Code compat) - total_output_tokens uses cost_tracker.output_tokens() - added current_usage breakdown (input, output, cache_creation, cache_read) - session_id uses the real tool_ctx.session_id UUID
…astaken#327) - replace naive SGR-only ANSI stripper with proper CSI/OSC/C0 handler - add maxLines config field (default 10) to limit status line height - compute status line height dynamically from actual line count - truncate over-long lines with ellipsis instead of clipping - add tests for max_lines deserialization
|
Thank you for the thorough review on #327! All requested changes have been addressed in this new PR (#330). Changes per review point:
All existing tests pass (505 core, 658 TUI). Happy to iterate further if anything needs adjustment. |
Kuberwastaken
left a comment
There was a problem hiding this comment.
Thanks for the thorough rework — the trust issue from #327 is properly closed (status_line only comes from base in Settings::merge, project files never reach it; env fallback is safe since project env is never applied to the process). Hardening (clamp, timeout+kill, stderr null, 4 KiB cap, control stripping, cancel token) all checks out and tests pass.
A few things before merge:
- clippy
-D warningsfails:render.rs:2938explicit_counter_loopintruncate_with_ellipsis(usechar_indices()/enumerate()),app.rs:7336unnecessary_map_or(test). - Output capture can deadlock:
child.wait()is raced against the 5s timeout but stdout is only read after wait returns (main.rs:2054-2069). A command that writes >64 KiB blocks on the pipe, gets killed every poll, and the line never updates. Read stdout concurrently (wait_with_output()under the timeout, or copy into atake(4096)reader). #[serde(default)]onConfigis a silent schema change — on main a partial"config": {...}fails withmissing field permission_mode; this PR makes every field optional. Probably desirable, but call it out in the PR and add a partial-config parse test (or split it out).- Three
Mutex::lock().unwrap()in prod paths (main.rs:2012, 2028, 3731) — useParkingMutex(already imported in that file) or handle poisoning. entries.len() <= 20→21insettings_screen.rs:840— if the PR adds a settings entry say so, otherwise revert.cc-statuslineis intercepted but not in the slash-command list/help; astatusLineentry in docs/configuration.md is missing. Narrow-terminal/large-padding rendering has no test.
1–4 blocking, 5 can be a follow-up if you prefer. Then lgtm.
|
Thanks for the review, I am looking into it, but give me some time |
|
No worries, take your time :) thank you |
|
Following up on my review above before you push the next round — I found one more thing while re-reading, and it's the most important item on the list, so I'd rather you fix it in the same pass than discover it in round three. Sorry for the extra scope.
Right now { "config": { "hooks": { "UserPromptSubmit": [ { "command": "…" } ] } } }With There's a second edge to it: What I'd like: drop Two corrections to my earlier review while I'm here:
One new perf note: the session-JSON block sits inside the main loop, which polls at 16 ms ( Also two clippy heads-ups for when CI runs (fork PRs need approval here — I'll approve on your next push): To be clear about where this stands: the trust fix from #327 is correct and the hardening work is good — I'm not asking you to redo any of it. It's the one |
No description provided.