Skip to content

feat(tui): /statusline composes the bottom chrome, ctx reads at every fullness (#5950) - #5962

Merged
Hmbown merged 1 commit into
mainfrom
feat/bottom-chrome-configurable-5950
Sep 6, 2026
Merged

feat(tui): /statusline composes the bottom chrome, ctx reads at every fullness (#5950)#5962
Hmbown merged 1 commit into
mainfrom
feat/bottom-chrome-configurable-5950

Conversation

@Hmbown

@Hmbown Hmbown commented Sep 6, 2026

Copy link
Copy Markdown
Owner

No-Issue: first slice of #5950 (ctx always visible, /statusline drives the metrics line, dead toggles retired); the row presets and auto-omission of unavailable segments stay open on #5950.

The 0.9.12 shell built the two rows under the composer without ever reading
app.status_items, so /statusline was a checklist that changed nothing —
status_items had exactly one reader in the whole renderer
(should_fetch_provider_balance in tui/ui/provider_routes.rs). And the
metrics line's context reading was gated at 50% fullness, which is most of a
session spent with no context signal at all.

What changed

1. ctx NN% at every fullness. info_segments (tui/ui/frame.rs) no
longer drops the reading below 50%. The warning styling keeps the thresholds
it had: ChromeInk::Info below 80%, ChromeInk::Failure from 80% up. The
reading is still the row's floor — it sheds after everything else, including
the help hint.

2. /statusline drives the metrics line. Every segment in
info_segments is gated on its StatusItem, and one item that was not a
metrics-line fact is wired where it does live:

StatusItem What the toggle now shows/hides
Model InfoSegmentId::Model (route identity)
ContextPercent InfoSegmentId::Context (ctx NN%)
Cost InfoSegmentId::Cost
Balance InfoSegmentId::Balance — new segment, see below
Cache InfoSegmentId::Cache
Tokens InfoSegmentId::OutputTokens
SessionMetrics InfoSegmentId::Ttft + InfoSegmentId::Rate
Mode the posture bar's plan/act/operate chip (tideline_footer_from_app)

Balance previously only authorised a background fetch whose result nothing
in the chrome painted. It now paints too: InfoSegmentId::Balance, label from
the existing MessageId::FooterBalancePrefix (already in all 15 locale packs
— no new user-visible strings in this PR), value from
BalanceInfo::chip_label, shed priority 5 so it outlives the cost, which is
right for a reading that is off by default and only on because its owner asked
for it by name. The same status item still gates the fetch, so the row can
only show a number this session asked for.

The posture bar's permission chip, working clocks (#5914 / #5920) and live
counts stay unconditional — they are the bar's own posture statement, and the
shed ladder and clock order in phase_strip.rs are untouched (the only change
in that file is a one-line .filter() on the already-optional mode_chip).

3. Retired toggles, with receipts. Eight items were checkboxes that could
not change anything, so they are gone rather than left lying:

Retired Receipt
ReasoningReplay zero references outside config.rs — never rendered by anything
RateLimit, LastToolElapsed the enum's own doc said "placeholder until wired" / "(reserved)"; never wired
PrefixStability only reference was a test asserting it stays out of the default footer
GitBranch infoline.rs module doc, 2026-09-02: "the repository and branch moved to the launch header and the git bottom view" — wiring it back would reverse that call and, since it was in default_footer, would put a branch on everyone's metrics line by default
Status the posture bar's turn clock carries the activity word (working 1m 15s), and that clock is #5914/#5920's core statement, not a composable chip
Agents the posture bar's live counts own the agent count, and each count is also the click target that opens the dock

Old config files keep loading: StatusItem::from_key returns None for the
retired keys and deser_status_items skips them with a warning, which is the
same forward/backward-compatible path that already existed. A test covers a
pre-#5950 status_items list surviving the upgrade.

4. No second config key. tui.status_items already existed, is already
persisted by config_persistence::persist_status_items, and is already loaded
in app/init.rs. It is reused as-is; the issue's proposed [tui.bottom]
schema is deliberately not added. default_footer() gains
ContextPercent so the reading is on out of the box.

Not in this slice

#5950 also asks for compact/hidden presets for small windows and for
omitting → effective unavailable / cost: unknown on providers that cannot
price a turn. Neither is here. The cost half is now reachable — a custom
provider can turn the cost segment off in /statusline — but "omit the
segment automatically when the value cannot be proven" is a separate
behavioural change, and the row-count presets are a layout change.

Verification

cargo fmt -p codewhale-tui                                     clean
cargo check -p codewhale-tui --lib --tests                     clean
RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- \
  tui::ui::frame tui::phase_strip tui::views::status_picker \
  tui::infoline localization                                   119 passed, 0 failed
RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- \
  config::tests config_persistence tui::ui::tests::default_footer \
  tui::ui::tests::should_fetch tui::widgets                     708 passed, 0 failed

New tests: context_reading_paints_at_every_fullness (10% and 60%, asserted
at 40, 80 and 160 columns), context_reading_keeps_its_warning_threshold
(10 / 79 / 80), statusline_toggle_removes_its_segment_on_the_next_frame
(drives the real StatusPickerView with key events and re-renders),
every_metrics_segment_answers_to_a_status_item (all items on paints every
segment; an empty list paints none), plus a config test for a pre-#5950
status_items list still loading.

No golden needed re-blessing: the infoline_* and footer_* goldens render
synthetic segments and default status items, and both stayed byte-identical.
The two one_owner_tests assertions that encoded the 50% silence were
updated to assert the reading paints once at every fullness.

Buffer-level evidence only: no run against a real provider and no live
terminal session.

🤖 Generated with Claude Code

https://claude.ai/code/session_0188XYyJaw9Mh9uSrqQBoqhm


Note

Low Risk
TUI presentation and config parsing only; retired keys are ignored rather than failing load, with no auth or data-path changes.

Overview
/statusline and tui.status_items actually control the bottom chrome again. After the 0.9.12 shell split, most picker toggles did nothing because info_segments ignored the list; each remaining StatusItem now gates exactly one on-screen element (metrics segments for model, context, cost, balance, cache, tokens, session metrics; posture bar plan/act/operate for mode). Eight keys that never drove UI (status, agents, reasoning_replay, prefix_stability, git_branch, last_tool_elapsed, rate_limit) are removed from the enum but still parse safely from old configs.

Context and balance behavior changes on the metrics line. ctx NN% is shown at every fullness (not only ≥50%), with warning styling still from 80% up; context_percent is added to the default footer. Balance gets a new metrics segment when enabled—the same toggle still authorizes the prepaid balance fetch—plus updated shed order so balance outlives cost on narrow widths.

Docs, config.example.toml, and tests cover legacy status_items lists, picker integration, and frame invariants.

Reviewed by Cursor Bugbot for commit f088718. Bugbot is set up for automated code reviews on this repo. Configure here.

… fullness (#5950)

The 0.9.12 shell built the two rows under the composer without ever reading
`app.status_items`. The whole renderer had exactly one reader of that list —
`should_fetch_provider_balance` in `tui/ui/provider_routes.rs` — so every
toggle in `/statusline` except the balance fetch changed nothing on screen.
The metrics line also gated its context reading at 50% fullness, which meant
most of a session ran with no context indicator at all.

The context reading now paints from 0%, with the ink thresholds it always
had: Info below 80%, Failure from 80% up. It is still the row's floor and
sheds after everything else, including the help hint.

`info_segments` now gates every segment on its `StatusItem`:

  Model           -> the route segment
  ContextPercent  -> ctx NN%
  Cost            -> the session price
  Balance         -> a new segment (see below)
  Cache           -> cache NN%
  Tokens          -> the output-token count
  SessionMetrics  -> ttft and tok/s

`Balance` previously authorised a fetch whose result nothing in the chrome
painted; it now paints one, labelled from the existing
`MessageId::FooterBalancePrefix` (already in all 15 packs — this change adds
no user-visible strings), valued from `BalanceInfo::chip_label`, shed
priority 5 so it outlives the cost. `Mode` gates the posture bar's
plan/act/operate chip via a one-line filter on the already-optional
`mode_chip`; the permission chip, the working clocks (#5914) and the live
counts stay unconditional and the shed ladder in `phase_strip.rs` is
untouched.

Eight items that could not change anything are retired rather than left
lying: `ReasoningReplay` (no references outside `config.rs`), `RateLimit`
and `LastToolElapsed` (their own docs said "placeholder until wired"),
`PrefixStability` (one test reference), `GitBranch` (the launch header and
git dock own the branch since 2026-09-02), `Status` and `Agents` (the
posture bar's clock and live counts own those facts). Old config files keep
loading: `from_key` returns None for the retired keys and
`deser_status_items` skips them with a warning.

`tui.status_items` is reused as-is — no second key, and the issue's proposed
`[tui.bottom]` schema is deliberately not added. `default_footer()` gains
`ContextPercent` so the reading is on out of the box.

Not in this slice: the issue's compact/hidden row presets, and automatically
omitting an unprovable effort or price. The cost half is now reachable by
turning the cost item off.

Verified:
  cargo fmt -p codewhale-tui                                    clean
  cargo check -p codewhale-tui --lib --tests                    clean
  RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- \
    tui::ui::frame tui::phase_strip tui::views::status_picker \
    tui::infoline localization                                  119 passed, 0 failed
  RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- \
    config::tests config_persistence tui::ui::tests::default_footer \
    tui::ui::tests::should_fetch tui::widgets                    708 passed, 0 failed

No golden needed re-blessing: the infoline and footer goldens render
synthetic segments and default status items, and both stayed byte-identical.
Buffer-level evidence only — no run against a real provider and no live
terminal session.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188XYyJaw9Mh9uSrqQBoqhm
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Copilot AI lite review requested due to automatic review settings September 6, 2026 18:57
@cursor

cursor Bot commented Sep 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_b13fb746-9599-42eb-8093-9330db70780b)

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 1m 53s —— View job


Review complete

  • Gather context (PR body, changed files list)
  • Read diff against origin/main
  • Review correctness (frame.rs, config.rs, infoline.rs, phase_strip.rs, status_picker.rs)
  • Check provider/model/route safety implications
  • Check reuse/simplification and dead-code/migration receipts for retired StatusItems
  • Check test coverage for new/changed behavior
  • Post final review summary

Note: cargo check/cargo test were not run — the sandbox in this session declined the approval prompt for cargo invocations. Everything below is from manual reading of the diff. If you want compile/test confirmation, re-run with --allowedTools covering Bash(cargo *).

Summary

This is a clean, well-scoped fix. It does exactly what the description claims:

  • crates/tui/src/tui/ui/frame.rs: info_segments now gates every metrics-line segment on app.status_items via a shows(StatusItem::X) closure, in the order the PR table promises. The context reading's pct >= 50 gate is correctly replaced by shows(StatusItem::ContextPercent), and the 80% warning-color threshold (ChromeInk::Failure) is untouched — verified against context_reading_keeps_its_warning_threshold (79% → Info, 80% → Failure).
  • The new Balance segment reuses BalanceInfo::chip_label and MessageId::FooterBalancePrefix (no new locale strings, matches the "already in all 15 locale packs" claim) and is gated by the same StatusItem::Balance that authorizes should_fetch_provider_balance (crates/tui/src/tui/ui/provider_routes.rs:303) — confirmed no other place reads that fetch flag.
  • phase_strip.rs: the mode_chip filter is the only change in that file, and it's a one-line .filter() — permission chip, clocks, and live counts are correctly left unconditional, matching the "posture bar's own statement" claim.
  • Retired StatusItem variants (Status, Agents, ReasoningReplay, PrefixStability, GitBranch, LastToolElapsed, RateLimit): grepped the whole crates/ tree — zero remaining references. from_key correctly routes all seven old keys to None, and deser_status_items (config.rs:1722) skips-with-warning rather than failing the whole file. The added regression test (config/tests.rs) proves a pre-feat(tui): make 0.9.12 bottom chrome (posture bar + metrics line) configurable; /statusline is effectively dead; restore always-visible context #5950 config with status, git_branch, rate_limit still parses and drops just those keys, preserving order of the survivors.
  • default_footer() gaining ContextPercent is reflected correctly in both the updated test (ui/tests.rs) and config.example.toml's sample list.
  • Shed priorities in infoline.rs: Balance correctly inserted at priority 5, between Cost (6) and Model/Context (0), matching "the balance outlives the cost" claim. (Note: Cache and Ttft both being 8 is pre-existing, not introduced by this PR — not flagging it.)

Provider/model/route safety

No concerns. This PR only reads ApiProvider/StatusItem for display gating (is_available_for, provider_has_balance_api) — it does not infer or switch provider/model selection from any string or prefix. provider_has_balance_api's allow-list (Deepseek, DeepseekCN, Openrouter, Siliconflow, SiliconflowCn) matches the code; the config.example.toml comment lists "DeepSeek, DeepSeekCN, OpenRouter, SiliconFlow" and omits SiliconflowCn from the prose — cosmetic only, not a behavior bug.

Reuse/simplification

Nothing to flag — the PR deletes far more than it adds in config.rs (-74/+36) by removing dead variants rather than leaving them stubbed, in keeping with the repo's "an abstraction must delete caller code" / migrate-the-last-consumer rules. The Balance gate correctly reuses the existing fetch-authorization item instead of introducing a second flag.

Tests

Good coverage added directly in frame.rs: context_reading_paints_at_every_fullness, context_reading_keeps_its_warning_threshold, statusline_toggle_removes_its_segment_on_the_next_frame (exercises the real picker → event → app.status_items → next-frame path, not just the gating function in isolation), and every_metrics_segment_answers_to_a_status_item (empty list ⇒ empty row). Existing one_owner_tests.rs and ui/tests.rs assertions were updated to match new behavior rather than deleted/weakened.

No issues found that need a fix.

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +149 to +154
if shows(StatusItem::Balance)
&& let Some(balance) = app.balance_cell.lock().ok().and_then(|guard| {
guard
.as_ref()
.and_then(crate::pricing::BalanceInfo::chip_label)
})

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.

🟡 Provider switches show stale balance

Switching between prepaid providers leaves balance_cell holding the prior balance. ProviderPickerApplied never refreshes it, so the new provider displays stale credit until a later turn.

Prompt for agents
The new balance segment reads a provider-agnostic shared balance_cell. Switching providers through ProviderPickerApplied calls switch_provider but does not schedule a balance fetch, and switch_provider does not clear the old value. Even paths that schedule a fetch leave the previous value visible until success, and an older in-flight request can overwrite the cell after a switch. Tie cached balance data and asynchronous results to the provider identity or generation. Clear or suppress mismatched data on every provider switch, trigger a fetch for the new supported provider, and reject late results from prior routes. Add coverage for switching between two balance-capable providers.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +149 to +154
if shows(StatusItem::Balance)
&& let Some(balance) = app.balance_cell.lock().ok().and_then(|guard| {
guard
.as_ref()
.and_then(crate::pricing::BalanceInfo::chip_label)
})

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.

🟡 Balance toggle does not fetch

Enabling Balance only reads balance_cell. StatusItemsUpdated never fetches it, so an idle user sees nothing until completing a turn or switching routes.

Prompt for agents
The balance status item now promises a visible segment, but toggling it only updates app.status_items. The one-shot startup attempt has already run and set balance_initiated even when Balance was disabled, while later automatic fetches occur only on turn completion or selected provider-switch paths. When StatusItemsUpdated transitions Balance from disabled to enabled, schedule an immediate fetch for the active supported provider using the active route credentials and URL. Preserve the existing cooldown and avoid fetching when the item remains disabled.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread CHANGELOG.md
Comment on lines +10 to +25
### Changed

- `/statusline` drives the bottom chrome again. Since the 0.9.12 shell
redesign the posture bar and the metrics line were built independently of
`tui.status_items`, so every toggle in the picker except the balance fetch
was decoration. Each remaining item now shows or hides exactly one thing:
`model`, `context_percent`, `cost`, `balance`, `cache`, `tokens` and
`session_metrics` are metrics-line segments, and `mode` is the posture
bar's plan/act/operate chip. The `status`, `agents`, `reasoning_replay`,
`prefix_stability`, `git_branch`, `last_tool_elapsed` and `rate_limit`
items drove nothing and are retired; an existing `config.toml` still loads
and those keys are ignored (#5950).
- The context reading is back on screen at every fullness. 0.9.12 painted
`ctx NN%` only from 50% up, which left most of a session with no context
signal at all; it now paints from 0% and keeps its warning colour from 80%
up (#5950).

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.

🔍 Branch carries forbidden changelog edits

CONTRIBUTING.md reserves both changelogs for batched commits on main. Remove these hunks to avoid conflicts with concurrent pull requests.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1198 to +1200
let mode_chip = mode_chip
.filter(|_| app.status_items.contains(&crate::config::StatusItem::Mode))
.map(|(text, ink)| (text.into_owned(), ink));

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.

📝 Info: Hidden mode retains its hint

Disabling Mode removes mode_chip but leaves mode_key populated. Current rendering suppresses it indirectly through the absent chip.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copilot AI left a comment

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.

🟢 Approval recommended

The behavior changes are coherent and well-covered by updated/new tests; remaining feedback is limited to minor documentation/log-message clarity.

Pull request overview

Restores /statusline / tui.status_items as the source of truth for composing the two-row bottom chrome in the TUI, ensuring every toggle maps to a real on-screen element and bringing the context reading back at all context fullness levels.

Changes:

  • Make tui.status_items actively gate metrics-line segments (info_segments) and the posture bar’s mode chip, so /statusline changes are immediately visible.
  • Always paint ctx NN% (not only ≥50% fullness), preserving the existing warning threshold styling at 80%+.
  • Retire inert StatusItem keys while keeping old configs forward-compatible by ignoring retired keys on load; update docs/changelogs and adjust/add tests.
File summaries
File Description
docs/zh_hans/GUIDE.md Updates Chinese guide to reflect new bottom-chrome composition and retired keys.
docs/GUIDE.md Updates English guide with the new /statusline ownership model and always-visible context reading.
crates/tui/src/tui/views/status_picker.rs Rewords picker docs/tests to reflect bottom-chrome composition and updated item set.
crates/tui/src/tui/ui/tests.rs Updates default-footer expectations (context reading now on by default; retired items removed).
crates/tui/src/tui/ui/frame/one_owner_tests.rs Adjusts invariants to require context reading at every fullness and on idle frames.
crates/tui/src/tui/ui/frame.rs Implements segment gating by StatusItem, adds a balance segment, and adds targeted regression/integration tests.
crates/tui/src/tui/phase_strip.rs Gates the posture bar’s mode chip on StatusItem::Mode.
crates/tui/src/tui/infoline.rs Adds InfoSegmentId::Balance and updates shed priorities/docs.
crates/tui/src/tui/app.rs Updates status_items field documentation to reflect its new real consumers/ownership.
crates/tui/src/config/tests.rs Adds coverage for legacy configs containing now-retired status_items keys.
crates/tui/src/config.rs Retires inert StatusItem variants, updates parsing/labels/hints, and defines prepaid-provider availability for balance.
crates/tui/CHANGELOG.md Documents /statusline being functional again and always-visible context reading (tui crate changelog).
config.example.toml Updates example config docs for tui.status_items, including retired keys behavior.
CHANGELOG.md Mirrors the same user-facing changelog notes at the repo level.
Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/tui/src/config.rs
Comment on lines +2441 to +2444
// Retired in #5950; skipped rather than rejected so an old
// `config.toml` still parses. See the type's doc comment.
"status" | "agents" | "reasoning_replay" | "prefix_stability" | "git_branch"
| "last_tool_elapsed" | "rate_limit" => None,
Comment thread config.example.toml
Comment on lines +1096 to +1097
# balance (prepaid providers only: DeepSeek, DeepSeekCN, OpenRouter,
# SiliconFlow), cache, tokens, session_metrics.
Comment thread docs/GUIDE.md
Comment on lines +226 to +228
`model`, `context_percent`, `cost`, `balance` (prepaid providers only:
DeepSeek, DeepSeekCN, OpenRouter, SiliconFlow), `cache`, `tokens` and
`session_metrics` are segments of the metrics line below it. Omit
Comment thread docs/zh_hans/GUIDE.md
底部状态行可配置。运行 `/statusline` 选择哪些底部的片区可见,或在 `config.toml` 里设置 `[tui].status_items` 同时控制选择和顺序。
当前支持的键包括 `mode`、`model`、`cost`、`balance`(仅 DeepSeek / DeepSeekCN)、`status`、`agents`、`reasoning_replay`、`prefix_stability`、`cache`、`context_percent`、`git_branch`、`last_tool_elapsed`(保留)、`rate_limit`(保留)、`tokens` 和 `session_metrics`。
省略 `status_items` 以保持内置默认顺序;把它设为 `[]` 以隐藏可配置的片区。
底部区域可配置。运行 `/statusline` 选择哪些内容可见,或在 `config.toml` 里设置 `[tui].status_items`。每个键只对应屏幕上的一样东西:`mode` 是姿态栏的 plan/act/operate 片区,而 `model`、`context_percent`、`cost`、`balance`(仅限预付费提供商:DeepSeek、DeepSeekCN、OpenRouter、SiliconFlow)、`cache`、`tokens` 和 `session_metrics` 是它下方指标行的片区。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f088718f15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +149 to +153
if shows(StatusItem::Balance)
&& let Some(balance) = app.balance_cell.lock().ok().and_then(|guard| {
guard
.as_ref()
.and_then(crate::pricing::BalanceInfo::chip_label)

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 Badge Scope the balance value to the active provider

When balance is enabled, this renders the provider-agnostic balance_cell without checking which provider produced it. After fetching a DeepSeek balance, switching to OpenRouter or SiliconFlow can display the old balance under the new route; the shared 60-second cooldown may skip the replacement fetch, and an earlier in-flight request can also overwrite the cell after the switch. Store the provider identity with the cached value and render it only when it matches the active route.

AGENTS.md reference: AGENTS.md:L69-L69

Useful? React with 👍 / 👎.

Comment on lines +149 to +150
if shows(StatusItem::Balance)
&& let Some(balance) = app.balance_cell.lock().ok().and_then(|guard| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fetch the balance when its status item is enabled

When a user enables balance through /statusline after startup, balance_cell is normally empty because balance is off by default and the one-shot startup fetch was skipped. The status update handler only changes app.status_items, so this new segment remains invisible until an unrelated turn completes and schedules a fetch. Trigger a balance fetch when the item transitions from disabled to enabled so the live preview actually produces the requested reading.

Useful? React with 👍 / 👎.

Comment on lines +93 to +96
// The context reading and the metrics claim the rest of the row;
// the route sheds its own qualifiers first.
let budget = crate::tui::phase_strip::info_route_budget(width);
let fields = crate::tui::phase_strip::route_identity_fields(app, tier, budget)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recompute the model budget from enabled segments

When /statusline disables context, cost, and the other metrics, the model segment still uses info_route_budget(width), which always reserves 60 columns for chrome that may no longer exist. For example, at 80 columns the route gets only 24 columns despite otherwise sharing the row only with the help hint, so route_identity_fields unnecessarily drops the provider and effort fields, leaves unused space, and removes the provider's click target. Base this budget on the segments currently enabled or let the row's shedding pass decide which route fields fit.

Useful? React with 👍 / 👎.

Comment thread crates/tui/src/config.rs
Comment on lines +2476 to +2478
StatusItem::Tokens => "output tokens of the live or last turn",
StatusItem::Balance => "remaining prepaid credit from the active provider",
StatusItem::SessionMetrics => "turns · steps · LLM/tool time · TTFT · tok/s · input",
StatusItem::SessionMetrics => "time to first token and output rate",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Localize the reworded status-picker descriptions

In every non-English locale, the /statusline picker renders these reworded token and session-metrics hints verbatim because StatusPickerView calls item.label() and item.hint() without a locale. Consequently the corrected descriptions introduced here remain English even though the surrounding picker title, instructions, and actions are translated. Add MessageId entries and resolve these strings through the picker's locale rather than returning raw English literals.

AGENTS.md reference: crates/tui/AGENTS.md:L25-L26

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-06T19:06:39.284269Z f088718 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codewhale-agent codewhale-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Codewhale review

PR restores /statusline control over the bottom chrome, adds an opt-in Balance segment, shows ctx at every fullness, and removes inert status items with legacy config compatibility. The core implementation is clean and well-tested; remaining concerns are minor documentation alignment and test coverage gaps.

Findings

  • [INFO] Balance provider list in docs expands without a corresponding provider-gating change (config.example.toml:1097)
    config.example.toml and both guides now list OpenRouter and SiliconFlow as supported balance providers, but the diff does not touch is_available_for or should_fetch_provider_balance. Confirm these providers are already supported by the balance fetch; otherwise users will see a toggle that never fetches/paints for them.
  • [INFO] every_metrics_segment_answers_to_a_status_item does not actually cover every segment (crates/tui/src/tui/ui/frame.rs)
    The test asserts Model, Context, Balance, Ttft, Rate, and OutputTokens, but the fixture never sets session cost or cache-hit tokens, so Cost and Cache gating are not exercised. Either extend the fixture to produce those segments or make the test name/claim match what is covered.
  • [INFO] Mode chip gating in phase_strip has no dedicated toggle test (crates/tui/src/tui/phase_strip.rs:1199)
    The one-line .filter() on StatusItem::Mode changes the posture bar, but no new test toggles Mode and asserts the plan/act/operate chip disappears. Existing tests only exercise the default-on path; a picker/frame test for this branch would close the gap.

Assessment

The implementation is a good first slice of #5950 with correct status-item gating, safe legacy config handling, and solid unit coverage for the core context-reading behavior. The remaining points are low-severity coverage/documentation checks rather than correctness blockers.


Advisory review by Codewhale (codewhale review --pr 5962 --post, head f088718f15fb4bf916034a1b9715932658c30295). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.

Comment thread config.example.toml
# interactively with `/statusline`.
# Supported keys: mode, model, context_percent, cost,
# balance (prepaid providers only: DeepSeek, DeepSeekCN, OpenRouter,
# SiliconFlow), cache, tokens, session_metrics.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Balance provider list in docs expands without a corresponding provider-gating change

config.example.toml and both guides now list OpenRouter and SiliconFlow as supported balance providers, but the diff does not touch is_available_for or should_fetch_provider_balance. Confirm these providers are already supported by the balance fetch; otherwise users will see a toggle that never fetches/paints for them.

// the working clocks (#5914) and the live counts are the bar's own
// posture statement and stay unconditional.
let mode_chip = mode_chip
.filter(|_| app.status_items.contains(&crate::config::StatusItem::Mode))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[INFO] Mode chip gating in phase_strip has no dedicated toggle test

The one-line .filter() on StatusItem::Mode changes the posture bar, but no new test toggles Mode and asserts the plan/act/operate chip disappears. Existing tests only exercise the default-on path; a picker/frame test for this branch would close the gap.

@7jrxt42BxFZo4iAnN4CX

Copy link
Copy Markdown
Contributor

Nice slice — thanks for driving #5950.

Small coverage gap I noticed while reviewing: the new mode gate in tideline_footer_from_app is the one gate without test coverage. tideline_tests.rs builds TidelineFooter directly via the builder (so it bypasses the status_items filter entirely), and the one_owner/new tests all keep status_items at the default (Mode present) — none exercises removing Mode from the list to assert the chip actually disappears from the posture bar. Not a blocker; a one-line assertion (status_items minus Modemode_chip: None) would lock the behavior in.

@Hmbown
Hmbown merged commit 5036b16 into main Sep 6, 2026
39 checks passed
@Hmbown
Hmbown deleted the feat/bottom-chrome-configurable-5950 branch September 6, 2026 21:31
Hmbown pushed a commit that referenced this pull request Sep 6, 2026
…erate derived changelogs

Rebased the batch onto main after #5957/#5958/#5960/#5962/#5967 landed. The
merged [Unreleased] section had grown a second '### Changed' header from the
two-sided conflict resolution; folded into one. Regenerated
crates/tui/CHANGELOG.md (sync-changelog.sh) and web/lib/changelog.generated.ts
(derive-changelog.mjs) so Version drift and the web changelog test pass.

cargo check -p codewhale-tui --lib --tests: clean.
check-feature-release-notes.sh v0.9.12 HEAD: OK.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJrzNAmppg4vt3LNJbaeri
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
pull Bot pushed a commit to soitun/CodeWhale that referenced this pull request Sep 10, 2026
)

The second half of Hmbown#5950; the /statusline composition half landed as Hmbown#5962.

These presets only decide how much of each of the two rows under the composer paints:

  [tui].posture_bar  = full | compact | hidden   (default full)
  [tui].metrics_line = full | compact | hidden   (default full)

Both also settable live with /config posture_bar compact; --save writes the [tui] key, a session-only set says so, and an unknown preset names the three and changes nothing.

compact is not a second renderer: each row keeps its existing shed ladder and the preset starts it at a fixed rung, so what compact keeps is exactly what a narrow row keeps (posture bar starts render_tideline_footer at COMPACT_SHED; metrics line starts shed_pass with every segment at or above SHED_BEFORE_HELP). hidden gives the row to the transcript, resolving info_height/footer_height to 0 exactly as mini mode already did.

Honesty in what the rows claim (respecting Hmbown#5578): a route that cannot prove its effective reasoning tier states no effort field instead of a placeholder that could never resolve (App::provable_reasoning_effort_label is the single gate); the cost segment is omitted only where the route itself cannot be priced (UsageChip::Unknown on BillingPresentation::Unknown) — cost: unknown stays wherever a price could exist.

Absence in an older config.toml means full; an unknown preset is refused at parse time rather than guessed.

Gates (run by the agent that authored this slice, on the pre-rebase tree; CARGO_PROFILE_DEV_DEBUG=0 to pin own artifacts in the shared target dir):
- cargo fmt --all: clean
- cargo clippy --workspace --all-targets --all-features --locked -- -D warnings (standing allowances): clean
- targeted (config, tui::infoline, tui::phase_strip, tui::ui::frame): 1014 passed / 0 failed / 0 ignored
- full cargo test -p codewhale-tui --lib --locked: 11857 passed / 0 failed / 13 ignored

Rebased onto main 9c66003 by the operator; CI is the gate for the rebased tree.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
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.

3 participants