Skip to content

feat(tui): show running shell jobs in footer#2219

Closed
reidliu41 wants to merge 1 commit into
Hmbown:mainfrom
reidliu41:feat/shell-footer-status
Closed

feat(tui): show running shell jobs in footer#2219
reidliu41 wants to merge 1 commit into
Hmbown:mainfrom
reidliu41:feat/shell-footer-status

Conversation

@reidliu41
Copy link
Copy Markdown
Contributor

@reidliu41 reidliu41 commented May 26, 2026

Summary

Fixes #2194

Background shell commands started with task_shell_start were only visible from the Tasks/sidebar view. When the sidebar was hidden or the user was focused on the composer, the footer could look idle even though a shell command was still running.

This adds a shell_jobs footer status item so active background shell work is visible in the footer:

  • one running shell: shell: <command> · <elapsed>
  • multiple running shells: <n> shells running · <elapsed>

Long commands are truncated in the footer to avoid crowding the status line; full details remain available in the Tasks/
sidebar panel.

Saved legacy tui.status_items configs are also upgraded in memory so existing users get the new default Shell jobs chip without manually editing config.

image

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features
  • cargo test --workspace --all-features

Checklist

  • Updated docs or comments as needed
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes

Greptile Summary

This PR surfaces active background shell jobs in the TUI footer so users can see running task_shell_start work without opening the sidebar. A ShellJobs chip renders one running command (truncated to 28 chars) or an N shells running count, each with an elapsed-time suffix drawn from the longest-running job.

  • New StatusItem::ShellJobs variant is added with full metadata and wired into render_footer_from alongside the existing Agents chip, both feeding into FooterProps.agents.
  • with_current_defaults migrates legacy status_items configs by inserting ShellJobs after Agents (or appending it), applied at App::new when a saved config is loaded.
  • Four new integration tests cover single-shell display, multi-shell count, render_footer_from propagation, and the migration path.

Confidence Score: 5/5

Safe to merge. All changes are additive UI-layer additions that do not touch data persistence, auth, or any critical path outside footer rendering.

The new shell status chip is purely additive — no existing behavior is altered, and the migration function only adds to saved configs, never removes. The string-prefix convention shell: used to identify shell tasks is consistent with the existing sidebar code.

crates/tui/src/tui/footer_ui.rs — the test-only footer_auxiliary_spans helper includes shell spans unconditionally, while the production render_footer_from gates them on config.

Important Files Changed

Filename Overview
crates/tui/src/config.rs Adds ShellJobs variant to StatusItem enum with key, description, hint, and a with_current_defaults migration function that inserts ShellJobs after Agents in legacy saved configs. Two unit tests cover both code paths.
crates/tui/src/config_ui.rs Adds ShellJobs to StatusItemValue and the two bidirectional From impls. Mechanical, consistent with the existing pattern.
crates/tui/src/tui/app.rs One-liner addition: pipes saved status_items through with_current_defaults before use, ensuring legacy configs receive the ShellJobs chip automatically.
crates/tui/src/tui/footer_ui.rs Adds footer_shell_status_spans and append_footer_chip helpers; wires ShellJobs into render_footer_from (config-gated) and footer_auxiliary_spans (unconditional). The test-only helper always includes shell spans regardless of the user's config, creating a minor parity gap.
crates/tui/src/tui/ui/tests.rs Adds four well-targeted tests covering single-shell display, multi-shell display, integration through render_footer_from, and the legacy-config migration path.

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Reviews (2): Last reviewed commit: "feat(tui): show running shell jobs in fo..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new ShellJobs status item to display running background shell commands in the TUI footer, along with a new voice_input module for bridging voice-input commands. The review feedback highlights three key areas for improvement: first, ensuring that with_current_defaults does not append the new status item if the user has explicitly configured an empty footer; second, respecting the user's configured ordering of Agents and ShellJobs rather than hardcoding their rendering order; and third, optimizing the character count check in the stderr summary helper to avoid scanning large strings unnecessarily.

