Remove context status-line meter (#17420)

Addresses #17313

Problem: The visual context meter in the status line was confusing and
continued to draw negative feedback, and context reporting should remain
an explicit opt-in rather than part of the default footer.

Solution: Remove the visual meter, restore opt-in context remaining/used
percentage items that explicitly say "Context", keep existing
context-usage configs working as a hidden alias, and update the setup
text and snapshots.
This commit is contained in:
Eric Traut
2026-04-12 15:42:09 -07:00
committed by GitHub
Unverified
parent 0393a485ed
commit 470510174b
6 changed files with 71 additions and 117 deletions
+1 -2
View File
@@ -324,8 +324,7 @@ pub struct Config {
/// Ordered list of status line item identifiers for the TUI.
///
/// When unset, the TUI defaults to: `model-with-reasoning`, `context-remaining`, and
/// `current-dir`.
/// When unset, the TUI defaults to: `model-with-reasoning` and `current-dir`.
pub tui_status_line: Option<Vec<String>>,
/// Ordered list of terminal title item identifiers for the TUI.
@@ -13,9 +13,9 @@ expression: "render_lines(&view, 72)"
[x] git-branch Current Git branch (omitted when unavaila…
[ ] model-with-reasoning Current model name with reasoning level
[ ] project-root Project root directory (omitted when unav…
[ ] context-usage Visual meter of context window usage (omi
[ ] context-remaining Percentage of context window remaining (o…
[ ] context-used Percentage of context window used (omitte…
[ ] five-hour-limit Remaining usage on 5-hour usage limit (om…
[ ] weekly-limit Remaining usage on weekly usage limit (om…
gpt-5-codex · ~/codex-rs · jif/statusline-preview
Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc
@@ -12,7 +12,7 @@
//! - Model information (name, reasoning level)
//! - Directory paths (current dir, project root)
//! - Git information (branch name)
//! - Context usage (meter, window size)
//! - Context usage (remaining %, used %, window size)
//! - Usage limits (5-hour, weekly)
//! - Session info (thread title, ID, tokens used)
//! - Application version
@@ -22,6 +22,7 @@ use ratatui::layout::Rect;
use ratatui::text::Line;
use std::collections::BTreeMap;
use std::collections::HashSet;
use strum::IntoEnumIterator;
use strum_macros::Display;
use strum_macros::EnumIter;
use strum_macros::EnumString;
@@ -62,15 +63,14 @@ pub(crate) enum StatusLineItem {
/// Current git branch name (if in a repository).
GitBranch,
/// Visual meter of context window usage.
/// Percentage of context window remaining.
ContextRemaining,
/// Percentage of context window used.
///
/// Also accepts legacy `context-remaining` and `context-used` config values.
#[strum(
to_string = "context-usage",
serialize = "context-remaining",
serialize = "context-used"
)]
ContextUsage,
/// Also accepts the legacy `context-usage` config value.
#[strum(to_string = "context-used", serialize = "context-usage")]
ContextUsed,
/// Remaining usage on the 5-hour rate limit.
FiveHourLimit,
@@ -112,8 +112,11 @@ impl StatusLineItem {
StatusLineItem::CurrentDir => "Current working directory",
StatusLineItem::ProjectRoot => "Project root directory (omitted when unavailable)",
StatusLineItem::GitBranch => "Current Git branch (omitted when unavailable)",
StatusLineItem::ContextUsage => {
"Visual meter of context window usage (omitted when unknown)"
StatusLineItem::ContextRemaining => {
"Percentage of context window remaining (omitted when unknown)"
}
StatusLineItem::ContextUsed => {
"Percentage of context window used (omitted when unknown)"
}
StatusLineItem::FiveHourLimit => {
"Remaining usage on 5-hour usage limit (omitted when unavailable)"
@@ -137,25 +140,6 @@ impl StatusLineItem {
}
}
const SELECTABLE_STATUS_LINE_ITEMS: &[StatusLineItem] = &[
StatusLineItem::ModelName,
StatusLineItem::ModelWithReasoning,
StatusLineItem::CurrentDir,
StatusLineItem::ProjectRoot,
StatusLineItem::GitBranch,
StatusLineItem::ContextUsage,
StatusLineItem::FiveHourLimit,
StatusLineItem::WeeklyLimit,
StatusLineItem::CodexVersion,
StatusLineItem::ContextWindowSize,
StatusLineItem::UsedTokens,
StatusLineItem::TotalInputTokens,
StatusLineItem::TotalOutputTokens,
StatusLineItem::SessionId,
StatusLineItem::FastMode,
StatusLineItem::ThreadTitle,
];
/// Runtime values used to preview the current status-line selection.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct StatusLinePreviewData {
@@ -232,7 +216,7 @@ impl StatusLineSetupView {
}
}
for item in SELECTABLE_STATUS_LINE_ITEMS.iter().cloned() {
for item in StatusLineItem::iter() {
let item_id = item.to_string();
if used_ids.contains(&item_id) {
continue;
@@ -317,19 +301,27 @@ mod tests {
use crate::app_event::AppEvent;
#[test]
fn context_usage_is_canonical_and_accepts_legacy_ids() {
assert_eq!(StatusLineItem::ContextUsage.to_string(), "context-usage");
assert_eq!(
"context-usage".parse::<StatusLineItem>(),
Ok(StatusLineItem::ContextUsage)
);
assert_eq!(
"context-remaining".parse::<StatusLineItem>(),
Ok(StatusLineItem::ContextUsage)
);
fn context_used_accepts_context_usage_legacy_id() {
assert_eq!(StatusLineItem::ContextUsed.to_string(), "context-used");
assert_eq!(
"context-used".parse::<StatusLineItem>(),
Ok(StatusLineItem::ContextUsage)
Ok(StatusLineItem::ContextUsed)
);
assert_eq!(
"context-usage".parse::<StatusLineItem>(),
Ok(StatusLineItem::ContextUsed)
);
}
#[test]
fn context_remaining_is_separate_selectable_id() {
assert_eq!(
"context-remaining".parse::<StatusLineItem>(),
Ok(StatusLineItem::ContextRemaining)
);
assert_eq!(
StatusLineItem::ContextRemaining.to_string(),
"context-remaining"
);
}
@@ -6,4 +6,4 @@ expression: normalized_backend_snapshot(terminal.backend())
" "
" Ask Codex to do anything "
" "
" gpt-5.4 xhigh fast · Context [ ] · /tmp/project "
" gpt-5.4 xhigh fast · Context 0% used · /tmp/project "
+5 -60
View File
@@ -450,9 +450,12 @@ impl ChatWidget {
Some(format!("{} used", format_tokens_compact(total)))
}
}
StatusLineItem::ContextUsage => self
StatusLineItem::ContextRemaining => self
.status_line_context_remaining_percent()
.map(|remaining| format!("Context {remaining}% left")),
StatusLineItem::ContextUsed => self
.status_line_context_used_percent()
.map(format_context_used_meter),
.map(|used| format!("Context {used}% used")),
StatusLineItem::FiveHourLimit => {
let window = self
.rate_limit_snapshots_by_limit_id
@@ -661,61 +664,3 @@ where
}
(items, invalid)
}
fn format_context_used_meter(used_percent: i64) -> String {
const METER_WIDTH: usize = 5;
const EIGHTHS_PER_CELL: i64 = 8;
const PARTIAL_BLOCKS: [&str; 8] = ["", "", "", "", "", "", "", ""];
let used_percent = used_percent.clamp(0, 100);
let total_eighths = (used_percent * METER_WIDTH as i64 * EIGHTHS_PER_CELL + 50) / 100;
let filled_cells = (total_eighths / EIGHTHS_PER_CELL) as usize;
let partial_eighths = (total_eighths % EIGHTHS_PER_CELL) as usize;
let mut meter = String::with_capacity(METER_WIDTH);
meter.push_str(&"".repeat(filled_cells));
meter.push_str(PARTIAL_BLOCKS[partial_eighths]);
let occupied_cells = filled_cells + usize::from(partial_eighths > 0);
meter.push_str(&" ".repeat(METER_WIDTH.saturating_sub(occupied_cells)));
format!("Context [{meter}]")
}
#[cfg(test)]
mod tests {
use super::format_context_used_meter;
use pretty_assertions::assert_eq;
#[test]
fn context_meter_uses_five_cells_with_partial_blocks() {
assert_eq!(
format_context_used_meter(/*used_percent*/ 100),
"Context [█████]"
);
assert_eq!(
format_context_used_meter(/*used_percent*/ 50),
"Context [██▌ ]"
);
assert_eq!(
format_context_used_meter(/*used_percent*/ 10),
"Context [▌ ]"
);
assert_eq!(
format_context_used_meter(/*used_percent*/ 0),
"Context [ ]"
);
}
#[test]
fn context_meter_clamps_out_of_range_values() {
assert_eq!(
format_context_used_meter(/*used_percent*/ 125),
"Context [█████]"
);
assert_eq!(
format_context_used_meter(/*used_percent*/ -1),
"Context [ ]"
);
}
}
@@ -867,32 +867,50 @@ async fn status_line_invalid_items_warn_once() {
}
#[tokio::test]
async fn status_line_legacy_context_used_renders_context_meter() {
async fn status_line_context_used_renders_labeled_percent() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.thread_id = Some(ThreadId::new());
chat.config.tui_status_line = Some(vec!["context-used".to_string()]);
chat.refresh_status_line();
assert_eq!(status_line_text(&chat), Some("Context [ ]".to_string()));
assert_eq!(status_line_text(&chat), Some("Context 0% used".to_string()));
assert!(
drain_insert_history(&mut rx).is_empty(),
"legacy context-used should remain a valid status line item"
"context-used should remain a valid status line item"
);
}
#[tokio::test]
async fn status_line_legacy_context_remaining_renders_context_meter() {
async fn status_line_context_remaining_renders_labeled_percent() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.thread_id = Some(ThreadId::new());
chat.config.tui_status_line = Some(vec!["context-remaining".to_string()]);
chat.refresh_status_line();
assert_eq!(status_line_text(&chat), Some("Context [ ]".to_string()));
assert_eq!(
status_line_text(&chat),
Some("Context 100% left".to_string())
);
assert!(
drain_insert_history(&mut rx).is_empty(),
"legacy context-remaining should remain a valid status line item"
"context-remaining should remain a valid status line item"
);
}
#[tokio::test]
async fn status_line_legacy_context_usage_renders_context_used_percent() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.thread_id = Some(ThreadId::new());
chat.config.tui_status_line = Some(vec!["context-usage".to_string()]);
chat.refresh_status_line();
assert_eq!(status_line_text(&chat), Some("Context 0% used".to_string()));
assert!(
drain_insert_history(&mut rx).is_empty(),
"legacy context-usage should remain a valid status line item"
);
}
@@ -995,7 +1013,7 @@ async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models(
chat.config.cwd = test_project_path().abs();
chat.config.tui_status_line = Some(vec![
"model-with-reasoning".to_string(),
"context-usage".to_string(),
"context-used".to_string(),
"current-dir".to_string(),
]);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
@@ -1008,7 +1026,7 @@ async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models(
assert_eq!(
status_line_text(&chat),
Some(format!("gpt-5.4 xhigh fast · Context [ ] · {test_cwd}"))
Some(format!("gpt-5.4 xhigh fast · Context 0% used · {test_cwd}"))
);
chat.set_model("gpt-5.3-codex");
@@ -1017,7 +1035,7 @@ async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models(
assert_eq!(
status_line_text(&chat),
Some(format!(
"gpt-5.3-codex xhigh · Context [ ] · {test_cwd}"
"gpt-5.3-codex xhigh · Context 0% used · {test_cwd}"
))
);
}
@@ -1141,7 +1159,7 @@ async fn status_line_model_with_reasoning_fast_footer_snapshot() {
chat.config.cwd = test_project_path().abs();
chat.config.tui_status_line = Some(vec![
"model-with-reasoning".to_string(),
"context-usage".to_string(),
"context-used".to_string(),
"current-dir".to_string(),
]);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));