Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion crates/codexbar-app/src/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,46 @@ impl Dashboard {
cx.notify();
}

/// A synthetic Claude usage payload through the real parser, `age` into a week that is 8% used, for the early
/// and firm SUS states without live accounts.
#[cfg(any(test, feature = "capture"))]
pub(crate) fn capture_claude_weekly(&mut self, age: Duration, cx: &mut Context<Self>) {
use codexbar_core::{AccountId, Provider};
assert!(matches!(self.source, DataSource::Demo));
let start: DateTime<Utc> = "2026-10-26T09:00:00Z".parse().unwrap();
let now = start + age;
crate::sus::capture_time(now, cx);
let session = (now + Duration::hours(3)).to_rfc3339();
let week = (start + Duration::days(7)).to_rfc3339();
let payload = format!(
r#"{{"five_hour":{{"utilization":12,"resets_at":"{session}"}},"seven_day":{{"utilization":8,"resets_at":"{week}"}}}}"#
);
let parsed = codexbar_providers::claude::parse_usage(&payload, None, now).unwrap();
let account = AccountSnapshot::new(
AccountId::new("claude"),
Provider::Claude,
parsed.metrics().to_vec(),
now,
)
.with_plan("Max 20x")
.with_trend(vec![0.02, 0.05, 0.08]);
self.states.clear();
self.placeholders.clear();
self.set_accounts(vec![account], cx);
crate::prefs_hub::PrefsHub::update_focus_choice(cx, "claude", |choice| {
choice.rings = vec!["weekly".into()];
});
self.focus_account(
&crate::handoff::FocusRequest {
account: "claude".into(),
metric: Some("weekly".into()),
},
cx,
);
self.view = DashboardView::Usage;
cx.notify();
}

