Skip to content

Commit 5007f99

Browse files
committed
feat(tooltip): preserve fractional precision in used / overage values
Mirror upstream chatStatusDashboard's quotaCreditsFormatter so a float like 195.9 stays 195.9 instead of being rounded to 196. The fallback percent-based used calc no longer pre-rounds; the tooltip formats with Intl.NumberFormat({ maximumFractionDigits: 2 }) at render time.
1 parent 7c4bd46 commit 5007f99

5 files changed

Lines changed: 45 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1111
- **Token-based billing (UBB) support**: auto-detects GitHub's new credit-based billing mode via the `token_based_billing` field. The tooltip title switches between **Copilot Premium Requests** (legacy) and **Copilot Credits** (UBB), so the count line stays a familiar `Used: 90 / 300 (30%)`. Overage line still labels its unit (`Overage: 5 requests` vs `Additional credits: 5`).
1212
- **`individual_max` (Max) and `individual_edu` (Student) plans** in the plan-name map.
1313
- **Per-snapshot `quota_reset_at`** (Unix seconds) read with top priority under UBB; falls back to `quota_reset_date_utc` then `quota_reset_date`. Shared by all return paths so pooled-exhausted users still see real reset times.
14-
- **Precise `used` calculation** via `quota_remaining` when present (avoids float precision loss from percentage-based reverse calculation).
14+
- **Precise `used` calculation** via `quota_remaining` when present (avoids float precision loss from percentage-based reverse calculation). Tooltip formats `used`, `quota`, and overage values with `Intl.NumberFormat({ maximumFractionDigits: 2 })` to preserve fractional precision (e.g. `195.9` instead of `196`), matching upstream's `quotaCreditsFormatter`.
1515
- **Pooled entitlement exhaustion**: enterprise unlimited plans signaling `has_quota=false` (without overage) now display `100%` red instead of misleadingly showing ``. Tooltip surfaces "Quota: Unlimited · pool exhausted" with the reset date.
1616

1717
### Changed

src/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async function fetchUsage(token) {
140140
quotaRemaining !== undefined
141141
? Math.max(0, entitlement - quotaRemaining)
142142
: entitlement > 0
143-
? Math.max(0, Math.round((entitlement * (100 - percentRemaining)) / 100))
143+
? Math.max(0, (entitlement * (100 - percentRemaining)) / 100)
144144
: 0;
145145

146146
return { ...shared, used, usedPct, noData: false };

src/extension.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,16 @@ function computeDisplayPct(data) {
180180

181181
const BILLING_URL = "https://github.com/settings/billing/premium_requests_usage";
182182

183+
// Mirrors vscode's quotaCreditsFormatter (chatStatusDashboard.ts): keep up to 2 fractional
184+
// digits so a float like 195.9 stays 195.9 instead of being rounded to 196 here.
185+
// Upstream also has a compact formatter for >= 100k; we skip that — premium quotas don't
186+
// reach that magnitude in practice, and the credit dashboard handles compact rendering
187+
// in its own surface.
188+
const usageFormatter = new Intl.NumberFormat(undefined, {
189+
maximumFractionDigits: 2,
190+
minimumFractionDigits: 0,
191+
});
192+
183193
/**
184194
* @param {import('./api').UsageData} data
185195
* @param {boolean} [isRateLimited]
@@ -295,12 +305,12 @@ function buildTooltip(data, isRateLimited, isOfflineState = false, isStale = fal
295305
// Title already names the unit (Credits vs Premium requests), so the
296306
// count line uses a neutral "Used:" label and skips the unit suffix.
297307
md.appendMarkdown(
298-
`Used: ${data.used} / ${data.quota} (${data.usedPct}%)  [$(graph)](${BILLING_URL})\n\n`,
308+
`Used: ${usageFormatter.format(data.used)} / ${usageFormatter.format(data.quota)} (${data.usedPct}%)  [$(graph)](${BILLING_URL})\n\n`,
299309
);
300310
if (data.overageEnabled && data.overageUsed > 0) {
301311
const overageLine = data.tokenBasedBilling
302-
? `Additional credits: ${data.overageUsed}`
303-
: `Overage: ${data.overageUsed} requests`;
312+
? `Additional credits: ${usageFormatter.format(data.overageUsed)}`
313+
: `Overage: ${usageFormatter.format(data.overageUsed)} requests`;
304314
md.appendMarkdown(`${overageLine}\n\n`);
305315
}
306316
appendResetLine(md, data.resetDate);

tests/api.test.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe("fetchUsage", () => {
4040
const data = await fetchUsage("test-token");
4141
expect(data.plan).toBe("Pro");
4242
expect(data.usedPct).toBe(30); // 100 - 70
43-
expect(data.used).toBe(90); // round(300 * 30 / 100)
43+
expect(data.used).toBe(90); // 300 * 30 / 100
4444
expect(data.quota).toBe(300);
4545
expect(data.unlimited).toBe(false);
4646
expect(data.noData).toBe(false);
@@ -239,9 +239,29 @@ describe("fetchUsage", () => {
239239
};
240240
fetch.mockResolvedValue(mockRes(200, body));
241241
const data = await fetchUsage("test-token");
242-
// entitlement=300, percent_remaining=70 → used = round(300 * 30 / 100) = 90
242+
// entitlement=300, percent_remaining=70 → used = 300 * 30 / 100 = 90
243243
expect(data.used).toBe(90);
244244
});
245+
246+
it("preserves fractional precision in `used` (matches vscode dashboard)", async () => {
247+
// Upstream chatStatusDashboard stores `used` as a float and formats with
248+
// Intl.NumberFormat({ maximumFractionDigits: 2 }) at render time. We mirror
249+
// that by not pre-rounding here. Concrete case: entitlement=300, percent_remaining=34.7
250+
// → used = 300 * 65.3 / 100 = 195.9 (vscode shows 195.9, not 196).
251+
const body = {
252+
...BASE_BODY,
253+
quota_snapshots: {
254+
premium_interactions: {
255+
...BASE_BODY.quota_snapshots.premium_interactions,
256+
percent_remaining: 34.7,
257+
quota_remaining: undefined,
258+
},
259+
},
260+
};
261+
fetch.mockResolvedValue(mockRes(200, body));
262+
const data = await fetchUsage("test-token");
263+
expect(data.used).toBeCloseTo(195.9, 5);
264+
});
245265
});
246266

247267
describe("plan mapping", () => {

tests/extension.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ describe("buildTooltip", () => {
124124
expect(md.value).toContain("Used: 90 / 300 (30%)");
125125
});
126126

127+
it("formats fractional `used` with up to 2 decimal places (matches vscode)", () => {
128+
// Mirrors upstream Intl.NumberFormat({ maximumFractionDigits: 2 }) behavior:
129+
// a float like 195.9 is preserved instead of being rounded to 196.
130+
const data = { ...BASE_DATA, used: 195.9, usedPct: 65.3 };
131+
const md = buildTooltip(data, false);
132+
expect(md.value).toContain("Used: 195.9 / 300 (65.3%)");
133+
});
134+
127135
it("uses 'Copilot Premium Requests' title in legacy mode", () => {
128136
const md = buildTooltip(BASE_DATA, false);
129137
expect(md.value).toContain("**Copilot Premium Requests**");

0 commit comments

Comments
 (0)