From 52f64efebaae18450f2501f433cf48cca04b5995 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 16 Aug 2026 06:05:53 +0000 Subject: [PATCH 1/6] Stop painting Cursor unavailable Plan as 0% on glance (SBS-876) --- CHANGELOG.md | 2 +- .../src-tauri/src/taskbar_widget.rs | 103 +++++++++++++-- .../src/components/PlanStatusCard.test.tsx | 34 +++++ .../src/components/PlanStatusCard.tsx | 16 ++- .../src/components/ProviderGrid.tsx | 15 ++- apps/desktop-tauri/src/floatbar/FloatBar.tsx | 59 ++++++--- .../src/lib/capacityPresentation.test.ts | 124 +++++++++++++++++- .../src/lib/capacityPresentation.ts | 124 ++++++++++++++---- .../desktop-tauri/src/lib/providerRow.test.ts | 21 +++ apps/desktop-tauri/src/lib/providerRow.ts | 5 +- apps/desktop-tauri/src/styles.css | 24 ++++ .../src/surfaces/ProviderDetailView.test.tsx | 38 ++++++ .../src/surfaces/ProviderDetailView.tsx | 45 +++++-- .../src/surfaces/TaskbarFlyout.test.tsx | 50 +++++++ .../src/surfaces/TaskbarFlyout.tsx | 51 ++++++- 15 files changed, 620 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e37141b4..893266a4 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Fixed - **Opening Settings from the floating bar no longer lands on General.** The bar asked for a `menuBar` tab the Settings window does not have, so the window fell back to General instead of Display. It now opens Display, the same tab the dashboard already uses for that action. The lists that name a Settings tab on each side of the bridge are compared in CI so a renamed tab cannot silently send you to General again. -- **Cursor no longer treats missing usage as zero.** An empty `individualUsage` object used to paint a 0% monthly bar and hide a real team pool sitting next to it. Monthly is now marked unavailable when Cursor reports no reading, and an empty individual object falls through to team usage. On-demand stays billed spend; plan and included dollars are labeled **Included** so they are not read as an invoice. A missing Composer tracking database is shown as unavailable, not as no activity. +- **Cursor no longer treats missing usage as zero.** An empty `individualUsage` object used to paint a 0% monthly bar and hide a real team pool sitting next to it. Monthly is now marked unavailable when Cursor reports no reading, and an empty individual object falls through to team usage. Glance surfaces no longer paint a 0% Plan bar when monthly is unavailable — Overview, flyout, detail, floating bar, and the native taskbar tile show the named state instead. On-demand stays billed spend; plan and included dollars are labeled **Included** so they are not read as an invoice. A missing Composer tracking database is shown as unavailable, not as no activity. - **Leftover English on glance surfaces now goes through locale keys.** Floatbar settings, freshness chips, account-status labels, Charts tab names, and About update copy used hardcoded English. They now use `en-US.ftl` (and zh-CN) so chips no longer show raw `stale` / `error` tokens. - **Refreshing model prices no longer blanks out the prices you already had.** The models.dev price cache was emptied before the new copy was written, so a second Ceiling process reading during that moment saw nothing, and a crash mid-write threw the cache away for good. Either way token costs quietly disappeared until a network refresh worked. The new copy is now written beside the old one and swapped in, so there is never a moment with no prices on disk. - **A Claude token refresh no longer replaces a symlinked credentials file.** Claude Code owns `.credentials.json`, and people who manage it with chezmoi or stow, or who share one file between WSL and Windows, keep a symlink at that path. Writing a refreshed token used to drop a fresh private file over the link, so the real file kept the old tokens and the next `chezmoi apply` signed Claude Code out. The refresh now follows the link and keeps the file's own permissions. It also takes a per-file lock on the shared file rather than on each path that points at it. If the app and the command line refresh the same login at the same moment, Claude retires the token the slower one used, so that one now steps aside and picks up the tokens that actually work instead of writing back dead ones and signing Claude Code out. Gemini and Grok already worked this way. diff --git a/apps/desktop-tauri/src-tauri/src/taskbar_widget.rs b/apps/desktop-tauri/src-tauri/src/taskbar_widget.rs index b64cccc1..53762b46 100644 --- a/apps/desktop-tauri/src-tauri/src/taskbar_widget.rs +++ b/apps/desktop-tauri/src-tauri/src/taskbar_widget.rs @@ -97,6 +97,9 @@ struct ConstrainingReadout<'a> { /// percentage is the wrong readout for a spend lane: "62%" of an $1800 cap /// does not tell you that you owe $1112.92 (SBS-191). amount: Option<&'a crate::commands::WindowAmountBridge>, + /// Set when the percent on this window is a placeholder, not a reading + /// (SBS-876). The tile must not round that placeholder to 0 or 100. + named_state: Option<&'a str>, } impl<'a> ConstrainingReadout<'a> { @@ -105,10 +108,47 @@ impl<'a> ConstrainingReadout<'a> { label, window, amount: None, + named_state: None, } } } +/// Inactive-row ids that mark `primary` as a placeholder, not a reading. +/// +/// Match by id only. Sweep: only Cursor writes 0% primary plus an inactive +/// row for that same window (`cursor-plan` / `cursor-monthly`). +fn primary_named_state(snapshot: &crate::commands::ProviderUsageSnapshot) -> Option<&str> { + let row = snapshot + .inactive_rate_windows + .iter() + .find(|row| row.id == "cursor-plan" || row.id == "cursor-monthly")?; + Some(if row.state == "unavailable" { + "unavailable" + } else { + "notEnforced" + }) +} + +fn strip_readout_percent(readout: &ConstrainingReadout<'_>, show_as_used: bool) -> Option { + if readout.named_state.is_some() { + return None; + } + let value = if show_as_used { + readout.window.used_percent + } else { + readout.window.remaining_percent + }; + Some(value.clamp(0.0, 100.0).round() as u8) +} + +fn strip_heat(snapshot: &crate::commands::ProviderUsageSnapshot) -> f64 { + let readout = constraining_readout(snapshot); + if readout.named_state.is_some() { + return -1.0; + } + readout.window.used_percent +} + /// Tile text for a currency-billed lane. /// /// Uncapped spend has no remaining figure to show, so "show remaining" falls @@ -203,6 +243,7 @@ fn cursor_actionable_windows( label: Some(label), window: &extra.window, amount: extra.amount.as_ref(), + named_state: None, }); } out @@ -222,6 +263,7 @@ fn cursor_on_demand_readout( }), window: &extra.window, amount: extra.amount.as_ref(), + named_state: None, }) } @@ -293,7 +335,12 @@ fn cursor_strip_readout( { return readout; } - ConstrainingReadout::new(snapshot.primary_label.as_deref(), &snapshot.primary) + ConstrainingReadout { + label: snapshot.primary_label.as_deref(), + window: &snapshot.primary, + amount: None, + named_state: primary_named_state(snapshot), + } } fn constraining_readout( @@ -303,7 +350,12 @@ fn constraining_readout( return cursor_strip_readout(snapshot); } - let mut best = ConstrainingReadout::new(snapshot.primary_label.as_deref(), &snapshot.primary); + let mut best = ConstrainingReadout { + label: snapshot.primary_label.as_deref(), + window: &snapshot.primary, + amount: None, + named_state: primary_named_state(snapshot), + }; // Claude's per-model weekly caps are parallel sub-pools, not blockers: at // 100% you switch model rather than stop. The tile shows one lane, so @@ -343,6 +395,7 @@ fn constraining_readout( label: Some(extra.title.as_str()), window: &extra.window, amount: extra.amount.as_ref(), + named_state: None, }); } out @@ -387,10 +440,8 @@ where return Some(*hit); } candidates.into_iter().max_by(|a, b| { - constraining_readout(a) - .window - .used_percent - .total_cmp(&constraining_readout(b).window.used_percent) + strip_heat(a) + .total_cmp(&strip_heat(b)) .then_with(|| b.account_id.cmp(&a.account_id)) }) } @@ -1133,14 +1184,8 @@ mod windows_host { let constraining = snapshot .filter(|snapshot| snapshot.error.is_none()) .map(super::constraining_readout); - let percent = constraining.map(|readout| { - let value = if settings.show_as_used { - readout.window.used_percent - } else { - readout.window.remaining_percent - }; - value.clamp(0.0, 100.0).round() as u8 - }); + let percent = constraining + .and_then(|readout| strip_readout_percent(&readout, settings.show_as_used)); // A spend lane's headline is the money, not the fraction. let spend = constraining.and_then(|readout| readout.amount); let amount_label = @@ -3273,6 +3318,36 @@ mod tests { ); } + /// SBS-876: Cursor still writes 0% primary when monthly is missing, plus + /// `cursor-plan` unavailable. The native tile must not round that + /// placeholder to Some(0) or Some(100). + #[test] + fn cursor_strip_omits_percent_when_plan_is_unavailable() { + let mut snapshot = snap("cursor", None, 0.0); + snapshot.primary_label = Some("Plan".into()); + snapshot + .inactive_rate_windows + .push(crate::commands::InactiveRateWindowSnapshot { + id: "cursor-plan".into(), + title: "Plan".into(), + description: "No usage reported".into(), + state: "unavailable".into(), + }); + + let readout = constraining_readout(&snapshot); + assert_eq!(readout.named_state, Some("unavailable")); + assert_eq!(strip_readout_percent(&readout, true), None); + assert_eq!(strip_readout_percent(&readout, false), None); + + // Auto still wins the strip when present; the placeholder does not. + snapshot.secondary = Some(rate_window(42.0, Some(43_200))); + snapshot.secondary_label = Some("Auto".into()); + let with_auto = constraining_readout(&snapshot); + assert_eq!(with_auto.label, Some("Auto")); + assert!(with_auto.named_state.is_none()); + assert_eq!(strip_readout_percent(&with_auto, true), Some(42)); + } + #[test] fn uncapped_spend_reports_spend_even_in_remaining_mode() { // No denominator means no headroom figure exists. Falling back to diff --git a/apps/desktop-tauri/src/components/PlanStatusCard.test.tsx b/apps/desktop-tauri/src/components/PlanStatusCard.test.tsx index ea298622..cb89a821 100644 --- a/apps/desktop-tauri/src/components/PlanStatusCard.test.tsx +++ b/apps/desktop-tauri/src/components/PlanStatusCard.test.tsx @@ -309,6 +309,40 @@ describe("PlanStatusCard", () => { expect(screen.queryByText("lifted")).toBeNull(); }); + /** + * SBS-876: a missing Cursor plan used to paint Plan 0% used (or 100% left) + * and a quiet "Plan Unavailable" row at the same time. + */ + it("shows Unavailable for a missing Cursor plan and does not paint 0% or 100% left", () => { + render( + , + ); + + expect(screen.getByText("unavailable")).toBeInTheDocument(); + expect(screen.getByText("Plan")).toBeInTheDocument(); + expect(screen.queryByText(/0% used/)).toBeNull(); + expect(screen.queryByText(/100% left/)).toBeNull(); + expect(screen.queryByText(/0% left/)).toBeNull(); + }); + it("separates unavailable windows from not-enforced ones", () => { render( {provider.error}

) : (
- + {meters.primary && ( + + )} {meters.companions.map((meter) => ( { - const constraining = constrainingWindow(provider).window; + const gridPercent = (provider: ProviderUsageSnapshot): number | null => { + const constraining = constrainingWindow(provider); + // Named-state windows are not a 0/100 bar (SBS-876 / CEILING_UI.md). + if (constraining.namedState) return null; const pct = showAsUsed - ? constraining.usedPercent - : constraining.remainingPercent; + ? constraining.window.usedPercent + : constraining.window.remainingPercent; return Math.max(0, Math.min(100, pct)); }; const totalItems = providers.length + 1; @@ -106,6 +108,7 @@ export default function ProviderGrid({ {visibleProviders.map((p) => { const status = providerGlanceStatus(p); const brand = getProviderIcon(p.providerId).brandColor; + const percent = gridPercent(p); return (