feat(tui): /statusline composes the bottom chrome, ctx reads at every fullness (#5950) - #5962
Conversation
… 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>
Bugbot couldn't run - usage limit reachedBugbot 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 finished @Hmbown's task in 1m 53s —— View job Review complete
Note: SummaryThis is a clean, well-scoped fix. It does exactly what the description claims:
Provider/model/route safetyNo concerns. This PR only reads Reuse/simplificationNothing to flag — the PR deletes far more than it adds in TestsGood coverage added directly in No issues found that need a fix. |
| 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) | ||
| }) |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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) | ||
| }) |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| ### 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). |
| let mode_chip = mode_chip | ||
| .filter(|_| app.status_items.contains(&crate::config::StatusItem::Mode)) | ||
| .map(|(text, ink)| (text.into_owned(), ink)); |
There was a problem hiding this comment.
🟢 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_itemsactively gate metrics-line segments (info_segments) and the posture bar’s mode chip, so/statuslinechanges are immediately visible. - Always paint
ctx NN%(not only ≥50% fullness), preserving the existing warning threshold styling at 80%+. - Retire inert
StatusItemkeys 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.
| // 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, |
| # balance (prepaid providers only: DeepSeek, DeepSeekCN, OpenRouter, | ||
| # SiliconFlow), cache, tokens, session_metrics. |
| `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 |
| 底部状态行可配置。运行 `/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` 是它下方指标行的片区。 |
There was a problem hiding this comment.
💡 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".
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| if shows(StatusItem::Balance) | ||
| && let Some(balance) = app.balance_cell.lock().ok().and_then(|guard| { |
There was a problem hiding this comment.
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 👍 / 👎.
| // 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| 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", |
There was a problem hiding this comment.
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 👍 / 👎.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
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 touchis_available_fororshould_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()onStatusItem::Modechanges the posture bar, but no new test togglesModeand 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.
| # interactively with `/statusline`. | ||
| # Supported keys: mode, model, context_percent, cost, | ||
| # balance (prepaid providers only: DeepSeek, DeepSeekCN, OpenRouter, | ||
| # SiliconFlow), cache, tokens, session_metrics. |
There was a problem hiding this comment.
[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)) |
There was a problem hiding this comment.
[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.
|
Nice slice — thanks for driving #5950. Small coverage gap I noticed while reviewing: the new |
…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>
) 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>
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/statuslinewas a checklist that changed nothing —status_itemshad exactly one reader in the whole renderer(
should_fetch_provider_balanceintui/ui/provider_routes.rs). And themetrics 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) nolonger drops the reading below 50%. The warning styling keeps the thresholds
it had:
ChromeInk::Infobelow 80%,ChromeInk::Failurefrom 80% up. Thereading is still the row's floor — it sheds after everything else, including
the help hint.
2.
/statuslinedrives the metrics line. Every segment ininfo_segmentsis gated on itsStatusItem, and one item that was not ametrics-line fact is wired where it does live:
StatusItemModelInfoSegmentId::Model(route identity)ContextPercentInfoSegmentId::Context(ctx NN%)CostInfoSegmentId::CostBalanceInfoSegmentId::Balance— new segment, see belowCacheInfoSegmentId::CacheTokensInfoSegmentId::OutputTokensSessionMetricsInfoSegmentId::Ttft+InfoSegmentId::RateModetideline_footer_from_app)Balancepreviously only authorised a background fetch whose result nothingin the chrome painted. It now paints too:
InfoSegmentId::Balance, label fromthe 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 isright 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.rsare untouched (the only changein that file is a one-line
.filter()on the already-optionalmode_chip).3. Retired toggles, with receipts. Eight items were checkboxes that could
not change anything, so they are gone rather than left lying:
ReasoningReplayconfig.rs— never rendered by anythingRateLimit,LastToolElapsedPrefixStabilityGitBranchinfoline.rsmodule 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 indefault_footer, would put a branch on everyone's metrics line by defaultStatusworking 1m 15s), and that clock is #5914/#5920's core statement, not a composable chipAgentsOld config files keep loading:
StatusItem::from_keyreturnsNonefor theretired keys and
deser_status_itemsskips them with a warning, which is thesame forward/backward-compatible path that already existed. A test covers a
pre-#5950
status_itemslist surviving the upgrade.4. No second config key.
tui.status_itemsalready existed, is alreadypersisted by
config_persistence::persist_status_items, and is already loadedin
app/init.rs. It is reused as-is; the issue's proposed[tui.bottom]schema is deliberately not added.
default_footer()gainsContextPercentso the reading is on out of the box.Not in this slice
#5950 also asks for
compact/hiddenpresets for small windows and foromitting
→ effective unavailable/cost: unknownon providers that cannotprice a turn. Neither is here. The cost half is now reachable — a custom
provider can turn the cost segment off in
/statusline— but "omit thesegment automatically when the value cannot be proven" is a separate
behavioural change, and the row-count presets are a layout change.
Verification
New tests:
context_reading_paints_at_every_fullness(10% and 60%, assertedat 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
StatusPickerViewwith key events and re-renders),every_metrics_segment_answers_to_a_status_item(all items on paints everysegment; an empty list paints none), plus a config test for a pre-#5950
status_itemslist still loading.No golden needed re-blessing: the
infoline_*andfooter_*goldens rendersynthetic segments and default status items, and both stayed byte-identical.
The two
one_owner_testsassertions that encoded the 50% silence wereupdated 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
/statuslineandtui.status_itemsactually control the bottom chrome again. After the 0.9.12 shell split, most picker toggles did nothing becauseinfo_segmentsignored the list; each remainingStatusItemnow gates exactly one on-screen element (metrics segments for model, context, cost, balance, cache, tokens, session metrics; posture bar plan/act/operate formode). 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_percentis 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 legacystatus_itemslists, 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.