diff --git a/app/assets/bundled/svg/conversation-context-100.svg b/app/assets/bundled/svg/context-remaining-0.svg
similarity index 100%
rename from app/assets/bundled/svg/conversation-context-100.svg
rename to app/assets/bundled/svg/context-remaining-0.svg
index 37d0ef1277..9511432b12 100644
--- a/app/assets/bundled/svg/conversation-context-100.svg
+++ b/app/assets/bundled/svg/context-remaining-0.svg
@@ -1,37 +1,5 @@
diff --git a/app/assets/bundled/svg/conversation-context-90.svg b/app/assets/bundled/svg/context-remaining-10.svg
similarity index 97%
rename from app/assets/bundled/svg/conversation-context-90.svg
rename to app/assets/bundled/svg/context-remaining-10.svg
index 747ffa68bb..81d37c7daa 100644
--- a/app/assets/bundled/svg/conversation-context-90.svg
+++ b/app/assets/bundled/svg/context-remaining-10.svg
@@ -1,62 +1,62 @@
diff --git a/app/assets/bundled/svg/conversation-context-0.svg b/app/assets/bundled/svg/context-remaining-100.svg
similarity index 91%
rename from app/assets/bundled/svg/conversation-context-0.svg
rename to app/assets/bundled/svg/context-remaining-100.svg
index 7f941b1d5a..71e747d501 100644
--- a/app/assets/bundled/svg/conversation-context-0.svg
+++ b/app/assets/bundled/svg/context-remaining-100.svg
@@ -1,34 +1,34 @@
diff --git a/app/assets/bundled/svg/conversation-context-80.svg b/app/assets/bundled/svg/context-remaining-20.svg
similarity index 97%
rename from app/assets/bundled/svg/conversation-context-80.svg
rename to app/assets/bundled/svg/context-remaining-20.svg
index 06260a6a37..2b17d71b10 100644
--- a/app/assets/bundled/svg/conversation-context-80.svg
+++ b/app/assets/bundled/svg/context-remaining-20.svg
@@ -1,60 +1,60 @@
diff --git a/app/assets/bundled/svg/conversation-context-70.svg b/app/assets/bundled/svg/context-remaining-30.svg
similarity index 96%
rename from app/assets/bundled/svg/conversation-context-70.svg
rename to app/assets/bundled/svg/context-remaining-30.svg
index 513db5a6d8..7d099372ad 100644
--- a/app/assets/bundled/svg/conversation-context-70.svg
+++ b/app/assets/bundled/svg/context-remaining-30.svg
@@ -1,56 +1,56 @@
diff --git a/app/assets/bundled/svg/conversation-context-60.svg b/app/assets/bundled/svg/context-remaining-40.svg
similarity index 95%
rename from app/assets/bundled/svg/conversation-context-60.svg
rename to app/assets/bundled/svg/context-remaining-40.svg
index adbd499c3c..0a0b130a77 100644
--- a/app/assets/bundled/svg/conversation-context-60.svg
+++ b/app/assets/bundled/svg/context-remaining-40.svg
@@ -1,54 +1,54 @@
diff --git a/app/assets/bundled/svg/conversation-context-50.svg b/app/assets/bundled/svg/context-remaining-50.svg
similarity index 95%
rename from app/assets/bundled/svg/conversation-context-50.svg
rename to app/assets/bundled/svg/context-remaining-50.svg
index 7e730a66e2..6eccc18877 100644
--- a/app/assets/bundled/svg/conversation-context-50.svg
+++ b/app/assets/bundled/svg/context-remaining-50.svg
@@ -1,50 +1,50 @@
diff --git a/app/assets/bundled/svg/conversation-context-40.svg b/app/assets/bundled/svg/context-remaining-60.svg
similarity index 95%
rename from app/assets/bundled/svg/conversation-context-40.svg
rename to app/assets/bundled/svg/context-remaining-60.svg
index eec0598c32..bce954852b 100644
--- a/app/assets/bundled/svg/conversation-context-40.svg
+++ b/app/assets/bundled/svg/context-remaining-60.svg
@@ -1,46 +1,46 @@
diff --git a/app/assets/bundled/svg/conversation-context-30.svg b/app/assets/bundled/svg/context-remaining-70.svg
similarity index 94%
rename from app/assets/bundled/svg/conversation-context-30.svg
rename to app/assets/bundled/svg/context-remaining-70.svg
index 89fd8a7254..4f3b1bde95 100644
--- a/app/assets/bundled/svg/conversation-context-30.svg
+++ b/app/assets/bundled/svg/context-remaining-70.svg
@@ -1,44 +1,44 @@
diff --git a/app/assets/bundled/svg/conversation-context-20.svg b/app/assets/bundled/svg/context-remaining-80.svg
similarity index 93%
rename from app/assets/bundled/svg/conversation-context-20.svg
rename to app/assets/bundled/svg/context-remaining-80.svg
index fe16834842..6c2e644512 100644
--- a/app/assets/bundled/svg/conversation-context-20.svg
+++ b/app/assets/bundled/svg/context-remaining-80.svg
@@ -1,40 +1,40 @@
diff --git a/app/assets/bundled/svg/conversation-context-10.svg b/app/assets/bundled/svg/context-remaining-90.svg
similarity index 93%
rename from app/assets/bundled/svg/conversation-context-10.svg
rename to app/assets/bundled/svg/context-remaining-90.svg
index 22a088599d..5fa5d801e6 100644
--- a/app/assets/bundled/svg/conversation-context-10.svg
+++ b/app/assets/bundled/svg/context-remaining-90.svg
@@ -1,38 +1,38 @@
diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs
index cf8ee68873..679dbb7b1b 100644
--- a/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs
+++ b/app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs
@@ -609,7 +609,7 @@ impl AgentInputFooter {
let context_window_button = ctx.add_typed_action_view(|_ctx| {
ActionButton::new("", AgentInputButtonTheme)
- .with_icon(Icon::ConversationContext0)
+ .with_icon(Icon::ContextRemaining100)
.with_tooltip("Context window usage")
.with_size(button_size)
.with_tooltip_alignment(TooltipAlignment::Left)
diff --git a/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs b/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs
index 4d813f620d..601769ffda 100644
--- a/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs
+++ b/app/src/ai/blocklist/agent_view/agent_input_footer/toolbar_item.rs
@@ -138,7 +138,7 @@ impl AgentToolbarItemKind {
Self::NLDToggle => Some(Icon::NLD),
Self::VoiceInput => Some(Icon::Microphone),
Self::FileAttach => Some(Icon::Plus),
- Self::ContextWindowUsage => Some(Icon::ConversationContext0),
+ Self::ContextWindowUsage => Some(Icon::ContextRemaining100),
Self::FileExplorer => Some(Icon::FileCopy),
Self::RichInput => Some(Icon::TextInput),
Self::ShareSession => Some(Icon::Phone01),
diff --git a/app/src/ai/blocklist/usage/mod.rs b/app/src/ai/blocklist/usage/mod.rs
index c066b12051..6e1ad33e0c 100644
--- a/app/src/ai/blocklist/usage/mod.rs
+++ b/app/src/ai/blocklist/usage/mod.rs
@@ -6,29 +6,35 @@ pub mod conversation_usage_view;
pub mod rollup;
pub fn icon_for_context_window_usage(context_window_usage: f32) -> Icon {
- // Match the context window usage to the nearest 10% icon.
- if context_window_usage >= 0.95 {
- Icon::ConversationContext100
- } else if context_window_usage >= 0.85 {
- Icon::ConversationContext90
- } else if context_window_usage >= 0.75 {
- Icon::ConversationContext80
- } else if context_window_usage >= 0.65 {
- Icon::ConversationContext70
- } else if context_window_usage >= 0.55 {
- Icon::ConversationContext60
- } else if context_window_usage >= 0.45 {
- Icon::ConversationContext50
- } else if context_window_usage >= 0.35 {
- Icon::ConversationContext40
- } else if context_window_usage >= 0.25 {
- Icon::ConversationContext30
- } else if context_window_usage >= 0.15 {
- Icon::ConversationContext20
- } else if context_window_usage >= 0.05 {
- Icon::ConversationContext10
+ // The circle's solid (white) marks represent the context *remaining*, not
+ // the amount used: an empty conversation shows an all-white circle (100%
+ // remaining) and counts down to an all-grey circle as the context window
+ // fills up (0% remaining). So match the *remaining* fraction
+ // (`1 - usage`) to the nearest 10% icon, where `ContextRemainingN`
+ // brightens N% of the ring.
+ let context_window_remaining = 1.0 - context_window_usage;
+ if context_window_remaining >= 0.95 {
+ Icon::ContextRemaining100
+ } else if context_window_remaining >= 0.85 {
+ Icon::ContextRemaining90
+ } else if context_window_remaining >= 0.75 {
+ Icon::ContextRemaining80
+ } else if context_window_remaining >= 0.65 {
+ Icon::ContextRemaining70
+ } else if context_window_remaining >= 0.55 {
+ Icon::ContextRemaining60
+ } else if context_window_remaining >= 0.45 {
+ Icon::ContextRemaining50
+ } else if context_window_remaining >= 0.35 {
+ Icon::ContextRemaining40
+ } else if context_window_remaining >= 0.25 {
+ Icon::ContextRemaining30
+ } else if context_window_remaining >= 0.15 {
+ Icon::ContextRemaining20
+ } else if context_window_remaining >= 0.05 {
+ Icon::ContextRemaining10
} else {
- Icon::ConversationContext0
+ Icon::ContextRemaining0
}
}
@@ -47,3 +53,7 @@ pub fn render_context_window_usage_icon(
icon.to_warpui_icon(fill).finish()
}
+
+#[cfg(test)]
+#[path = "mod_tests.rs"]
+mod tests;
diff --git a/app/src/ai/blocklist/usage/mod_tests.rs b/app/src/ai/blocklist/usage/mod_tests.rs
new file mode 100644
index 0000000000..dbe71cb31e
--- /dev/null
+++ b/app/src/ai/blocklist/usage/mod_tests.rs
@@ -0,0 +1,71 @@
+//! Tests for the context-window usage circle icon mapping.
+//!
+//! Regression guard for the color semantics of the context-window circle:
+//! the solid (white) marks represent the context *remaining*, not the amount
+//! used. An empty conversation (0% used → 100% remaining) shows a full white
+//! circle and it counts down to an all-grey circle as the window fills up
+//! (100% used → 0% remaining).
+
+use warp_core::ui::Icon;
+
+use super::icon_for_context_window_usage;
+
+#[test]
+fn empty_conversation_shows_full_white_circle() {
+ // 0% used == 100% remaining -> all-white circle.
+ assert_eq!(
+ icon_for_context_window_usage(0.0),
+ Icon::ContextRemaining100
+ );
+}
+
+#[test]
+fn full_context_window_shows_all_grey_circle() {
+ // 100% used == 0% remaining -> all-grey circle.
+ assert_eq!(icon_for_context_window_usage(1.0), Icon::ContextRemaining0);
+}
+
+#[test]
+fn icon_brightness_tracks_remaining_not_used() {
+ // Lightly-used conversation: lots of context remaining -> mostly white.
+ assert_eq!(icon_for_context_window_usage(0.1), Icon::ContextRemaining90);
+ // Half used -> half white.
+ assert_eq!(icon_for_context_window_usage(0.5), Icon::ContextRemaining50);
+ // Heavily used (the original report's 88%): little remaining -> mostly grey.
+ assert_eq!(
+ icon_for_context_window_usage(0.88),
+ Icon::ContextRemaining10
+ );
+}
+
+#[test]
+fn mapping_is_monotonic_more_usage_never_brightens_the_circle() {
+ // As usage increases, the number of bright (remaining) marks must be
+ // non-increasing — the circle only ever empties as context fills.
+ let icon_rank = |usage: f32| match icon_for_context_window_usage(usage) {
+ Icon::ContextRemaining0 => 0,
+ Icon::ContextRemaining10 => 10,
+ Icon::ContextRemaining20 => 20,
+ Icon::ContextRemaining30 => 30,
+ Icon::ContextRemaining40 => 40,
+ Icon::ContextRemaining50 => 50,
+ Icon::ContextRemaining60 => 60,
+ Icon::ContextRemaining70 => 70,
+ Icon::ContextRemaining80 => 80,
+ Icon::ContextRemaining90 => 90,
+ Icon::ContextRemaining100 => 100,
+ other => panic!("unexpected icon: {other:?}"),
+ };
+
+ let mut usage = 0.0;
+ let mut previous = icon_rank(usage);
+ while usage <= 1.0 {
+ let current = icon_rank(usage);
+ assert!(
+ current <= previous,
+ "icon brightness increased as usage rose to {usage}: {previous} -> {current}"
+ );
+ previous = current;
+ usage += 0.05;
+ }
+}
diff --git a/crates/warp_core/src/ui/icons.rs b/crates/warp_core/src/ui/icons.rs
index 1d4eebd2b2..21dce9d97f 100644
--- a/crates/warp_core/src/ui/icons.rs
+++ b/crates/warp_core/src/ui/icons.rs
@@ -240,17 +240,17 @@ pub enum Icon {
ContextWindowSixtyPct,
ContextWindowWarning,
ContextWindowSummarized,
- ConversationContext0,
- ConversationContext10,
- ConversationContext20,
- ConversationContext30,
- ConversationContext40,
- ConversationContext50,
- ConversationContext60,
- ConversationContext70,
- ConversationContext80,
- ConversationContext90,
- ConversationContext100,
+ ContextRemaining0,
+ ContextRemaining10,
+ ContextRemaining20,
+ ContextRemaining30,
+ ContextRemaining40,
+ ContextRemaining50,
+ ContextRemaining60,
+ ContextRemaining70,
+ ContextRemaining80,
+ ContextRemaining90,
+ ContextRemaining100,
LeftSidebarOpen,
LeftSidebarClose,
Diff,
@@ -577,17 +577,17 @@ impl From for &'static str {
Icon::ContextWindowSixtyPct => "bundled/svg/context-window-60-pct.svg",
Icon::ContextWindowWarning => "bundled/svg/context-window-warning.svg",
Icon::ContextWindowSummarized => "bundled/svg/context-window-summarized.svg",
- Icon::ConversationContext0 => "bundled/svg/conversation-context-0.svg",
- Icon::ConversationContext10 => "bundled/svg/conversation-context-10.svg",
- Icon::ConversationContext20 => "bundled/svg/conversation-context-20.svg",
- Icon::ConversationContext30 => "bundled/svg/conversation-context-30.svg",
- Icon::ConversationContext40 => "bundled/svg/conversation-context-40.svg",
- Icon::ConversationContext50 => "bundled/svg/conversation-context-50.svg",
- Icon::ConversationContext60 => "bundled/svg/conversation-context-60.svg",
- Icon::ConversationContext70 => "bundled/svg/conversation-context-70.svg",
- Icon::ConversationContext80 => "bundled/svg/conversation-context-80.svg",
- Icon::ConversationContext90 => "bundled/svg/conversation-context-90.svg",
- Icon::ConversationContext100 => "bundled/svg/conversation-context-100.svg",
+ Icon::ContextRemaining0 => "bundled/svg/context-remaining-0.svg",
+ Icon::ContextRemaining10 => "bundled/svg/context-remaining-10.svg",
+ Icon::ContextRemaining20 => "bundled/svg/context-remaining-20.svg",
+ Icon::ContextRemaining30 => "bundled/svg/context-remaining-30.svg",
+ Icon::ContextRemaining40 => "bundled/svg/context-remaining-40.svg",
+ Icon::ContextRemaining50 => "bundled/svg/context-remaining-50.svg",
+ Icon::ContextRemaining60 => "bundled/svg/context-remaining-60.svg",
+ Icon::ContextRemaining70 => "bundled/svg/context-remaining-70.svg",
+ Icon::ContextRemaining80 => "bundled/svg/context-remaining-80.svg",
+ Icon::ContextRemaining90 => "bundled/svg/context-remaining-90.svg",
+ Icon::ContextRemaining100 => "bundled/svg/context-remaining-100.svg",
Icon::LeftSidebarOpen => "bundled/svg/left-panel-open.svg",
Icon::LeftSidebarClose => "bundled/svg/left-panel-close.svg",
Icon::Diff => "bundled/svg/diff.svg",