Comment thread crates/tui/src/config.rs
Comment on lines +731 to +740
pub fn with_current_defaults(mut items: Vec<StatusItem>) -> Vec<StatusItem> {
if !items.contains(&StatusItem::ShellJobs) {
if let Some(pos) = items.iter().position(|item| *item == StatusItem::Agents) {
items.insert(pos + 1, StatusItem::ShellJobs);
} else {
items.push(StatusItem::ShellJobs);
}
}
items
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If a user has explicitly configured an empty footer (i.e., status_items = [] in their configuration to hide the footer entirely), with_current_defaults will still append StatusItem::ShellJobs to the empty list, overriding their preference. We should check if items is empty and return early to preserve the user's choice of an empty footer.

    #[must_use]
    pub fn with_current_defaults(mut items: Vec<StatusItem>) -> Vec<StatusItem> {
        if items.is_empty() {
            return items;
        }
        if !items.contains(&StatusItem::ShellJobs) {
            if let Some(pos) = items.iter().position(|item| *item == StatusItem::Agents) {
                items.insert(pos + 1, StatusItem::ShellJobs);
            } else {
                items.push(StatusItem::ShellJobs);
            }
        }
        items
    }

Comment on lines +489 to +498
let mut agents = Vec::new();
if has(S::Agents) {
append_footer_chip(
&mut agents,
crate::tui::widgets::footer_agents_chip(running_agent_count(app), app.ui_locale),
);
}
if has(S::ShellJobs) {
append_footer_chip(&mut agents, footer_shell_status_spans(app));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The relative ordering of Agents and ShellJobs is currently hardcoded, meaning Agents will always render before ShellJobs even if the user configures them in the opposite order (e.g., status_items = [shell_jobs, agents]). To respect the user's configured order as described in the StatusItem documentation, we can iterate over items and append the chips in the order they appear.

Suggested change
let mut agents = Vec::new();
if has(S::Agents) {
append_footer_chip(
&mut agents,
crate::tui::widgets::footer_agents_chip(running_agent_count(app), app.ui_locale),
);
}
if has(S::ShellJobs) {
append_footer_chip(&mut agents, footer_shell_status_spans(app));
}
let mut agents = Vec::new();
for item in items {
match *item {
S::Agents => {
append_footer_chip(
&mut agents,
crate::tui::widgets::footer_agents_chip(running_agent_count(app), app.ui_locale),
);
}
S::ShellJobs => {
append_footer_chip(&mut agents, footer_shell_status_spans(app));
}
_ => {}
}
}

Comment thread crates/tui/src/tui/voice_input.rs Outdated
return String::new();
}
let mut summary: String = trimmed.chars().take(300).collect();
if trimmed.chars().count() > 300 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling trimmed.chars().count() scans the entire string to count all characters, which is an $O(N)$ operation. If the command produces a very large amount of stderr output, this can be inefficient. Since we only need to check if the string has more than 300 characters, we can use trimmed.chars().nth(300).is_some() which is much more efficient as it stops scanning after 300 characters.

Suggested change
if trimmed.chars().count() > 300 {
if trimmed.chars().nth(300).is_some() {

@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 26, 2026

great idea - I love this! will work on getting it released asap @reidliu41

Comment thread crates/tui/src/tui/voice_input.rs Outdated
Comment on lines +1 to +10
//! Voice-input command bridge for the composer.
//!
//! CodeWhale stays out of platform microphone APIs here. A configured command
//! owns recording and speech-to-text, writes the final transcript to stdout,
//! and the TUI inserts that transcript into the composer.

use std::path::Path;
use std::process::Stdio;
use std::time::Duration;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Orphaned module — not wired into the module tree

voice_input.rs is never declared with mod voice_input (or pub mod voice_input) in crates/tui/src/tui/mod.rs, so the Rust compiler silently ignores the entire file. The 127 lines of logic and the four unit tests it contains will never run. This looks like an accidental commit from a separate feature branch — either it needs a pub mod voice_input; line added to mod.rs, or the file should be removed from this PR.

Fix in Codex Fix in Claude Code Fix in Cursor

Comment thread crates/tui/src/config.rs
  Add a Shell jobs status item that surfaces active background shell
  commands in the footer. Show the running command and elapsed time for a
  single job, or a count for multiple jobs.

  Also migrate saved legacy status_items at startup so existing users see
  the new default footer chip without manually editing config.
@reidliu41 reidliu41 force-pushed the feat/shell-footer-status branch from 454a678 to 88f955d Compare May 26, 2026 14:23
@Hmbown
Copy link
Copy Markdown
Owner

Hmbown commented May 26, 2026

This conflicts with merged footer changes from #2194 (shell-running chip) and #2166 (user feedback). The functionality overlaps — closing as superseded. Thanks @reidliu41!

@Hmbown Hmbown closed this May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(tui): shell-running status chip in footer

2 participants