From 1f12aba15328245a3e5f0d584454a67515d17b25 Mon Sep 17 00:00:00 2001 From: tsouth89 Date: Wed, 5 Aug 2026 00:46:11 -0400 Subject: [PATCH 1/2] Mark expected usage on the bars, and unhide Cursor on-demand (1.5.24) The pace prediction existed but never reached the app window: the verdict added in #203 lives in MenuCard, which only the tray renders. The window uses ProviderDetailView, which nothing in that change touched. - Mark where usage should be on every weekly/monthly bar, in the overview and in a provider's detail view. The marker is where the bar's edge should be right now, and it mirrors for bars that show remaining, so it reads the same either way. Overspending fills the span between edge and marker with a striped band, so the question is "how far ahead" rather than "which side of a line". - Derive it from elapsed time against the window's own duration, not from PaceSnapshot. It therefore needs no provider support and applies to every long window, instead of the one window pace is computed for. - Skip windows under 12h. A 5-hour session is not spent evenly, so a marker there would sweep across the bar and mean nothing. - Stop filtering Cursor's on-demand lane out of the detail view. It is the only Cursor lane that bills real money and now carries that money beside its bar, so 1.5.23's overdraft work can finally reach the main window. Verified by running the app against live accounts, which caught two things tests could not: `.usage-bar` collided with an existing flex component and collapsed every bar to zero width, and a red marker on an exhausted red bar was invisible. The marker and band are now drawn in the text colour. --- .github/release-notes-1.5.24.md | 33 +++++ CHANGELOG.md | 12 ++ Cargo.lock | 4 +- apps/desktop-tauri/package.json | 2 +- apps/desktop-tauri/src-tauri/Cargo.toml | 2 +- apps/desktop-tauri/src-tauri/tauri.conf.json | 2 +- .../src/components/PlanStatusCard.tsx | 44 ++++++- apps/desktop-tauri/src/i18n/keys.ts | 1 + .../src/lib/capacityPresentation.ts | 7 +- .../src/lib/expectedPace.test.ts | 119 ++++++++++++++++++ apps/desktop-tauri/src/lib/expectedPace.ts | 94 ++++++++++++++ apps/desktop-tauri/src/styles.css | 45 +++++++ .../src/surfaces/ProviderDetailView.test.tsx | 19 ++- .../src/surfaces/ProviderDetailView.tsx | 58 +++++++-- rust/Cargo.toml | 2 +- rust/src/locale.rs | 1 + rust/src/locale/en-US.ftl | 1 + rust/src/locale/zh-CN.ftl | 1 + version.env | 4 +- 19 files changed, 425 insertions(+), 26 deletions(-) create mode 100644 .github/release-notes-1.5.24.md create mode 100644 apps/desktop-tauri/src/lib/expectedPace.test.ts create mode 100644 apps/desktop-tauri/src/lib/expectedPace.ts diff --git a/.github/release-notes-1.5.24.md b/.github/release-notes-1.5.24.md new file mode 100644 index 00000000..d7d6d336 --- /dev/null +++ b/.github/release-notes-1.5.24.md @@ -0,0 +1,33 @@ +## Highlights + +### Your usage bars now show where you *should* be + +Ceiling could already tell whether you were on course to run out early. It just wasn't on the thing you actually look at. + +Every weekly and monthly bar — in the Overview and in a provider's detail view — now carries a marker for where usage should be at this point in the window. One rule, everywhere: **the marker is where the bar's edge should be right now.** + +- Edge sitting at the marker → you're on pace. +- Edge past it → you're ahead of budget, and the overspend fills in as a striped band so you can see *how far*, not just *which side*. +- Bars set to show remaining capacity mirror the marker, so it means the same thing either way. + +It's worked out from elapsed time against the window's own length, which means it needs nothing from the provider and shows up on every long window at once, instead of only the single window a pace prediction was calculated for. + +Five-hour session bars are deliberately left plain. Nobody spends a session evenly, so a marker there would drift across the bar all afternoon and tell you nothing. + +### Cursor on-demand spend is visible where you look + +1.5.23 added the dollars behind Cursor's on-demand lane. The main window was filtering that lane out by name, so the number never made it to the screen most people use. + +The **On-demand** row now appears in a provider's detail view with its spend beside it — `$0.00 of $1.00`. On-demand is the only Cursor lane that bills real money, which makes it the row worth showing. + +## Installers + +- **Ceiling-1.5.24-Setup.exe** - standard installer +- **Ceiling-1.5.24-portable.exe** - portable +- **Ceiling-1.5.24-Store-Setup.exe** - Microsoft Store package (WebView2 bundled) + +Portable builds show alerts as banners but do not keep them in the notification center. That requires the Start Menu shortcut installed by the standard or Store build. + +--- + +**Full Changelog**: https://github.com/tsouth89/ceiling/compare/v1.5.23...v1.5.24 diff --git a/CHANGELOG.md b/CHANGELOG.md index 678056a7..b8a5fc51 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [Ceiling] 1.5.24 - 2026-08-05 + +Puts the pace answer on the usage bars themselves, in the main window. + +### Added +- Every weekly and monthly bar in the overview and in a provider's detail view now carries a marker showing where usage *should* be at this point in the window. The rule is the same wherever it appears: the marker is where the bar's edge should be right now. Run past it and the overspend shows as a striped band, so "how far ahead am I" reads as a distance rather than as a line to decode. Bars shown as remaining mirror the marker so it keeps the same meaning either way. +- The marker is derived from elapsed time against the window's own duration, so it needs no provider support and appears on every long window at once, rather than on the single window pace prediction is calculated for. +- Short windows are left alone. A five-hour session is not spent evenly, so a marker there would sweep across the bar and mean nothing; anything under twelve hours is skipped. + +### Fixed +- Cursor's on-demand lane now appears in the app window, with the money beside it. It is the only Cursor lane that bills real money, and the detail view was filtering it out by name, so 1.5.23's work to surface overdraft spend could not reach the surface most people actually look at. + ## [Ceiling] 1.5.23 - 2026-08-05 Adds a pace verdict and Cursor on-demand spend, both from user feature requests. diff --git a/Cargo.lock b/Cargo.lock index 5f87aee3..49e76c47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,7 +664,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "codexbar" -version = "1.5.23" +version = "1.5.24" dependencies = [ "aes-gcm", "anyhow", @@ -706,7 +706,7 @@ dependencies = [ [[package]] name = "codexbar-desktop-tauri" -version = "1.5.23" +version = "1.5.24" dependencies = [ "chrono", "chrono-tz", diff --git a/apps/desktop-tauri/package.json b/apps/desktop-tauri/package.json index 498e6bcb..51e6c109 100644 --- a/apps/desktop-tauri/package.json +++ b/apps/desktop-tauri/package.json @@ -1,7 +1,7 @@ { "name": "desktop-tauri", "private": true, - "version": "1.5.23", + "version": "1.5.24", "packageManager": "pnpm@10.18.1", "type": "module", "scripts": { diff --git a/apps/desktop-tauri/src-tauri/Cargo.toml b/apps/desktop-tauri/src-tauri/Cargo.toml index 025e20ba..0e804aa9 100644 --- a/apps/desktop-tauri/src-tauri/Cargo.toml +++ b/apps/desktop-tauri/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codexbar-desktop-tauri" -version = "1.5.23" +version = "1.5.24" edition = "2024" publish = false diff --git a/apps/desktop-tauri/src-tauri/tauri.conf.json b/apps/desktop-tauri/src-tauri/tauri.conf.json index f0c81b04..8d946e22 100644 --- a/apps/desktop-tauri/src-tauri/tauri.conf.json +++ b/apps/desktop-tauri/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Ceiling", - "version": "1.5.23", + "version": "1.5.24", "identifier": "io.github.tsouth89.ceiling", "build": { "beforeDevCommand": "pnpm run dev", diff --git a/apps/desktop-tauri/src/components/PlanStatusCard.tsx b/apps/desktop-tauri/src/components/PlanStatusCard.tsx index a0ecea4f..224adb80 100644 --- a/apps/desktop-tauri/src/components/PlanStatusCard.tsx +++ b/apps/desktop-tauri/src/components/PlanStatusCard.tsx @@ -3,6 +3,7 @@ import type { ProviderUsageSnapshot, RateWindowSnapshot } from "../types/bridge" import { ProviderIcon } from "./providers/ProviderIcon"; import { accountIdentityLabel } from "../lib/providerRow"; import { getProviderIcon } from "./providers/providerIcons"; +import { expectedOverlay } from "../lib/expectedPace"; import { useFormattedResetTime } from "../hooks/useFormattedResetTime"; import { useLocale } from "../hooks/useLocale"; import { @@ -89,6 +90,8 @@ function MeterRow({ const awaitingReset = snap.isExhausted && showReset; // Optional setting: promote reset into the hero slot when depleted. const resetAsHero = awaitingReset && showResetWhenExhausted; + // Where usage should be by now, on long windows only. + const expected = expectedOverlay(snap, showAsUsed); return (
)}
-
-
+
+
+
+ {expected?.gap && ( +
+ )} +
+ {expected && ( +
+ )}
); diff --git a/apps/desktop-tauri/src/i18n/keys.ts b/apps/desktop-tauri/src/i18n/keys.ts index c0e43aa0..1ec495d6 100644 --- a/apps/desktop-tauri/src/i18n/keys.ts +++ b/apps/desktop-tauri/src/i18n/keys.ts @@ -497,6 +497,7 @@ export const ALL_LOCALE_KEYS = [ "PanelLeftSuffix", "PanelOnPaceBudget", "PanelAmountOf", + "UsageExpectedByNow", "PaceVerdictOnTrack", "PaceVerdictAhead", "PaceVerdictPlenty", diff --git a/apps/desktop-tauri/src/lib/capacityPresentation.ts b/apps/desktop-tauri/src/lib/capacityPresentation.ts index 7221df0c..9f188360 100644 --- a/apps/desktop-tauri/src/lib/capacityPresentation.ts +++ b/apps/desktop-tauri/src/lib/capacityPresentation.ts @@ -2,6 +2,7 @@ import type { PaceSnapshot, ProviderUsageSnapshot, RateWindowSnapshot, + WindowAmountBridge, } from "../types/bridge"; export type CapacityFreshness = "live" | "stale" | "error"; @@ -10,6 +11,8 @@ export type ConstrainingWindow = { id: string; label: string; window: RateWindowSnapshot; + /** Money behind this lane, when the provider meters it in currency. */ + amount?: WindowAmountBridge | null; }; export type GlanceMeters = { @@ -252,12 +255,14 @@ function nonPrimaryWindows( label: string | null | undefined, window: RateWindowSnapshot | null | undefined, fallback: string, + amount?: WindowAmountBridge | null, ) => { if (!window) return; out.push({ id, label: label?.trim() || fallback, window, + amount, }); }; @@ -266,7 +271,7 @@ function nonPrimaryWindows( push("tertiary", provider.tertiaryLabel, provider.tertiary, "Extra"); for (const extra of provider.extraRateWindows ?? []) { if (extra.id === "reset-credits") continue; - push(`extra-${extra.id}`, extra.title, extra.window, extra.title); + push(`extra-${extra.id}`, extra.title, extra.window, extra.title, extra.amount); } return out; } diff --git a/apps/desktop-tauri/src/lib/expectedPace.test.ts b/apps/desktop-tauri/src/lib/expectedPace.test.ts new file mode 100644 index 00000000..1a3c7116 --- /dev/null +++ b/apps/desktop-tauri/src/lib/expectedPace.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, it } from "vitest"; + +import type { RateWindowSnapshot } from "../types/bridge"; +import { expectedOverlay, expectedUsedPercent } from "./expectedPace"; + +const NOW = Date.parse("2026-08-05T12:00:00.000Z"); +const MINUTE = 60 * 1000; + +function win(overrides: Partial = {}): RateWindowSnapshot { + return { + usedPercent: 50, + remainingPercent: 50, + windowMinutes: 10080, + resetsAt: new Date(NOW + 3.5 * 24 * 60 * MINUTE).toISOString(), + resetDescription: null, + isExhausted: false, + reservePercent: null, + reserveDescription: null, + reserveWillLastToReset: false, + reserveEtaSeconds: null, + ...overrides, + }; +} + +describe("expectedUsedPercent", () => { + it("is the elapsed fraction of the window", () => { + // Weekly window resetting in 3.5 days => exactly half elapsed. + expect(expectedUsedPercent(win(), NOW)).toBeCloseTo(50, 5); + }); + + it("tracks a monthly window", () => { + const monthly = win({ + windowMinutes: 30 * 24 * 60, + resetsAt: new Date(NOW + 6 * 24 * 60 * MINUTE).toISOString(), + }); + // 24 of 30 days elapsed. + expect(expectedUsedPercent(monthly, NOW)).toBeCloseTo(80, 5); + }); + + it("stays silent on short windows, where an even burn means nothing", () => { + // A 5-hour session window: nobody paces one linearly. + const session = win({ + windowMinutes: 300, + resetsAt: new Date(NOW + 150 * MINUTE).toISOString(), + }); + expect(expectedUsedPercent(session, NOW)).toBeNull(); + }); + + it("needs both a duration and a reset to place the marker", () => { + expect(expectedUsedPercent(win({ windowMinutes: null }), NOW)).toBeNull(); + expect(expectedUsedPercent(win({ resetsAt: null }), NOW)).toBeNull(); + expect(expectedUsedPercent(win({ resetsAt: "not-a-date" }), NOW)).toBeNull(); + expect(expectedUsedPercent(win({ windowMinutes: 0 }), NOW)).toBeNull(); + }); + + it("stays silent outside the window rather than pinning to an edge", () => { + // Already reset. + const past = win({ resetsAt: new Date(NOW - MINUTE).toISOString() }); + expect(expectedUsedPercent(past, NOW)).toBeNull(); + // Reset further out than the window is long: the window has not started. + const future = win({ + resetsAt: new Date(NOW + 30 * 24 * 60 * MINUTE).toISOString(), + }); + expect(expectedUsedPercent(future, NOW)).toBeNull(); + }); +}); + +describe("expectedOverlay", () => { + // 80% of a monthly window elapsed. + const monthly = (usedPercent: number) => + win({ + usedPercent, + remainingPercent: 100 - usedPercent, + windowMinutes: 30 * 24 * 60, + resetsAt: new Date(NOW + 6 * 24 * 60 * MINUTE).toISOString(), + }); + + it("mirrors the marker when the bar shows remaining capacity", () => { + expect(expectedOverlay(monthly(80), true, NOW)?.tickPercent).toBeCloseTo(80, 5); + expect(expectedOverlay(monthly(80), false, NOW)?.tickPercent).toBeCloseTo(20, 5); + }); + + it("reads 'ahead' from usage, not from bar geometry", () => { + // Same overspending window in both display modes. + expect(expectedOverlay(monthly(92), true, NOW)?.ahead).toBe(true); + expect(expectedOverlay(monthly(92), false, NOW)?.ahead).toBe(true); + expect(expectedOverlay(monthly(60), true, NOW)?.ahead).toBe(false); + expect(expectedOverlay(monthly(60), false, NOW)?.ahead).toBe(false); + }); + + it("shades the overspend band in both modes, spanning edge to marker", () => { + // Used 92% against an expected 80%. + const used = expectedOverlay(monthly(92), true, NOW); + expect(used?.gap?.left).toBeCloseTo(80, 5); + expect(used?.gap?.width).toBeCloseTo(12, 5); + + // Same window shown as remaining: edge at 8%, marker at 20%. + const left = expectedOverlay(monthly(92), false, NOW); + expect(left?.gap?.left).toBeCloseTo(8, 5); + expect(left?.gap?.width).toBeCloseTo(12, 5); + }); + + it("draws no band when under budget", () => { + expect(expectedOverlay(monthly(60), true, NOW)?.gap).toBeNull(); + }); + + it("draws no band for a hairline difference", () => { + // Half a percent off pace is not worth a stripe. + expect(expectedOverlay(monthly(80.5), true, NOW)?.gap).toBeNull(); + }); + + it("passes the silence through", () => { + const session = win({ + windowMinutes: 300, + resetsAt: new Date(NOW + 150 * MINUTE).toISOString(), + }); + expect(expectedOverlay(session, true, NOW)).toBeNull(); + }); +}); diff --git a/apps/desktop-tauri/src/lib/expectedPace.ts b/apps/desktop-tauri/src/lib/expectedPace.ts new file mode 100644 index 00000000..b0bb0160 --- /dev/null +++ b/apps/desktop-tauri/src/lib/expectedPace.ts @@ -0,0 +1,94 @@ +import type { RateWindowSnapshot } from "../types/bridge"; + +/** + * Windows shorter than this get no expected-usage marker. + * + * "Where you should be by now" assumes consumption spreads evenly across the + * window. That is a fair budget for a weekly or monthly quota, but meaningless + * for a 5-hour session: nobody paces a session linearly, so a marker there + * would sweep across the bar and mean nothing. + */ +const MIN_WINDOW_MINUTES = 12 * 60; + +/** Below this the fill edge and the marker are the same pixel; drawing a gap + * there is visual noise rather than information. */ +const MIN_GAP_PERCENT = 1.5; + +export interface ExpectedOverlay { + /** Marker position along the bar, already mirrored for the display mode. */ + tickPercent: number; + /** Shaded span between the fill edge and the marker, when worth drawing. */ + gap: { left: number; width: number } | null; + /** True when more has been used than the calendar says it should be. */ + ahead: boolean; + /** Expected usage as a percentage, for labelling. */ + expectedUsedPercent: number; +} + +/** + * Where usage *should* be by this point in the window, as a percentage. + * + * Derived from elapsed time alone, so it works for any window that reports a + * duration and a reset — no pace calculation and no provider support needed. + * Returns null when the window is too short to be worth marking, or when the + * provider does not report enough to place the marker honestly. + */ +export function expectedUsedPercent( + snap: RateWindowSnapshot, + nowMs: number = Date.now(), +): number | null { + const minutes = snap.windowMinutes; + if (minutes == null || !Number.isFinite(minutes) || minutes <= 0) return null; + if (minutes < MIN_WINDOW_MINUTES) return null; + if (!snap.resetsAt) return null; + + const resetMs = Date.parse(snap.resetsAt); + if (!Number.isFinite(resetMs)) return null; + + const durationMs = minutes * 60 * 1000; + const startMs = resetMs - durationMs; + const elapsed = nowMs - startMs; + if (elapsed <= 0 || elapsed >= durationMs) return null; + + return (elapsed / durationMs) * 100; +} + +/** + * Everything needed to draw the expected-usage overlay on a bar. + * + * The bar can show either used or remaining capacity, so the marker is mirrored + * to match: in both modes it lands where the bar's edge *should* be right now. + * `ahead` is computed from usage rather than from bar geometry, so it means the + * same thing in either mode. + */ +export function expectedOverlay( + snap: RateWindowSnapshot, + showAsUsed: boolean, + nowMs: number = Date.now(), +): ExpectedOverlay | null { + const expected = expectedUsedPercent(snap, nowMs); + if (expected == null) return null; + + const used = clamp(snap.usedPercent); + const edge = showAsUsed ? used : clamp(snap.remainingPercent); + const tickPercent = clamp(showAsUsed ? expected : 100 - expected); + + const lo = Math.min(edge, tickPercent); + const hi = Math.max(edge, tickPercent); + const width = hi - lo; + const ahead = used > expected; + + return { + tickPercent, + // Only shade when overspending: that is the actionable case, and a band on + // every bar all the time would be noise. + gap: ahead && width >= MIN_GAP_PERCENT ? { left: lo, width } : null, + ahead, + expectedUsedPercent: expected, + }; +} + +function clamp(value: number): number { + if (!Number.isFinite(value)) return 0; + return Math.min(100, Math.max(0, value)); +} diff --git a/apps/desktop-tauri/src/styles.css b/apps/desktop-tauri/src/styles.css index 237cc8d4..28ccc7c5 100644 --- a/apps/desktop-tauri/src/styles.css +++ b/apps/desktop-tauri/src/styles.css @@ -4638,6 +4638,7 @@ html:has(.menu-surface--tray) { } .plan-status-card__bar { + position: relative; height: 5px; border-radius: 999px; background: rgba(148, 163, 184, 0.18); @@ -6924,12 +6925,56 @@ body:has(.dashboard-shell) #root { font-size: 13px; } .provider-focus__progress { + position: relative; height: 5px; margin-top: 10px; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--text-secondary) 20%, transparent); } + +/* Expected-usage overlay. + + The bar itself stays clipped and rounded; the marker lives in this wrapper + instead, so it can stand proud of the bar. That matters twice over: it stops + the marker reading as the fill's own edge when usage is exactly on pace, and + it keeps part of the marker on the card background, where `--text-primary` + is guaranteed to contrast whatever colour the fill happens to be. */ +.pace-overlay { + position: relative; +} + +/* Where the bar's edge should be by this point in the window. */ +.pace-overlay__tick { + position: absolute; + top: -3px; + bottom: -3px; + width: 2px; + margin-left: -1px; + border-radius: 1px; + background: var(--text-primary); + opacity: 0.75; + pointer-events: none; +} + +.pace-overlay__tick[data-ahead="true"] { + opacity: 0.95; +} + +/* Overspending reads as a band, not as "which side of a line am I on". + Striped in the text colour rather than red: an exhausted bar is already red, + and red-on-red made both the band and the marker disappear. */ +.pace-overlay__gap { + position: absolute; + top: 0; + bottom: 0; + background: repeating-linear-gradient( + -45deg, + color-mix(in srgb, var(--text-primary) 42%, transparent) 0 2px, + transparent 2px 4px + ); + pointer-events: none; +} .provider-focus__progress-fill { height: 100%; border-radius: inherit; diff --git a/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx b/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx index 1cf63805..28dbd1e4 100644 --- a/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx +++ b/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx @@ -36,7 +36,18 @@ function codex(): ProviderUsageSnapshot { tertiary: null, extraRateWindows: [ { id: "spark", title: "Codex Spark", window: rate(0) }, - { id: "promo", title: "Promotional", window: rate(100) }, + { + id: "cursor-on-demand", + title: "On-demand", + window: rate(35), + amount: { + used: 3.5, + limit: 10, + currencyCode: "USD", + formattedUsed: "$3.50", + formattedLimit: "$10.00", + }, + }, ], inactiveRateWindows: [ { @@ -124,7 +135,11 @@ describe("ProviderDetailView", () => { // A window that dropped out of a successful response reads as Unavailable, // never as "not currently enforced" or a fabricated percentage. expect(screen.getByText("Unavailable")).toBeInTheDocument(); - expect(screen.queryByText("Promotional")).toBeNull(); + // On-demand used to be filtered out of this view. It is the only Cursor + // lane that bills real money, and it now carries that money beside its + // bar, so it belongs here. + expect(screen.getByText("On-demand")).toBeInTheDocument(); + expect(screen.getByText("$3.50 of $10.00")).toBeInTheDocument(); expect(screen.getAllByText(/Weekly pace/)).toHaveLength(2); expect(screen.getAllByText(/Far ahead of budget/)).toHaveLength(2); expect(container.querySelector(".provider-focus__pace-fill")?.getAttribute("data-tone")) diff --git a/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx b/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx index d82cc575..5432ff18 100644 --- a/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx +++ b/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx @@ -5,8 +5,10 @@ import type { ProviderLocalUsageSummary, ProviderUsageSnapshot, RateWindowSnapshot, + WindowAmountBridge, } from "../types/bridge"; import { ProviderIcon } from "../components/providers/ProviderIcon"; +import { expectedOverlay } from "../lib/expectedPace"; import { useFormattedResetTime } from "../hooks/useFormattedResetTime"; import { useLocale } from "../hooks/useLocale"; import { formatRelativeUpdated } from "../lib/relativeTime"; @@ -23,6 +25,7 @@ type DetailWindow = { id: string; label: string; window: RateWindowSnapshot; + amount?: WindowAmountBridge | null; }; interface ProviderDetailViewProps { @@ -88,13 +91,42 @@ function DetailProgress({ window: RateWindowSnapshot; showAsUsed: boolean; }) { + // Where usage should be by this point in the window. Long windows only: + // see `expectedUsedPercent`. + const expected = expectedOverlay(window, showAsUsed); return ( -
-
+
+
+
+ {expected?.gap && ( +
+ )} +
+ {expected && ( +
+ )}
); } @@ -123,6 +155,13 @@ function SecondaryWindow({ {reset && {reset}}
+ {metric.amount && ( +
+ {metric.amount.formattedLimit + ? `${metric.amount.formattedUsed} of ${metric.amount.formattedLimit}` + : metric.amount.formattedUsed} +
+ )}
); @@ -254,9 +293,10 @@ export default function ProviderDetailView({ ); const metrics = useMemo( () => - allMeasuredWindows(provider) - .slice(1) - .filter((metric) => !/promotional|on-demand/i.test(metric.label)), + // On-demand is the only Cursor lane that bills real money, and it now + // carries that money beside its bar, so it is the most actionable row + // here rather than noise. (Promotional no longer exists at all.) + allMeasuredWindows(provider).slice(1), [provider], ); const primaryPercent = Math.round(percentFor(provider.primary, showAsUsed)); diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1a089ac9..e2746552 100755 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codexbar" -version = "1.5.23" +version = "1.5.24" edition = "2024" authors = ["Ceiling Contributors"] description = "Local-first Windows companion for monitoring AI capacity and reset times" diff --git a/rust/src/locale.rs b/rust/src/locale.rs index ae9dd06b..375ebbc1 100644 --- a/rust/src/locale.rs +++ b/rust/src/locale.rs @@ -737,6 +737,7 @@ locale_keys! { PanelLeftSuffix, PanelOnPaceBudget, PanelAmountOf, + UsageExpectedByNow, PaceVerdictOnTrack, PaceVerdictAhead, PaceVerdictPlenty, diff --git a/rust/src/locale/en-US.ftl b/rust/src/locale/en-US.ftl index e9a7411a..b2036b33 100644 --- a/rust/src/locale/en-US.ftl +++ b/rust/src/locale/en-US.ftl @@ -492,6 +492,7 @@ PanelUsedSuffix = used PanelLeftSuffix = left PanelOnPaceBudget = On-pace budget PanelAmountOf = of +UsageExpectedByNow = Expected { "{}" }% used by now PaceVerdictOnTrack = On track PaceVerdictAhead = Ahead of pace PaceVerdictPlenty = Plenty left diff --git a/rust/src/locale/zh-CN.ftl b/rust/src/locale/zh-CN.ftl index 8ae6ce9e..e397e823 100644 --- a/rust/src/locale/zh-CN.ftl +++ b/rust/src/locale/zh-CN.ftl @@ -492,6 +492,7 @@ PanelUsedSuffix = 已用 PanelLeftSuffix = 剩余 PanelOnPaceBudget = 按节奏预算 PanelAmountOf = 共 +UsageExpectedByNow = 按当前时间应已用 { "{}" }% PaceVerdictOnTrack = 按计划 PaceVerdictAhead = 超出节奏 PaceVerdictPlenty = 余量充足 diff --git a/version.env b/version.env index 7a90c9a1..20f659ed 100755 --- a/version.env +++ b/version.env @@ -1,2 +1,2 @@ -MARKETING_VERSION=1.5.23 -BUILD_NUMBER=125 +MARKETING_VERSION=1.5.24 +BUILD_NUMBER=126 From 168d6eb298cf239c8764345f4681960e4b269a80 Mon Sep 17 00:00:00 2001 From: tsouth89 Date: Wed, 5 Aug 2026 00:52:37 -0400 Subject: [PATCH 2/2] Address review: localise the expected-usage strings Both the marker tooltip and the amount row hardcoded English in ProviderDetailView, while the same strings already had locale keys used elsewhere. Reuse UsageExpectedByNow and PanelAmountOf so the window matches the overview and does not drift from the translations. --- .../src/surfaces/ProviderDetailView.test.tsx | 4 +++- apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx b/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx index 28dbd1e4..84be528e 100644 --- a/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx +++ b/apps/desktop-tauri/src/surfaces/ProviderDetailView.test.tsx @@ -139,7 +139,9 @@ describe("ProviderDetailView", () => { // lane that bills real money, and it now carries that money beside its // bar, so it belongs here. expect(screen.getByText("On-demand")).toBeInTheDocument(); - expect(screen.getByText("$3.50 of $10.00")).toBeInTheDocument(); + // Connector word comes from the locale bundle, which this test does not + // mount, so match the amounts rather than the joined sentence. + expect(screen.getByText(/\$3\.50.*\$10\.00/)).toBeInTheDocument(); expect(screen.getAllByText(/Weekly pace/)).toHaveLength(2); expect(screen.getAllByText(/Far ahead of budget/)).toHaveLength(2); expect(container.querySelector(".provider-focus__pace-fill")?.getAttribute("data-tone")) diff --git a/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx b/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx index 5432ff18..597d6904 100644 --- a/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx +++ b/apps/desktop-tauri/src/surfaces/ProviderDetailView.tsx @@ -91,6 +91,7 @@ function DetailProgress({ window: RateWindowSnapshot; showAsUsed: boolean; }) { + const { t } = useLocale(); // Where usage should be by this point in the window. Long windows only: // see `expectedUsedPercent`. const expected = expectedOverlay(window, showAsUsed); @@ -100,7 +101,10 @@ function DetailProgress({ aria-hidden title={ expected - ? `Expected ${Math.round(expected.expectedUsedPercent)}% used by now` + ? t("UsageExpectedByNow").replace( + "{}", + String(Math.round(expected.expectedUsedPercent)), + ) : undefined } > @@ -145,6 +149,7 @@ function SecondaryWindow({ metric.window.resetDescription, resetTimeRelative, ); + const { t } = useLocale(); const percent = Math.round(percentFor(metric.window, showAsUsed)); return (
@@ -158,7 +163,7 @@ function SecondaryWindow({ {metric.amount && (
{metric.amount.formattedLimit - ? `${metric.amount.formattedUsed} of ${metric.amount.formattedLimit}` + ? `${metric.amount.formattedUsed} ${t("PanelAmountOf")} ${metric.amount.formattedLimit}` : metric.amount.formattedUsed}
)}