Skip to content

Commit 69f4b6c

Browse files
authored
Format billion-scale usage totals
Display compact usage with B and T units and cover magnitude boundaries in tests and UI captures.
2 parents 52a0fae + 7f627c4 commit 69f4b6c

10 files changed

Lines changed: 104 additions & 18 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ body:
1616
id: version
1717
attributes:
1818
label: ReRouted version
19-
placeholder: "For example: 0.4.2"
19+
placeholder: "Shown in Settings under Application"
2020
validations:
2121
required: true
2222
- type: input

.github/ISSUE_TEMPLATE/question.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ body:
1414
id: version
1515
attributes:
1616
label: ReRouted version
17-
placeholder: "For example: 0.4.2"
17+
placeholder: "Shown in Settings under Application"
1818
validations:
1919
required: true
2020
- type: input

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ ReRouted is an independent project and is not affiliated with or endorsed by any
9999

100100
### 1. Install
101101

102-
[Download ReRouted 0.4.2 for Apple Silicon](https://github.com/gitcommit90/rerouted/releases/download/v0.4.2/ReRouted-0.4.2-arm64.dmg), open the DMG, and drag ReRouted to Applications.
102+
[Download ReRouted 0.4.3 for Apple Silicon](https://github.com/gitcommit90/rerouted/releases/download/v0.4.3/ReRouted-0.4.3-arm64.dmg), open the DMG, and drag ReRouted to Applications.
103103

104104
ReRouted requires Apple Silicon and macOS 12 Monterey or newer.
105105

@@ -200,7 +200,7 @@ The implementation is intentionally small: Electron, Node's built-in HTTP server
200200

201201
## Current release
202202

203-
ReRouted `0.4.2` ships for Apple Silicon macOS with hardened OAuth callbacks and renderer lock boundaries, uncapped SQLite usage history, 60-second quota refreshes while the Quota page is open, masked account identities, and image inputs in chat completions. Public builds are Developer ID signed, notarized, stapled, and distributed through stable GitHub Releases with in-app updates. The public API is intentionally limited to health, model discovery, and chat completions; a published third-party client compatibility matrix is still forthcoming.
203+
ReRouted `0.4.3` fixes compact usage metrics so billion and trillion totals display with `B` and `T` units instead of continuing to grow in millions. It includes the hardened OAuth callbacks and renderer lock boundaries, uncapped SQLite usage history, 60-second quota refreshes while the Quota page is open, masked account identities, and image inputs introduced in `0.4.2`. Public builds are Developer ID signed, notarized, stapled, and distributed through stable GitHub Releases with in-app updates. The public API is intentionally limited to health, model discovery, and chat completions; a published third-party client compatibility matrix is still forthcoming.
204204

205205
ReRouted is an independent personal project.
206206

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rerouted",
3-
"version": "0.4.2",
3+
"version": "0.4.3",
44
"description": "A macOS menu-bar router for accounts, models, and automatic fallback.",
55
"author": "gitcommit90",
66
"homepage": "https://rerouted.dev",

scripts/capture-ui.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ function demoUsage(period = "24h") {
6363
requests: 148,
6464
ok: 143,
6565
errors: 5,
66-
prompt_tokens: 184_200,
67-
completion_tokens: 61_480,
68-
cached_tokens: 92_600,
69-
total_tokens: 338_280,
66+
prompt_tokens: 1_486_000_000,
67+
completion_tokens: 661_000_000,
68+
cached_tokens: 483_648,
69+
total_tokens: 2_147_483_648,
7070
byModel: [
7171
{ model: "coding", requests: 92, prompt_tokens: 121_000, completion_tokens: 42_300 },
7272
{ model: "chatgpt/gpt-5", requests: 38, prompt_tokens: 45_800, completion_tokens: 13_100 },
@@ -543,6 +543,26 @@ app.whenReady().then(async () => {
543543
settings: ".settings-group",
544544
}[p] || "#view > *";
545545
await capture(`app-${p}.png`, selector);
546+
if (p === "home") {
547+
await win.webContents.executeJavaScript(`
548+
(() => {
549+
const value = document.querySelector("[data-home-tokens]")?.textContent.trim();
550+
if (value !== "2.1B") throw new Error("Unexpected billion token format: " + value);
551+
return true;
552+
})()
553+
`);
554+
}
555+
if (p === "stats") {
556+
await win.webContents.executeJavaScript(`
557+
(() => {
558+
const metrics = [...document.querySelectorAll(".metric")];
559+
const tokens = metrics.find((metric) => metric.querySelector(".metric-label")?.textContent.trim() === "Tokens");
560+
const value = tokens?.querySelector(".metric-value")?.textContent.trim();
561+
if (value !== "2.1B") throw new Error("Unexpected activity token format: " + value);
562+
return true;
563+
})()
564+
`);
565+
}
546566
if (p === "providers") {
547567
await win.webContents.executeJavaScript(`
548568
(() => {

src/renderer/app.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const api = window.rerouted;
44
const { accountDisplayName, accountIdentityLabel, maskAccountEmail } =
55
window.ReroutedAccountIdentity;
6+
const { compactNumber: fmtNum } = window.ReroutedNumberFormat;
67
const { createLatestRequestGate, guardSensitiveRender } = window.ReroutedRendererLockState;
78
const $ = (sel, el = document) => el.querySelector(sel);
89
const view = $("#view");
@@ -800,13 +801,6 @@ function fmtTime(at) {
800801
}
801802
}
802803

803-
function fmtNum(n) {
804-
const x = Number(n) || 0;
805-
if (x >= 1_000_000) return (x / 1_000_000).toFixed(1).replace(/\.0$/, "") + "M";
806-
if (x >= 10_000) return (x / 1000).toFixed(1).replace(/\.0$/, "") + "k";
807-
return x.toLocaleString();
808-
}
809-
810804
function fmtRelativeTime(at) {
811805
if (!at) return "Never";
812806
const seconds = Math.max(0, Math.round((Date.now() - Number(at)) / 1000));

src/renderer/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
</nav>
6060
</div>
6161
<script src="account-identity.js"></script>
62+
<script src="number-format.js"></script>
6263
<script src="lock-state.js"></script>
6364
<script src="app.js"></script>
6465
</body>

src/renderer/number-format.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use strict";
2+
3+
(function exposeNumberFormat(root) {
4+
const UNITS = [
5+
{ value: 1_000, suffix: "k" },
6+
{ value: 1_000_000, suffix: "M" },
7+
{ value: 1_000_000_000, suffix: "B" },
8+
{ value: 1_000_000_000_000, suffix: "T" },
9+
];
10+
11+
function compactNumber(value) {
12+
const parsed = Number(value);
13+
const number = Number.isFinite(parsed) ? parsed : 0;
14+
const absolute = Math.abs(number);
15+
if (absolute < 10_000) return number.toLocaleString("en-US");
16+
17+
let unitIndex = 0;
18+
while (unitIndex < UNITS.length - 1 && absolute >= UNITS[unitIndex + 1].value) {
19+
unitIndex += 1;
20+
}
21+
22+
let unit = UNITS[unitIndex];
23+
let rounded = Number((number / unit.value).toFixed(1));
24+
if (Math.abs(rounded) >= 1_000 && unitIndex < UNITS.length - 1) {
25+
unit = UNITS[++unitIndex];
26+
rounded = Number((number / unit.value).toFixed(1));
27+
}
28+
return `${rounded.toFixed(1).replace(/\.0$/, "")}${unit.suffix}`;
29+
}
30+
31+
const api = { compactNumber };
32+
if (typeof module !== "undefined" && module.exports) module.exports = api;
33+
if (root) root.ReroutedNumberFormat = api;
34+
})(typeof window !== "undefined" ? window : null);

tests/number-format.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use strict";
2+
3+
const assert = require("node:assert/strict");
4+
const { describe, it } = require("node:test");
5+
const { compactNumber } = require("../src/renderer/number-format");
6+
7+
describe("compact usage number formatting", () => {
8+
it("uses the expected suffix at each magnitude", () => {
9+
assert.equal(compactNumber(0), "0");
10+
assert.equal(compactNumber(9_999), "9,999");
11+
assert.equal(compactNumber(10_000), "10k");
12+
assert.equal(compactNumber(12_345), "12.3k");
13+
assert.equal(compactNumber(1_000_000), "1M");
14+
assert.equal(compactNumber(12_345_678), "12.3M");
15+
assert.equal(compactNumber(1_000_000_000), "1B");
16+
assert.equal(compactNumber(12_345_678_901), "12.3B");
17+
assert.equal(compactNumber(1_000_000_000_000), "1T");
18+
assert.equal(compactNumber(12_345_678_901_234), "12.3T");
19+
});
20+
21+
it("promotes values that round into the next unit", () => {
22+
assert.equal(compactNumber(999_949), "999.9k");
23+
assert.equal(compactNumber(999_950), "1M");
24+
assert.equal(compactNumber(999_949_999), "999.9M");
25+
assert.equal(compactNumber(999_950_000), "1B");
26+
assert.equal(compactNumber(999_949_999_999), "999.9B");
27+
assert.equal(compactNumber(999_950_000_000), "1T");
28+
});
29+
30+
it("handles exact, negative, and invalid values", () => {
31+
assert.equal(compactNumber(2_000_000_000), "2B");
32+
assert.equal(compactNumber(-2_500_000_000), "-2.5B");
33+
assert.equal(compactNumber("1234567890"), "1.2B");
34+
assert.equal(compactNumber(Number.NaN), "0");
35+
assert.equal(compactNumber(Number.POSITIVE_INFINITY), "0");
36+
});
37+
});

0 commit comments

Comments
 (0)