Skip to content

variable-width busy status shifts bottom status bar segments; spinner duplicated between status bar and input separator #6649

Description

@bug-ops

Description

The bottom status bar renders the animated busy segment (breeze spinner + activity verb) as the second left-aligned segment, before all metric segments. The humanized verb has variable display width (thinking = 8 cols, searching = 9, compacting · context = 20, …), so every status change makes all segments to its right — skills, tokens, api, cost, etc. — jump horizontally. The effect is constant visual churn on a line that otherwise holds stable session parameters.

Meanwhile the input separator row (one line above the input box) already shows the same breeze spinner while busy — the animation is duplicated across two adjacent lines, but the verb is deliberately shown only in the bottom bar (crates/zeph-tui/src/widgets/input.rs:19-24 documents this split; the wave equalizer in the side panel is a third busy indicator and is out of scope here).

Move the transient busy status (spinner + verb) up to the input separator row — where the spinner already lives — and keep the bottom line for stable parameters only. Nothing in the bottom bar then changes width mid-turn, and the duplication disappears.

Current Behavior

  • Bottom bar: crates/zeph-tui/src/widgets/status.rs:322-350 push_busy_segment (spinner + verb), pushed second after the mode chip (build_segment_lists, status.rs:280-284); every following segment's x-offset includes the verb width (SegmentList::layout, status.rs:118-220). Right-aligned uptime does not move (status.rs:242-262); everything between the verb and uptime does.
  • Separator row: crates/zeph-tui/src/widgets/input.rs:26-40 push_busy_tail appends the breeze spinner + interrupt hint, explicitly without the verb; test input_busy_width_40 (input.rs:288-303) pins the verb's absence.
  • Status text source: free-form AgentEvent::Status(String)SessionSlot::status_label → humanized by status_verbs::humanize (crates/zeph-tui/src/widgets/status_verbs.rs:108-158).

Expected Behavior

Input separator row (busy): you ▸ … ▸▸▹ {verb} · {interrupt hint} … — the humanized verb joins the spinner that is already there. The interrupt-hint text itself is owned by #6646 (esc to interruptctrl+c when that lands); this issue keeps whatever hint is current.

Bottom status bar: the busy segment (spinner + verb) is removed entirely. Remaining left-to-right content — mode chip, [Plan]/Viewing:, skills, tokens, SEC, api, cost, sh:N, cocoon, bg, [SC:N], goal, filters, GRD, tok/s + TTFT, mouse, durable — with right-aligned uptime. Streaming tok/s + TTFT stay in the bottom bar (decided; their appear/disappear shift is accepted for now). The transient Press Ctrl+C again to exit hint from #6646 also stays in the bottom bar as a Priority::Critical segment — it is rare and short-lived, unlike the per-turn busy verb.

Idle rendering of the separator row is unchanged (prompt glyph, mode hint, ~N tokens, queue badges).

Invariants that must keep holding

  • Spinner Rule (specs/011-tui/spec.md:70-81, specs/001-system-invariants/spec.md:219, specs/constitution.md:40): every background/implicit operation shows a visible spinner + short status, cleared on completion. The rule is location-agnostic in constitution/invariants, but 011-tui currently pins "Status Bar | Current operation spinner + short status text" — the spec must be updated to name the separator row as the canonical location (spec change tracked with this issue).
  • specs/026-tui-subagent-management/spec.md FR-010 (spinner animates with the throbber tick) — unchanged mechanically, verify wording.
  • Verb formatting rules (short, present-continuous, only) via status_verbs::humanize — unchanged.
  • No blocking I/O on the render thread; render is a pure function of existing state (status_label moves render site only, no new channels).

Implementation Notes

  • input.rs::push_busy_tail gains the humanized verb; truncate the verb before the hint under width pressure (narrow terminals) — the row already has a width-budget pattern.
  • status.rs::build_segment_lists drops push_busy_segment; remove the now-unused busy plumbing from the status widget.
  • Invert test input_busy_width_40 (verb must now appear) and update status-bar layout tests that assert the busy segment.
  • Motion-off mode (motion=Off renders static · instead of animated frames) must carry over to the separator row rendering of the verb.
  • Coordination: require double Ctrl+C to exit TUI and move agent-cancel from Esc to Ctrl+C #6646 touches the same two widgets (status hint segment, separator interrupt hint). Whichever lands second rebases trivially; semantics do not conflict.

Environment

  • Version: main @ v0.22.3
  • Crate: zeph-tui (feature tui, desktop bundle)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2High value, medium complexityenhancementNew feature or requesttuiTUI dashboarduxUser experience

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions