Skip to content

feat(tui): compact and hidden presets for the bottom chrome (#5950) - #5973

Open
Hmbown wants to merge 3 commits into
mainfrom
feat/bottom-chrome-presets-5950
Open

feat(tui): compact and hidden presets for the bottom chrome (#5950)#5973
Hmbown wants to merge 3 commits into
mainfrom
feat/bottom-chrome-presets-5950

Conversation

@Hmbown

@Hmbown Hmbown commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Closes #5950

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

What this adds

[tui].posture_bar and [tui].metrics_line each accept full | compact | hidden (default full), also settable live via /config posture_bar compact (--save writes the [tui] key; a session-only set says so; an unknown preset names the three and changes nothing; absence in an older config.toml means full).

compact is not a second renderer. Each row keeps its existing shed ladder; the preset only starts it at a fixed rung — what compact keeps is exactly what a narrow row keeps:

  • posture bar: render_tideline_footer starts at COMPACT_SHED — clocks, hint and live counts go first; the permission chip, mode chip and cap warning stay.
  • metrics line: shed_pass starts with every segment at or above SHED_BEFORE_HELP — route, context reading, cost and balance stay; telemetry goes.

hidden gives the row to the transcript (info_height/footer_height → 0, exactly as mini mode already did); no other row moves, so the composer is never displaced.

Honesty (#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, because there the words are a reading, not noise.

Gates

  • cargo fmt --all: clean
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings (standing allowances): clean
  • Targeted (config, infoline, phase_strip, ui::frame): 1014 passed / 0 failed / 0 ignored
  • Full cargo test -p codewhale-tui --lib --locked: 11857 passed / 0 failed / 13 ignored

Authored and gated by an agent session (CARGO_PROFILE_DEV_DEBUG=0 to pin own artifacts in the shared target dir); rebased onto current main (9c66003ff) and pushed by the operator. CI is the gate for the rebased tree.

This slice was carried to PR by the takeover session after its authoring agent terminated post-commit.


Devin Review

Note

Low Risk
Changes are confined to TUI layout presets and display-only metrics text, with broad unit coverage and no auth or billing execution paths touched.

Overview
Adds [tui].posture_bar and [tui].metrics_line presets (full | compact | hidden, default full), wired from config.toml, app init, and live /config … --save. hidden sets the posture/metrics row heights to zero so the transcript keeps those lines; compact does not add a second renderer—it starts each row’s existing shed ladder one rung in (posture: permission/mode/cap warning only; metrics: route, context, cost/balance without telemetry or the help hint).

The bottom chrome also stops stating unprovable facts: routes that cannot prove an effective reasoning tier omit the effort field in the metrics route segment (via provable_reasoning_effort_label) instead of high→effective unavailable, while /status still shows the full label. Session cost is dropped from the line only when BillingPresentation::Unknown; cost: unknown remains on metered routes that simply lack a session reading.

Docs/changelog and targeted tests cover config parsing, infoline/phase_strip shedding, composed frame layout, and the effort/cost honesty rules.

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

The second half of #5950; the /statusline composition half landed as #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 #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>
Copilot AI lite review requested due to automatic review settings September 7, 2026 03:28
@cursor

cursor Bot commented Sep 7, 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_da6e0645-9b61-4ae8-b42a-1ee938c5fd47)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

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-07T03:35:31.310966Z 8a6288f 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.

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 2m 37s —— View job


Review: compact/hidden presets for the bottom chrome (#5950)

  • Gather context (PR body, changed files list)
  • Read the diff (git diff origin/main...HEAD)
  • Review config.rs / config tests (posture_bar / metrics_line presets)
  • Review commands/groups/config/config.rs (/config command handling)
  • Review infoline.rs and phase_strip.rs (shed ladder start rungs)
  • Review ui/frame.rs (info_height/footer_height hidden logic, cost/effort honesty)
  • Review app.rs / init.rs (wiring)
  • Check provider/model safety implications
  • Post final review

Summary

Clean, well-scoped implementation. No correctness bugs found.

  • ChromeRowPreset (crates/tui/src/config.rs:1877-1922) is a straightforward full | compact | hidden enum, defaults to Full, case-insensitive from_setting, symmetric as_setting. Serde parsing correctly rejects unknown values instead of silently defaulting (verified by tui_config_parses_bottom_chrome_row_presets).
  • /config posture_bar|metrics_line (crates/tui/src/commands/groups/config/config.rs:2387-2420) follows the existing session-only-vs---save pattern used by other keys; an invalid value returns an error and leaves app.posture_bar/app.metrics_line unchanged (test asserts this explicitly). No duplicate match arms.
  • compact is genuinely "the existing shed ladder, started later," not a second renderer — verified the rung math by hand:
    • infoline.rs: SHED_BEFORE_HELP = 7; the compact filter keeps shed_priority() < 7, i.e. Model/Context (0), Balance (5), Cost (6) survive, while OutputTokens (7), Ttft/Cache (8), Rate (9) and the help hint are dropped up front — matches the stated "route, context, cost, balance stay; telemetry goes."
    • phase_strip.rs: COMPACT_SHED = SHED_COUNTS (4); at that starting rung the permission chip (sheds at 8) and mode chip (sheds at 7) always survive, the turn/session clocks (1/2) and counts (4) are already gone, and the hint only survives if at_context_cap() bumps its rung to SHED_CAP_WARNING (5) — matches "clocks, hint and counts go first; permission, mode, cap warning stay."
  • hidden reuses the existing info_height/footer_height zeroing path that mini mode already used (frame.rs:1303-1330), and every downstream consumer of those heights gates on the numeric value rather than the mini flag, so hidden truly doesn't move the composer or other rows — confirmed by row_presets_reclaim_rows_and_quiet_them_in_the_composed_frame.
  • Honesty changes (Cost surfaces only move at TurnComplete — long agentic turns show no $ at all #5578) are narrow and correctly scoped: provable_reasoning_effort_label() only suppresses the effort field when effective_reasoning_effort_for_active_route is Unavailable; /status still calls the full reasoning_effort_display_label() so the complete story isn't lost, just kept off the compact row. session_cost_label only omits the cost segment when BillingPresentation::Unknown (route itself unpriceable), preserving cost: unknown for Metered routes lacking a reading this session. This is pre-existing BillingPresentation classification logic, unmodified by this PR — no provider-prefix inference introduced.
  • Provider/model safety: no code here infers a provider/model switch from a wire-id prefix or freeform text; the reasoning-effort and billing branches key off explicit ApiProvider/BillingPresentation values already computed elsewhere.
  • Tests: strong coverage — config round-trip, /config live+persist+reject, InfoLine compact behavior across a width sweep, posture-bar compact across a width sweep including the cap-warning case, and a full-frame integration test covering all four combinations of hidden/compact on both rows. Nothing to add here.

Minor (non-blocking) nit

  • App::provable_reasoning_effort_label() (crates/tui/src/tui/app.rs:6238-6243) calls effective_reasoning_effort_for_active_route(self.reasoning_effort) once for the Unavailable check, then reasoning_effort_display_label() recomputes the same call internally. It's pure/cheap (no I/O), so this isn't a bug — just a small duplicate computation that could be avoided by threading the already-computed effective value into a label-only helper, if it's ever worth the churn.

Good documentation throughout (module docs, CHANGELOG in both root and crates/tui, config.example.toml, docs/GUIDE.md + zh_hans translation) and the "compact is not a second renderer" invariant is honestly reflected in the code rather than just asserted in the PR description.

@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 2 potential issues.

Devin Review

crate::route_billing::UsageChip::PricedSubtotal { .. }
| crate::route_billing::UsageChip::Unknown => {
UsageChip::Money(amount) => Some(amount.clone()),
UsageChip::Unknown if app.billing_presentation == BillingPresentation::Unknown => None,

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.

🟡 Earlier unknown costs disappear

After an unpriced metered turn, switching to an unclassified route makes session_cost_label hide the session-wide unknown cost. Earlier billable usage remains unpriced.

Prompt for agents
Preserve the cumulative session-cost contract when deciding whether to omit an Unknown chip. session_cost_label currently checks only app.billing_presentation, which describes the active route, while App::cumulative_usage_chip returns Unknown for historical unpriced or legacy turns across the whole session. Distinguish an empty/current unpriceable route from unknown coverage already accumulated on earlier priceable routes, and omit only the former. Add a route-switch regression case: record an unpriced metered turn, switch billing_presentation to Unknown, and verify the metrics line still reports unknown cumulative cost.
Devin Review

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

Comment thread CHANGELOG.md
Comment on lines +32 to +39
- The bottom chrome no longer prints facts it cannot stand behind. A route
that cannot prove its effective reasoning tier — typically a custom
OpenAI-compatible gateway with no endpoint receipt — states no effort
field in the metrics line rather than the placeholder
`high→effective unavailable`; `/status` still tells the full story. The
cost segment is omitted only where the route itself cannot be priced
(`cost: unknown` stays on metered routes that merely lack a reading this
session, per #5578) (#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.

🔍 Remove branch changelog edits

The contribution rules reserve both changelogs for batched updates on main. This PR modifies both files and creates avoidable merge conflicts.

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 change is well-scoped to TUI/config behavior with strong targeted tests, and the only issue found is a small doc-comment mismatch.

Pull request overview

Adds configurable size presets for the TUI’s two bottom-chrome rows (posture bar + metrics line), enabling compact/hidden modes without changing /statusline composition, and tightens the “honesty” rules so the chrome avoids rendering facts it cannot substantiate.

Changes:

  • Introduces [tui].posture_bar and [tui].metrics_line presets (full|compact|hidden) with live toggling via /config … and optional persistence via --save.
  • Implements compact by starting existing shed ladders at a later rung (no second renderer), and implements hidden by zeroing row height to return space to the transcript.
  • Omits unprovable reasoning-effort fields and omits the cost segment only for unpriceable routes, while keeping other surfaces’ reporting intact.
File summaries
File Description
docs/zh_hans/GUIDE.md Documents the new bottom-chrome row presets in Simplified Chinese.
docs/GUIDE.md Documents the new bottom-chrome row presets and their semantics.
crates/tui/src/tui/ui/tests.rs Updates UI test fixtures to include the new config fields.
crates/tui/src/tui/ui/frame/one_owner_tests.rs Adds frame-level tests for compact/hidden row preset behavior.
crates/tui/src/tui/ui/frame.rs Applies compact/hidden presets in layout/render; adds “honesty” tests for effort/cost display.
crates/tui/src/tui/phase_strip/tideline_tests.rs Adds posture-bar compact-mode tests to ensure only “posture” facts remain.
crates/tui/src/tui/phase_strip.rs Omits unprovable effort in route identity; adds compact start rung support to TidelineFooter shedding.
crates/tui/src/tui/infoline/tests.rs Adds compact-mode tests to ensure telemetry/help drop before width shedding.
crates/tui/src/tui/infoline.rs Adds compact-mode support to the infoline shed pass and documentation.
crates/tui/src/tui/app/init.rs Loads posture_bar / metrics_line from config with Full defaults when absent.
crates/tui/src/tui/app.rs Adds posture_bar / metrics_line fields and the provable-effort gate helper.
crates/tui/src/lib.rs Updates embedded config/test fixtures to include the new TUI config fields.
crates/tui/src/config/tests.rs Adds parsing tests for the new ChromeRowPreset and backward compatibility (absent => full).
crates/tui/src/config.rs Adds TuiConfig fields and defines ChromeRowPreset (`full
crates/tui/src/commands/groups/config/config.rs Adds `/config posture_bar
crates/tui/CHANGELOG.md Documents the new presets and the bottom-chrome “honesty” changes at the crate level.
config.example.toml Documents the new [tui] keys and their allowed values/usages.
CHANGELOG.md Documents the new presets and “honesty” changes in the repo-level changelog.
Review details
  • Files reviewed: 18/18 changed files
  • Comments generated: 1
  • 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 on lines +17 to +20
/// The session cost as the metrics line prints it — the same price string
/// `/cost`, the roster's right column and the price widget print
/// (SHELL-DESIGN-20260901 §2.11 item 5). Empty until the session has a
/// priced or counted turn.

@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: 8a6288f531

ℹ️ 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".

crate::route_billing::UsageChip::PricedSubtotal { .. }
| crate::route_billing::UsageChip::Unknown => {
UsageChip::Money(amount) => Some(amount.clone()),
UsageChip::Unknown if app.billing_presentation == BillingPresentation::Unknown => None,

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 Preserve unknown cost for unclassified billing routes

When a paid route uses an unrecognized gateway or lacks enough credential/product evidence, BillingPresentation::Unknown means its billing basis is unknown, not that it cannot incur a charge. In that scenario cumulative_usage_chip() deliberately returns UsageChip::Unknown, but this arm removes cost: unknown from the metrics line entirely, hiding the only persistent indication that session cost is indeterminate. Keep the unknown chip unless there is affirmative Local or subscription evidence.

AGENTS.md reference: crates/tui/AGENTS.md:L8-L11

Useful? React with 👍 / 👎.

Comment on lines +2389 to +2391
return CommandResult::error(format!(
"{row_key} must be one of: {}",
crate::config::ChromeRowPreset::SETTINGS.join(", ")

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 Route the new preset feedback through localization

For every non-English UI locale, invalid preset feedback—and the success/session-only responses later in this branch—remains hard-coded English. These user-visible command messages should use tr(locale, MessageId::...), leaving only setting names and command tokens composed in code.

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

Useful? React with 👍 / 👎.

Comment on lines +2394 to +2398
if row_key == "posture_bar" {
app.posture_bar = preset;
} else {
app.metrics_line = preset;
}

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 Apply runtime state only after a successful save

When /config posture_bar ... --save or /config metrics_line ... --save targets an unwritable or malformed config file, these assignments occur before persist_table_string_key can fail. The command therefore reports an error while silently applying the requested preset for the current session. Persist first and update the app only on success, or restore the previous value on failure.

Useful? React with 👍 / 👎.

// rather than `high→effective unavailable` (#5950): a placeholder that
// can never resolve is noise, not a reading. First-party routes keep
// their tier, `auto: tier` and `req→eff` labels.
let effort = app.provable_reasoning_effort_label().unwrap_or_default();

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 Keep the provider when effort is unprovable

For a custom or otherwise unprovable route, this now converts the missing effort label to an empty string, but the existing candidate builder only includes the provider when effort is nonempty. Consequently even a wide metrics line changes from my-gateway · vendor-model-x · … to only vendor-model-x; the requested omission of the unverifiable effort field accidentally removes the still-known provider identity as well. Add a provider-plus-model candidate for this case.

Useful? React with 👍 / 👎.

Comment on lines +2402 to +2406
return match persist_table_string_key(
app.config_path.as_deref(),
"tui",
row_key,
value,

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 Persist row presets into the active profile

When Codewhale is launched with --profile work and that profile defines any [profiles.work.tui] table, merge_config selects the profile's entire TuiConfig over the base table. This call nevertheless writes the preset to the base [tui] table and reports success, so the live change works but the profile shadows it on the next launch and the supposedly saved preset reverts. Write through the active profile path or reject the save with an explicit scope message.

Useful? React with 👍 / 👎.

@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

Adds tui.posture_bar and tui.metrics_line presets (full/compact/hidden), live /config control, and honesty gating for effort and cost labels. The implementation reuses existing shed ladders rather than adding a second renderer, and tests cover config parsing, live mutation, compact shedding, frame layout, and the new effort/cost honesty rules.

Findings

  • [INFO] Only metrics_line --save persistence is covered; posture_bar save path lacks direct test (crates/tui/src/commands/groups/config/config.rs:4876)
    The new config test exercises live /config posture_bar compact and saving metrics_line HIDDEN --save, but never saves posture_bar with --save. The two branches share a helper, but a regression in the posture_bar persist path or key name would not be caught.
  • [INFO] Case sensitivity differs between /config and config.toml (crates/tui/src/config.rs:1883)
    ChromeRowPreset::from_setting trims and lowercases, so /config posture_bar COMPACT works. However the serde-derived Deserialize with rename_all = "snake_case" only accepts the exact lowercase values, so a hand-edited posture_bar = "COMPACT" in config.toml is rejected. Consider adding serde aliases/custom deserialization or explicitly documenting that TOML values must be lowercase.

Assessment

The PR is well-structured, heavily tested, and the behavior matches the stated design. No correctness defects were found in the changed paths; only minor test and documentation gaps are noted.


Advisory review by Codewhale (codewhale review --pr 5973 --post, head 8a6288f5312219e87af4d05e67918d2dc929f86f). 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.

}

/// The bottom-chrome row presets (#5950) apply on the next frame and
/// `--save` writes the `[tui]` key; an unknown preset names the three.

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] Only metrics_line --save persistence is covered; posture_bar save path lacks direct test

The new config test exercises live /config posture_bar compact and saving metrics_line HIDDEN --save, but never saves posture_bar with --save. The two branches share a helper, but a regression in the posture_bar persist path or key name would not be caught.

Comment thread crates/tui/src/config.rs
/// rows back to the transcript without touching `status_items`.
///
/// `compact` is not a second renderer: it starts the row's existing shed
/// ladder at a fixed rung and lets width shed the rest, so what it keeps is

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] Case sensitivity differs between /config and config.toml

ChromeRowPreset::from_setting trims and lowercases, so /config posture_bar COMPACT works. However the serde-derived Deserialize with rename_all = "snake_case" only accepts the exact lowercase values, so a hand-edited posture_bar = "COMPACT" in config.toml is rejected. Consider adding serde aliases/custom deserialization or explicitly documenting that TOML values must be lowercase.

@cursor

cursor Bot commented Sep 7, 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_0955a1de-3e5b-4c1d-8f67-6a5ae9082b1b)

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Hmbown's task in 3m 28s —— View job


Review: compact/hidden presets for the bottom chrome (#5950)

Summary

Overall this is a clean, well-scoped feature (ChromeRowPreset::{Full,Compact,Hidden} reusing the existing shed ladders rather than adding a second renderer), and I traced the two most-repeated bot findings against the actual data flow to confirm which are real. Two are — I've posted them as inline comments with the code trace:

  1. crates/tui/src/tui/ui/frame.rs:36 (confirmed) — session_cost_label gates omission of cost: unknown on app.billing_presentation, which reflects only the currently active route. But App::cumulative_usage_chip() (app.rs:2720) can return UsageChip::Unknown from session-wide history (cost_unpriced_turns > 0, cost_coverage_unknown_legacy) that has nothing to do with the current route. Switching to an unrelated BillingPresentation::Unknown route later hides a real, unrelated unknown-cost fact from earlier in the session — the opposite of the "Cost surfaces only move at TurnComplete — long agentic turns show no $ at all #5578 honesty" goal this PR states. The PR's own added test demonstrates the same session state producing "cost: unknown" under Metered and "" under Unknown.
  2. crates/tui/src/tui/phase_strip.rs:61 (confirmed) — route_identity_fields gates both the [Provider, Model, Effort] and [Model, Effort] candidates on !effort.is_empty(). Now that provable_reasoning_effort_label() legitimately returns None/empty for unprovable routes, both gates fail and the code falls through to model-only — silently dropping the provider too, even at ShellTier::Wide with room to spare. Only the effort field was supposed to be omitted. The new test for this path never asserts the provider string is still present, which is why it slipped through.

Other bot findings I checked and downgraded:

  • The --save ordering (apply live state before persist_table_string_key can fail) and the profile-shadowing concern (persist_table_string_key writes the base [tui] table regardless of an active profile) are both pre-existing patterns shared by every other key using this helper (e.g. stream_chunk_timeout_secs just above it, and 4 other existing call sites) — not new bugs introduced by this PR, so not blocking, but worth a follow-up if it's ever fixed generally.
  • Case sensitivity mismatch between /config posture_bar COMPACT (accepted, lowercased in from_setting) and hand-edited config.toml (rename_all = "snake_case" on Deserialize, case-sensitive) is real but genuinely minor/INFO — same as codewhale-agent's note.

Provider/model safety (EPIC #2608): no findings. provable_reasoning_effort_label, effective_route_identity_display, and the billing branches all key off explicit ApiProvider/BillingPresentation/EffectiveReasoningEffort enum values computed elsewhere — no inference from a wire-id prefix or freeform text.

Reuse/simplification: good — compact on both rows is implemented as "start the existing shed ladder at a later rung" (COMPACT_SHED in phase_strip.rs, SHED_BEFORE_HELP filter in infoline.rs), no parallel rendering path. hidden reuses the info_height/footer_height zeroing that mini mode already had.

Tests: strong breadth (config round-trip, /config live+persist+reject, width-sweep shedding, full-frame integration), but the two confirmed bugs above point to real gaps: neither the cost test nor the effort test exercises the specific combination (session-wide unknown cost + a route switch; provider identity when effort is dropped) that breaks.

crate::route_billing::UsageChip::PricedSubtotal { .. }
| crate::route_billing::UsageChip::Unknown => {
UsageChip::Money(amount) => Some(amount.clone()),
UsageChip::Unknown if app.billing_presentation == BillingPresentation::Unknown => None,

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.

Confirmed bug: session-wide cost: unknown can be hidden by a later route switch.

session_cost_label gates omission on app.billing_presentation, but billing_presentation reflects only the currently active route (set in apply.rs/session_state.rs/provider_routes.rs on every route switch), not why cumulative_usage_chip() returned Unknown.

Trace: App::cumulative_usage_chip (crates/tui/src/tui/app.rs:2720) checks session-wide counters (cost_coverage_unknown_legacy, cost_unpriced_turns) before falling back to the route-based usage_chip(self.billing_presentation, ...). So a session that accrued a genuine unpriced turn on a Metered route (session.cost_unpriced_turns > 0) keeps returning UsageChip::Unknown even after the user switches to an unrelated BillingPresentation::Unknown route later. At that point this new guard hides the label — even though the Unknown chip is reporting real, unrelated session history, not something about the currently-unclassified route.

This PR's own test (cost_is_omitted_only_where_the_route_cannot_be_priced, added just below) demonstrates the same session state (cost_coverage_unknown_legacy = true) producing "cost: unknown" under Metered and "" under Unknown — i.e. the label depends on the current route even though the underlying unknown-coverage fact hasn't changed.

Fix direction: only suppress when the session itself has no priced/unpriced/legacy coverage at all (i.e., cumulative_usage_chip() reached Unknown via the final route-based fallback, not the earlier session-counter branches) — e.g. have cumulative_usage_chip distinguish "no session coverage, route itself unpriceable" from "session has real unknown coverage" as separate UsageChip states, or have session_cost_label check the session counters directly instead of the current route's billing_presentation.

// rather than `high→effective unavailable` (#5950): a placeholder that
// can never resolve is noise, not a reading. First-party routes keep
// their tier, `auto: tier` and `req→eff` labels.
let effort = app.provable_reasoning_effort_label().unwrap_or_default();

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.

Confirmed bug: provider identity is silently dropped, not just the effort field.

route_identity_fields gates both the [Provider, Model, Effort] candidate (line 67: !provider.is_empty() && !effort.is_empty()) and the [Model, Effort] candidate (line 76: !effort.is_empty()) on effort being non-empty. Before this PR, effort came from reasoning_effort_display_label(), which was always non-empty (even the "unavailable" case produced high→effective unavailable), so these gates were effectively always true whenever there was width for them.

Now that provable_reasoning_effort_label() can legitimately return Noneeffort == "" for an unprovable route, both gates fail and the code falls straight through to candidates.push(vec![field(RouteFieldKind::Model, model)]) — dropping the provider field entirely, even at ShellTier::Wide with a large budget where provider · model would easily fit. The PR intends to omit only the effort field ("no effort field... First-party routes keep their tier"), not the provider.

The added test unprovable_effort_states_no_field_instead_of_a_placeholder (crates/tui/src/tui/ui/frame.rs) sets app.set_provider_identity(ApiProvider::Custom, "my-gateway") but never asserts "my-gateway" appears in the row — this gap is why the regression wasn't caught.

Fix direction: add a [Provider, Model] candidate (no effort field) for tier != ShellTier::Compact && !provider.is_empty() && effort.is_empty(), so the provider survives when only the effort is unprovable.

…ake; passes on re-roll)

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@cursor

cursor Bot commented Sep 7, 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_1cd0fc17-041a-41d9-8b32-2807c7ff68de)

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

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


Review: compact/hidden presets for the bottom chrome (#5950)

  • Gather context (diff since last push, prior bot/agent findings)
  • Check whether previously confirmed bugs were addressed
  • Review full diff for new correctness issues
  • Check provider/model/route safety invariant
  • Check reuse/simplification and test coverage
  • Post final review

Summary

Re-checked git diff origin/main...HEAD against the latest head (edfb317, a CI-retrigger-only commit — no code changed since 8a6288f). The two bugs already confirmed by earlier review passes on this PR are both still present; nothing since has touched frame.rs or phase_strip.rs. Reporting them once more here as the authoritative summary, plus what's clean.

Correctness bugs

  1. crates/tui/src/tui/ui/frame.rs:36cost: unknown can be hidden by an unrelated later route switch, contradicting this PR's own stated goal and an existing doc comment in the same file.

    session_cost_label suppresses UsageChip::Unknown only via app.billing_presentation == BillingPresentation::Unknown — the currently active route. But App::cumulative_usage_chip() (app.rs:2720) explicitly documents the opposite contract: "Session history wins over the current route: switching to an OAuth or local route must not hide spend already accrued on a metered route." Its logic checks session.cost_coverage_unknown_legacy / cost_unpriced_turns before falling back to the route-based usage_chip(self.billing_presentation, ...). So a session that genuinely accrued unpriced spend on a Metered route keeps returning UsageChip::Unknown even after switching to an unrelated BillingPresentation::Unknown route — and the new guard now hides that real fact.

    The PR's own added test (cost_is_omitted_only_where_the_route_cannot_be_priced, same file) demonstrates this directly: identical session state (cost_coverage_unknown_legacy = true) produces "cost: unknown" under Metered and "" under Unknown — the label flips on the current route alone, not on any change to the underlying unknown-coverage fact.

    Fix direction: have session_cost_label (or cumulative_usage_chip) distinguish "session has no coverage at all and the route itself is unpriceable" from "session has real unknown/legacy coverage" — e.g. give UsageChip two Unknown variants (session-derived vs. route-derived), or check the session counters directly instead of app.billing_presentation.

    Fix this →

  2. crates/tui/src/tui/phase_strip.rs:61 — omitting an unprovable effort field also silently drops the provider field.

    route_identity_fields gates both the [Provider, Model, Effort] candidate (line 67: !provider.is_empty() && !effort.is_empty()) and the [Model, Effort] candidate (line 76: !effort.is_empty()) on effort being non-empty. Before this PR effort was never empty (even the unprovable case produced high→effective unavailable), so these gates always passed when there was width. Now provable_reasoning_effort_label() legitimately returns Noneeffort == "", so both gates fail and the code falls straight to the model-only candidate — dropping the provider too, even at ShellTier::Wide with room to spare. Only the effort field was meant to be omitted.

    The new test unprovable_effort_states_no_field_instead_of_a_placeholder sets app.set_provider_identity(ApiProvider::Custom, "my-gateway") but never asserts "my-gateway" appears in the row/fields — that gap is why this wasn't caught.

    Fix direction: add a [Provider, Model] candidate (no effort) for tier != ShellTier::Compact && !provider.is_empty() && effort.is_empty(), and extend the test to assert the provider string survives.

    Fix this →

Both were already flagged inline by Devin, Codex, and a prior Claude pass on this same commit — confirming again because they remain unfixed and are the only correctness-blocking findings.

Provider/model/route safety (EPIC #2608)

No issues. provable_reasoning_effort_label, effective_route_identity_display, and the billing branches all key off explicit ApiProvider / BillingPresentation / EffectiveReasoningEffort enum values computed elsewhere — nothing infers a provider/model switch from a wire-id prefix or freeform text.

Reuse / simplification

Good. compact is implemented both places as "start the existing shed ladder at a later rung" — COMPACT_SHED in phase_strip.rs, the SHED_BEFORE_HELP filter in infoline.rs — not a second renderer. hidden reuses the info_height/footer_height zeroing path mini mode already used. /config posture_bar|metrics_line follows the existing session-only-vs---save pattern used by other keys, no duplicated match arms.

Non-blocking, previously noted by other reviewers, not re-litigating:

  • --save applies the in-memory preset before persist_table_string_key can fail, and writes the base [tui] table even under an active profile that shadows it — both are pre-existing patterns shared by every other key using this helper, not new to this PR.
  • /config posture_bar COMPACT (case-insensitive) vs. hand-edited config.toml (rename_all = "snake_case", case-sensitive) is a minor, genuinely INFO-level inconsistency.

Tests

Strong breadth otherwise (config round-trip, /config live+persist+reject, width-sweep shedding on both rows, full-frame integration across all four hidden/compact combinations). The two gaps above are exactly what let the two real bugs through — the cost test switches routes without ever having recorded unpriced spend on the prior route, and the effort test never asserts the provider string.
· branch feat/bottom-chrome-presets-5950

@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

Adds tui.posture_bar and tui.metrics_line presets (full | compact | hidden), wired through config parsing, app init, /config live updates, and frame rendering. Also changes effort and cost display so unprovable/unpriceable facts are omitted instead of shown as placeholders. The implementation is well-structured and the targeted tests cover the main behavior.

Findings

  • [INFO] Compact metrics balance retention lacks a direct regression test (crates/tui/src/tui/infoline/tests.rs)
    The compact metrics path drops segments with shed_priority >= SHED_BEFORE_HELP and the PR states that the balance segment should survive compact mode. The new infoline compact test only asserts route, context, and cost are kept; work_segments() appears to omit balance, so a misclassified shed_priority for the Balance segment could go unnoticed.

Assessment

No blocking correctness issues found. The change is localized, follows the existing shed-ladder design, and includes broad tests. The main gap is a small test-coverage hole for the compact metrics balance claim.


Advisory review by Codewhale (codewhale review --pr 5973 --post, head edfb317414ba99148135043c0847ec9f10378032). 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.

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): make 0.9.12 bottom chrome (posture bar + metrics line) configurable; /statusline is effectively dead; restore always-visible context

2 participants