/// Synthetic CLI payload through the real parser, for age-boundary rendering without live accounts.
#[cfg(any(test, feature = "capture"))]
pub(crate) fn capture_gemini_weekly(&mut self, mature: bool, cx: &mut Context<Self>) {
Expand Down Expand Up @@ -1911,7 +1951,12 @@ impl Dashboard {
gpui_kit::component::h_flex()
.gap_1()
.child(div().text_xs().child("Fleet"))
.child(crate::sus::badge(fleet.score, "fleet-sus", "Fleet SUS", cx))
.child(crate::sus::badge(
fleet.score.map(codexbar_core::sus::Reading::Firm),
"fleet-sus",
"Fleet SUS",
cx,
))
.child(div().text_xs().text_color(cx.theme().muted_foreground).child(
if fleet.subscriptions == 0 {
"no eligible subscriptions".to_owned()
Expand Down
5 changes: 5 additions & 0 deletions crates/codexbar-app/src/self_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::dashboard::{Dashboard, DashboardView};
use crate::theme::Appearance;

mod antigravity;
mod claude_weekly;
mod gemini_status;
mod gemini_weekly;
mod muse;
Expand All @@ -39,6 +40,7 @@ pub fn start(cx: &mut App, handle: AnyWindowHandle, dashboard: Entity<Dashboard>
|| std::env::var_os("CODEXBAR_CAPTURE_MUSE").is_some()
|| std::env::var_os("CODEXBAR_CAPTURE_GEMINI_WEEKLY").is_some()
|| std::env::var_os("CODEXBAR_CAPTURE_GEMINI_STATUS").is_some()
|| std::env::var_os("CODEXBAR_CAPTURE_CLAUDE_WEEKLY").is_some()
{
let hub = crate::settings_hub::SettingsHub::global(cx);
assert!(
Expand Down Expand Up @@ -206,6 +208,9 @@ async fn run(
if std::env::var_os("CODEXBAR_CAPTURE_GEMINI_WEEKLY").is_some() {
saved.extend(gemini_weekly::run(cx, handle, dashboard, dir).await);
}
if std::env::var_os("CODEXBAR_CAPTURE_CLAUDE_WEEKLY").is_some() {
saved.extend(claude_weekly::run(cx, handle, dashboard, dir).await);
}
if std::env::var_os("CODEXBAR_CAPTURE_GEMINI_STATUS").is_some() {
saved.extend(gemini_status::run(cx, handle, dashboard, dir).await);
}
Expand Down
65 changes: 65 additions & 0 deletions crates/codexbar-app/src/self_capture/claude_weekly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! A synthetic Claude week through the production parser and views: n/a, early from 1 hour, firm from 12 hours.
use super::{save, settle};
use crate::{dashboard::Dashboard, theme::Appearance};
use chrono::Duration;
use gpui_kit::test::TestWindowExt as _;
use gpui_kit::{AnyWindowHandle, AppContext as _, AsyncApp, Entity};
use std::path::Path;

/// Each age into an 8%-used week, its screenshot name, its SUS label, and whether its bar forecasts.
pub(crate) fn states() -> [(Duration, &'static str, &'static str, bool); 6] {
[
(Duration::minutes(59), "59m", "Weekly: SUS n/a, unavailable", false),
(Duration::hours(1), "1h", "Weekly: SUS 7 early, early estimate", false),
(Duration::hours(6), "6h", "Weekly: SUS 45 early, early estimate", false),
(
Duration::hours(12) - Duration::minutes(1),
"11h59m",
"Weekly: SUS 89 early, early estimate",
false,
),
(Duration::hours(12), "12h", "Weekly: SUS 89, yellow", true),
(
Duration::hours(15) + Duration::minutes(55),
"15h55m",
"Weekly: SUS 84, yellow",
true,
),
]
}

pub(super) async fn run(
cx: &mut AsyncApp,
handle: AnyWindowHandle,
dashboard: &Entity<Dashboard>,
dir: &Path,
) -> Vec<String> {
let wide = std::env::var_os("CODEXBAR_CAPTURE_WIDE").is_some();
let layout = if wide { "wide" } else { "compact" };
let mut saved = Vec::new();
for (appearance, theme) in [(Appearance::Light, "light"), (Appearance::Dark, "dark")] {
cx.update(|cx| crate::prefs_hub::PrefsHub::set_appearance(cx, appearance));
for (age, name, sus, forecasts) in states() {
cx.update(|cx| dashboard.update(cx, |d, cx| d.capture_claude_weekly(age, cx)));
settle(cx).await;
cx.update(|cx| {
cx.update_window(handle, |_, window, cx| {
window.render_frame(cx);
let id = if wide {
"window-weekly-claude-bar-sus"
} else {
"focus-metric-1-sus"
};
assert_eq!(window.find(id).label(), Some(sus), "{name}");
let ring = window.find("focus-share").label().unwrap().to_owned();
assert_eq!(ring.contains("at reset"), forecasts, "{name}: {ring}");
})
})
.expect("the Claude fixture must reach the actual table and weekly card");
let name = format!("claude-weekly-{name}-{layout}-{theme}");
assert!(save(cx, handle, dir, &name));
saved.push(name);
}
}
saved
}
3 changes: 2 additions & 1 deletion crates/codexbar-app/src/self_capture/gemini_weekly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub(super) async fn run(
Some(if mature {
"Gemini Models weekly: SUS 42, red"
} else {
"Gemini Models weekly: SUS n/a, unavailable"
// 11h 59m 59s: an early score, without a forecast until 12 hours.
"Gemini Models weekly: SUS 42 early, early estimate"
})
);
let ring = window.find("focus-share");
Expand Down
56 changes: 37 additions & 19 deletions crates/codexbar-app/src/sus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,60 @@ use crate::{account_table::AccountState, settings_hub::SettingsHub};
use chrono::{DateTime, Utc};
use codexbar_core::{
AccountSnapshot, Metric,
sus::{self, Band, Score},
sus::{self, Band, Reading},
};
use gpui_kit::component::{ActiveTheme as _, StyledExt as _, tooltip::Tooltip};
use gpui_kit::{
AnyElement, App, ElementId, InteractiveElement as _, IntoElement as _, ParentElement as _,
StatefulInteractiveElement as _, Styled as _, TestSupportExt as _, div,
AnyElement, App, ElementId, Hsla, InteractiveElement as _, IntoElement as _, ParentElement as _, SharedString,
StatefulInteractiveElement as _, Styled as _, TestSupportExt as _, div, prelude::FluentBuilder as _,
};
use std::collections::{BTreeMap, HashMap};

pub fn badge(score: Option<Score>, id: &str, label: &str, cx: &App) -> AnyElement {
let text = score
.map(|s| format!("SUS {}", s.value()))
.unwrap_or_else(|| "SUS n/a".into());
let (color, band) = match score.map(Score::band) {
Some(Band::Green) => (cx.theme().success, "green"),
Some(Band::Yellow) => (
gpui_kit::rgb(if cx.theme().is_dark() { 0xe3b341 } else { 0x9a6700 }).into(),
"yellow",
),
Some(Band::Red) => (cx.theme().danger, "red"),
None => (cx.theme().muted_foreground, "unavailable"),
};
pub fn badge(reading: Option<Reading>, id: &str, label: &str, cx: &App) -> AnyElement {
let text = reading.map_or_else(|| "SUS n/a".into(), Reading::text);
let (color, band) = tone(reading, cx);
let early = reading.is_some_and(Reading::is_early);
let spoken = format!("{label}: {text}, {band}");
let tooltip:gpui_kit::SharedString="Weekly Sub Utilization Score, 0–100. 100 means using the full allowance at reset. Green ≥90, yellow ≥75, red <75. n/a means too little data, a window younger than 12 hours, or no quota forecast.".into();
let tooltip = tooltip(early);
div()
.id(ElementId::Name(id.to_owned().into()))
.test_support()
.aria_label(spoken)
.flex_none()
.text_xs()
.font_semibold()
.when(!early, |this| this.font_semibold())
.text_color(color)
.child(text)
.tooltip(move |window, cx| Tooltip::new(tooltip.clone()).build(window, cx))
.into_any_element()
}
/// The color and spoken band. An early score is provisional: it takes no band color, and the label says so instead.
fn tone(reading: Option<Reading>, cx: &App) -> (Hsla, &'static str) {
match reading {
Some(Reading::Firm(score)) => match score.band() {
Band::Green => (cx.theme().success, "green"),
Band::Yellow => (
gpui_kit::rgb(if cx.theme().is_dark() { 0xe3b341 } else { 0x9a6700 }).into(),
"yellow",
),
Band::Red => (cx.theme().danger, "red"),
},
Some(Reading::Early(_)) => (cx.theme().muted_foreground.opacity(0.75), "early estimate"),
None => (cx.theme().muted_foreground, "unavailable"),
}
}
fn tooltip(early: bool) -> SharedString {
if early {
"Early Weekly Sub Utilization Score: an estimate from usage in this week's first hours. It firms up 12 hours \
into the window. Until then it has no color and never raises risk or an alert."
.into()
} else {
"Weekly Sub Utilization Score, 0–100. 100 means using the full allowance at reset. Green ≥90, yellow ≥75, red \
<75. Early values from 1 hour are gray and marked early until 12 hours. n/a means too little data, a window \
younger than 1 hour, or no quota forecast."
.into()
}
}
pub fn metric(
account: &AccountSnapshot,
metric: &Metric,
Expand All @@ -53,7 +71,7 @@ pub fn metric(
}
fn metric_badge(metric: &Metric, now: DateTime<Utc>, id: &str, cx: &App) -> Option<AnyElement> {
if sus::is_weekly(metric) {
Some(badge(sus::weekly(metric, now), id, metric.label(), cx))
Some(badge(sus::reading(metric, now), id, metric.label(), cx))
} else if matches!(metric,Metric::Activity{label,..} if label=="Turns/7d") {
Some(badge(None, id, "Tracked activity has no quota", cx))
} else {
Expand Down
53 changes: 52 additions & 1 deletion crates/codexbar-app/src/ui_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8346,7 +8346,8 @@ fn gemini_models_weekly_is_the_row_pool_and_matures_at_twelve_hours(cx: &mut Tes
Some(if mature {
"Gemini Models weekly: SUS 42, red"
} else {
"Gemini Models weekly: SUS n/a, unavailable"
// Early before 12 hours: shown, but without a band, a forecast or a projected marker.
"Gemini Models weekly: SUS 42 early, early estimate"
Comment thread
HemSoft marked this conversation as resolved.
Resolved
})
);
assert_eq!(
Expand Down Expand Up @@ -8379,6 +8380,56 @@ fn gemini_models_weekly_is_the_row_pool_and_matures_at_twelve_hours(cx: &mut Tes
}
}

/// A Claude week through the real parser: n/a before 1 hour, an uncolored early SUS until 12 hours, then the firm,
/// banded score with its forecast, in both layouts.
#[gpui_kit::test]
fn claude_weekly_sus_is_early_from_one_hour_and_firm_from_twelve(cx: &mut TestAppContext) {
use chrono::Duration as Age;
let settings = TempSettings::new("claude-weekly-early", "{}");
let (handle, dashboard) = open_dashboard(cx, &settings);
for wide in [false, true] {
gpui_kit::VisualTestContext::from_window(handle, cx)
.simulate_resize(size(px(if wide { 2600. } else { 1440. }), px(1000.)));
for (age, sus, forecast) in [
(Age::minutes(59), "Weekly: SUS n/a, unavailable", None),
(Age::hours(1), "Weekly: SUS 7 early, early estimate", None),
(
Age::hours(12) - Age::minutes(1),
"Weekly: SUS 89 early, early estimate",
None,
),
(
Age::hours(12),
"Weekly: SUS 89, yellow",
Some("Weekly: 112% at reset · runs out in 5d 18h"),
),
(
Age::hours(15) + Age::minutes(55),
"Weekly: SUS 84, yellow",
Some("Weekly: 84% at reset"),
),
] {
cx.update(|cx| dashboard.update(cx, |d, cx| d.capture_claude_weekly(age, cx)));
cx.run_until_parked();
cx.update_window(handle, |_, window, cx| window.render_frame(cx))
.unwrap();
let (badge, at_reset) = if wide {
("window-weekly-claude-bar-sus", "window-weekly-claude-at-reset")
} else {
("focus-metric-1-sus", "focus-metric-1-at-reset")
};
assert_eq!(label(cx, handle, badge).as_deref(), Some(sus), "{age} wide={wide}");
if wide {
assert_eq!(
label(cx, handle, at_reset).as_deref(),
Some(forecast.unwrap_or("Weekly at reset: no forecast")),
"{age}"
);
}
}
}
}

/// Explicit, read-only Windows qualification. No GPUI app/window is created by this ordinary Rust test.
#[test]
#[ignore = "requires an explicitly authorized local snapshot directory"]
Expand Down
8 changes: 7 additions & 1 deletion crates/codexbar-app/src/widget_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,13 @@ fn preview_tile(ix: usize, view: &TileView, compact: bool, cx: &App) -> impl Int
)
.when_some(view.sus.as_ref(), |this, _| {
this.child(crate::sus::badge(
view.sus_score.and_then(codexbar_core::sus::Score::from_value),
{
use codexbar_core::sus::{Reading, Score};
view.sus_score
.and_then(Score::from_value)
.map(Reading::Firm)
.or_else(|| view.sus_early.and_then(Score::from_value).map(Reading::Early))
},
&format!("widget-{ix}-sus"),
"Widget weekly score",
cx,
Expand Down
26 changes: 26 additions & 0 deletions crates/codexbar-app/src/widget_feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ fn metric(account: &AccountSnapshot, metric: &Metric, now: DateTime<Utc>) -> Wid
sus: codexbar_core::sus::weekly(metric, now)
.filter(|_| account.sus_eligible(metric, now))
.map(|s| s.value()),
sus_early: codexbar_core::sus::early(metric, now)
.filter(|_| account.sus_eligible(metric, now))
.map(|s| s.value()),
sus_eligible: codexbar_core::sus::text(metric, now)
.filter(|_| account.sus_eligible(metric, now))
.is_some(),
Expand Down Expand Up @@ -197,6 +200,29 @@ mod tests {
assert_eq!(x.metrics[0].sus, None);
}

#[test]
fn an_early_week_carries_its_early_score_apart_from_the_firm_one() {
let early = AccountSnapshot::new(
AccountId::new("claude-early"),
Provider::Claude,
vec![Metric::Window {
label: "Weekly".into(),
used: 0.08,
resets_at: now() + chrono::Duration::hours(162),
pace: None,
length: Some(chrono::Duration::days(7)),
}],
now(),
);
let snapshot = build(&[early], &States::new(), &Layout::default(), now());
let metric = &snapshot.accounts[0].metrics[0];
assert_eq!(
(metric.sus, metric.sus_early, metric.sus_eligible),
(None, Some(45), true)
);
assert_eq!(metric.projected_percent, None);
}

#[test]
fn a_paced_window_carries_its_projection_and_an_unpaced_one_none() {
// 1%/h with 48h left on top of 42%: 90% at the reset (#197), near the limit (#234).
Expand Down
Loading
Loading