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
21 changes: 21 additions & 0 deletions apps/desktop-tauri/scripts/proof-scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,27 @@
{ "screenshot": "flyout-default", "clip": "panel" }
]
},
{
"name": "tray-cursor-weekly",
"query": "window=main&fixture=cursor-weekly",
"size": [310, 1032],
"steps": [
{ "click": { "selector": ".provider-grid__item", "label": "Cursor" } },
{ "expectText": ["Cursor", "Weekly", "cursor.user@example.com", "10% used", "$4.47"] },
{ "expectNoText": ["Grok Bot", "Auto", "API"] },
{ "screenshot": "tray-cursor-weekly", "clip": "panel" }
]
},
{
"name": "flyout-cursor-weekly",
"query": "window=flyout&fixture=cursor-weekly&theme=dark",
"size": [310, 1032],
"steps": [
{ "expectText": ["Cursor", "Weekly", "10% used", "$4.47"] },
{ "expectNoText": ["Grok Bot", "Auto", "API"] },
{ "screenshot": "flyout-cursor-weekly", "clip": "panel" }
]
},
{
"name": "settings-general-dark",
"query": "window=settings&tab=general&theme=dark",
Expand Down
4 changes: 1 addition & 3 deletions apps/desktop-tauri/src-tauri/src/tray_presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,9 +545,7 @@ pub(crate) fn named_extra_status(
.map(|extra| extra.title.as_str())
.filter(|title| !title.trim().is_empty())
.unwrap_or(declared.label);
let reading = extra
.map(|extra| &extra.window)
.filter(|window| !window.is_informational)
let reading = crate::usage_metric::named_extra_reading(snapshot, declared)
.map(|window| {
let label = crate::commands::compact_tray_status_label(window, language, show_as_used);
if snapshot.quota_estimated {
Expand Down
21 changes: 21 additions & 0 deletions apps/desktop-tauri/src-tauri/src/tray_presentation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,27 @@ fn named_extra_metric_without_a_reading_shows_a_labeled_dash() {
assert!(selected_tray_percents(&missing, &settings).0.is_nan());
}

#[test]
fn saved_grok_bot_shows_the_promoted_weekly_pool_in_the_tray() {
let mut settings = Settings {
show_as_used: true,
..Settings::default()
};
let preference = MetricPreference::NamedExtra("cursor-grok-bot".to_string());
settings.set_provider_metric(ProviderId::Cursor, preference.clone());
let mut weekly = grok_bot_cursor(None);
weekly.primary.used_percent = 10.0;
weekly.primary.is_informational = false;
weekly.secondary = None;
weekly.primary_label = Some("Weekly".to_string());

assert_eq!(
named_extra_status(&weekly, &preference, true, Language::English).as_deref(),
Some("Grok Bot 10% used")
);
assert_eq!(selected_tray_percents(&weekly, &settings).0, 10.0);
}

#[test]
fn named_extra_metric_without_a_reading_renders_an_unknown_icon_in_both_directions() {
for show_as_used in [true, false] {
Expand Down
21 changes: 18 additions & 3 deletions apps/desktop-tauri/src-tauri/src/usage_metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::borrow::Cow;
use std::cmp::Ordering;

use chrono::{DateTime, Utc};
use codexbar::core::{IconLane, ProviderId, UsagePace};
use codexbar::core::{IconLane, NamedExtraMetric, ProviderId, UsagePace};
use codexbar::settings::{MetricPreference, Settings};

use crate::commands::{ProviderUsageSnapshot, RateWindowSnapshot};
Expand Down Expand Up @@ -178,7 +178,7 @@ fn preferred_window(
MetricPreference::NamedExtra(id) => {
let declared = provider?.named_extra_metric(&id)?;
Some(
plan_window(snapshot, declared.id)
named_extra_reading(snapshot, declared)
.cloned()
.unwrap_or_else(unknown_window),
)
Expand Down Expand Up @@ -244,7 +244,7 @@ pub(crate) fn selected_named_extra_is_unknown(
.get_provider_metric(provider)
.named_extra_id()
.and_then(|id| provider.named_extra_metric(id))
.is_some_and(|declared| plan_window(snapshot, declared.id).is_none())
.is_some_and(|declared| named_extra_reading(snapshot, declared).is_none())
}

fn unknown_window() -> RateWindowSnapshot {
Expand All @@ -258,6 +258,21 @@ fn monthly_plan_window_id(provider: ProviderId) -> Option<&'static str> {
codexbar::core::instantiate_provider(provider).monthly_plan_window_id()
}

/// The known reading for a declared named extra allowance: its own extra
/// window, or the primary window once the provider has promoted the
/// allowance into it (see [`NamedExtraMetric::promoted_primary_label`]).
pub(crate) fn named_extra_reading(
snapshot: &ProviderUsageSnapshot,
declared: NamedExtraMetric,
) -> Option<&RateWindowSnapshot> {
let promoted = declared.promoted_primary_label.is_some()
&& snapshot.primary_label.as_deref() == declared.promoted_primary_label;
if promoted {
return non_informational(Some(&snapshot.primary));
}
plan_window(snapshot, declared.id)
}

fn plan_window<'a>(
snapshot: &'a ProviderUsageSnapshot,
window_id: &str,
Expand Down
16 changes: 16 additions & 0 deletions apps/desktop-tauri/src-tauri/src/usage_metric/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ fn named_extra_without_a_reading_stays_unknown_instead_of_borrowing_a_lane() {
assert_eq!(selected.used_percent, 0.0);
}

#[test]
fn saved_grok_bot_reads_the_promoted_weekly_pool() {
let mut weekly = grok_bot_snapshot(None);
weekly.primary = window(10.0);
weekly.secondary = None;
weekly.primary_label = Some("Weekly".to_string());
let selected = selected_usage_window(&weekly, &grok_bot_settings());
assert!(!selected.is_informational);
assert_eq!(selected.used_percent, 10.0);

// Without the weekly layout, a missing Grok Bot reading stays unknown.
let mut monthly = weekly.clone();
monthly.primary_label = Some("Total".to_string());
assert!(selected_usage_window(&monthly, &grok_bot_settings()).is_informational);
}

#[test]
fn undeclared_named_extra_falls_back_to_automatic() {
let mut settings = Settings::default();
Expand Down
47 changes: 47 additions & 0 deletions apps/desktop-tauri/src/proof/fixtures/scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const PROOF_SCENARIO_NAMES = [
"overview-all-accounts-private",
"errors",
"empty",
"cursor-weekly",
"openai-live-spend",
"claude-plan-hidden",
"codex-tool-activity",
Expand Down Expand Up @@ -183,6 +184,38 @@ function cursorSnapshot(now: Date, updatedMinutesAgo = 3): ProviderUsageSnapshot
);
}

/**
* X Subscription Cursor account (upstream #4695): the unified weekly pool is the
* primary "Weekly" meter. The monthly Auto/API lanes and the Grok Bot extra are
* gone; on-demand spend stays in the cost section.
*/
function cursorWeeklySnapshot(now: Date): ProviderUsageSnapshot {
return providerSnapshot(
{
providerId: "cursor",
displayName: "Cursor",
primary: rateWindow(now, 10.2, WEEK_MINUTES, 5 * DAY_MINUTES),
primaryLabel: "Weekly",
updatedAt: isoBefore(now, 3),
},
{
planName: "Cursor Enterprise",
accountEmail: "cursor.user@example.com",
sourceLabel: "web",
cost: {
used: 4.47,
limit: null,
remaining: null,
currencyCode: "USD",
period: "On-demand (billing cycle)",
resetsAt: isoAfter(now, 12 * DAY_MINUTES),
formattedUsed: "$4.47",
formattedLimit: null,
},
},
);
}

function geminiSnapshot(now: Date): ProviderUsageSnapshot {
return providerSnapshot(
{
Expand Down Expand Up @@ -494,6 +527,19 @@ function errorsScenario(now: Date): ProofScenario {
};
}

/** Cursor card on the unified weekly pool, with on-demand spend in the cost section. */
function cursorWeeklyScenario(now: Date): ProofScenario {
return {
name: "cursor-weekly",
settings: baseSettings({ enabledProviders: ["cursor"] }),
catalog: PROOF_PROVIDER_CATALOG,
providers: [cursorWeeklySnapshot(now)],
codexAccounts: emptyCodexAccounts(),
claudeAccounts: [],
localSpend: [],
};
}

function emptyScenario(): ProofScenario {
return {
name: "empty",
Expand Down Expand Up @@ -1048,6 +1094,7 @@ const BUILDERS: Record<ProofScenarioName, (now: Date) => ProofScenario> = {
"overview-all-accounts-private": (now) => overviewAllAccountsScenario(now, true),
errors: errorsScenario,
empty: emptyScenario,
"cursor-weekly": cursorWeeklyScenario,
"openai-live-spend": openAiLiveSpendScenario,
"claude-plan-hidden": claudePlanHiddenScenario,
"codex-tool-activity": codexToolActivityScenario,
Expand Down
4 changes: 4 additions & 0 deletions rust/src/core/provider/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pub struct NamedExtraMetric {
pub id: &'static str,
/// Label used when the snapshot carries no title for the window.
pub label: &'static str,
/// Snapshot primary label under which the provider has folded this
/// allowance into the primary window. A saved choice then reads the
/// primary instead of an absent extra window.
pub promoted_primary_label: Option<&'static str>,
}

/// Unique identifier for a provider
Expand Down
49 changes: 46 additions & 3 deletions rust/src/providers/cursor/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ pub(super) const GROK_BOT_TITLE: &str = "Grok Bot";
/// Optional monthly additional spend for unified personal memberships.
const UNIFIED_USAGE_PATH: &str = "/api/dashboard/v2/usage-status";
const UNIFIED_USAGE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
/// A 7-day shared pool; monthly billing cycles never span exactly this length.
const WEEKLY_POOL_WINDOW_MINUTES: u32 = 7 * 24 * 60;
/// Primary label once the shared weekly pool drives the main meter (upstream `"Weekly"`).
pub(super) const SHARED_WEEKLY_POOL_LABEL: &str = "Weekly";

#[derive(Debug)]
pub struct CursorUsageResult {
Expand All @@ -23,6 +27,28 @@ pub struct CursorUsageResult {
pub(super) email: Option<String>,
pub(super) plan_type: Option<String>,
pub(super) grok_bot: Option<NamedRateWindow>,
/// Overrides the primary label; `None` keeps the shared spec label.
pub(super) primary_label: Option<&'static str>,
}

/// Applies the sand usage feed to a built result. The Grok Bot allowance is
/// best-effort. A usable unified weekly pool replaces the monthly primary,
/// Auto and API lanes, and the Grok Bot extra it would duplicate. On-demand
/// cost is untouched.
fn apply_sand_usage(
result: &mut CursorUsageResult,
sand: Option<&SandUsageStatus>,
now: DateTime<Utc>,
) {
result.grok_bot = sand.and_then(|status| status.to_window(now));
let Some(pool) = sand.and_then(|status| status.shared_weekly_pool(now)) else {
return;
};
result.primary = pool;
result.secondary = None;
result.model_specific = None;
result.grok_bot = None;
result.primary_label = Some(SHARED_WEEKLY_POOL_LABEL);
}

/// Cursor API client
Expand Down Expand Up @@ -78,7 +104,7 @@ impl CursorApi {
.resolve_team_budget(&usage_summary, user_info.as_ref(), cookie_header)
.await;
let mut result = self.build_result_with_team_budget(usage_summary, user_info, team_budget);
result.grok_bot = sand_result.ok().flatten();
apply_sand_usage(&mut result, sand_result.ok().flatten().as_ref(), Utc::now());
if unified_personal && let Some(cost) = self.fetch_unified_monthly_cost(cookie_header).await
{
// Upstream #4593: the monthly billing read replaces the summary's
Expand Down Expand Up @@ -157,7 +183,7 @@ impl CursorApi {
async fn fetch_sand_usage(
&self,
cookie_header: &str,
) -> Result<Option<NamedRateWindow>, ProviderError> {
) -> Result<Option<SandUsageStatus>, ProviderError> {
let url = format!("{}/api/dashboard/get-sand-usage-status", self.base_url);
let response = self
.client
Expand All @@ -177,7 +203,7 @@ impl CursorApi {
.json()
.await
.map_err(|e| ProviderError::Parse(e.to_string()))?;
Ok(status.to_window(Utc::now()))
Ok(Some(status))
}

async fn fetch_user_info(&self, cookie_header: &str) -> Result<UserInfo, ProviderError> {
Expand Down Expand Up @@ -316,6 +342,7 @@ impl CursorApi {
email,
plan_type,
grok_bot: None,
primary_label: None,
}
}

Expand Down Expand Up @@ -514,9 +541,25 @@ struct SandUsageStatus {
has_non_zero_included_limit: Option<bool>,
included_limit_zero: Option<bool>,
sand_trial_expires_at: Option<String>,
/// The X Subscription shares this weekly pool across Cursor and Grok products.
is_unified: Option<bool>,
}

impl SandUsageStatus {
/// Upstream `CursorStatusSnapshot.toUsageSnapshot` (#4695): a unified pool
/// with a 7-day window and a reset still in the future becomes the main
/// meter. Trial-only, expired, non-unified and other-length pools return
/// `None`, so the monthly layout stays.
fn shared_weekly_pool(&self, now: DateTime<Utc>) -> Option<RateWindow> {
if self.is_unified != Some(true) {
return None;
}
let window = self.to_window(now)?.window;
(window.window_minutes == Some(WEEKLY_POOL_WINDOW_MINUTES)
&& window.resets_at.is_some_and(|reset| reset > now))
.then_some(window)
}

fn to_window(&self, now: DateTime<Utc>) -> Option<NamedRateWindow> {
let has_limit = self
.included_limit_zero
Expand Down
Loading