fix: restore session usage #6

Merged
StanPonomarev merged 1 commits from fix/restore-session-usage into main 2026-09-14 21:34:25 +02:00
3 changed files with 24 additions and 30 deletions
+11 -18
View File
@@ -3,42 +3,35 @@ import { createResource, createSignal, For, Show } from "solid-js";
import type { QuotaStatus, QuotaWindow } from "./quota.js";
import { QuotaRpc } from "./quota-rpc.js";
function duration(seconds: number) {
if (seconds % 86_400 === 0) return `${seconds / 86_400}d`;
if (seconds % 3_600 === 0) return `${seconds / 3_600}h`;
return `${Math.round(seconds / 60)}m`;
}
function percent(value: number) {
return value < 0.1 && value > 0 ? "<0.1" : value.toFixed(value < 10 ? 1 : 0);
}
function resetTiming(resetsAt: number, now: number) {
if (!Number.isFinite(resetsAt) || resetsAt <= 0 || !Number.isFinite(now))
return "reset unavailable";
if (!Number.isFinite(resetsAt) || resetsAt <= 0 || !Number.isFinite(now)) return "unavailable";
const remaining = resetsAt - now;
if (remaining <= 0) return "reset due";
if (remaining < 60_000) return "resets in <1m";
if (remaining <= 0) return "due";
if (remaining < 60_000) return "<1m";
const minutes = Math.floor(remaining / 60_000);
const days = Math.floor(minutes / 1_440);
if (days > 0) {
const hours = Math.floor((minutes % 1_440) / 60);
return `resets in ${days}d${hours > 0 ? ` ${hours}h` : ""}`;
return `${days}d${hours > 0 ? ` ${hours}h` : ""}`;
}
const hours = Math.floor(minutes / 60);
if (hours > 0) {
const rest = minutes % 60;
return `resets in ${hours}h${rest > 0 ? ` ${rest}m` : ""}`;
return `${hours}h${rest > 0 ? ` ${rest}m` : ""}`;
}
return `resets in ${minutes}m`;
return `${minutes}m`;
}
export function formatQuotaWindow(window: QuotaWindow & { remaining: number }, now = Date.now()) {
const label = window.label
? `${window.label} ${duration(window.seconds)}`
: duration(window.seconds);
return `${label}: ${percent(window.remaining)}% left · ${resetTiming(window.resetsAt, now)}`;
export function formatQuotaWindow(
window: QuotaWindow & { remaining: number; sessionConsumed: number },
now = Date.now(),
) {
return `${percent(window.remaining)}% · ~${percent(window.sessionConsumed)}pp · ${resetTiming(window.resetsAt, now)}`;
}
export async function loadQuotaStatus(load: () => Promise<QuotaStatus | null>) {
+12 -11
View File
@@ -424,11 +424,12 @@ test("sidebar text places reset timing beside latest remaining quota", () => {
label: null,
used: 25,
remaining: 75,
sessionConsumed: 1.5,
resetsAt: 100_000 + 2 * 86_400_000 + 5 * 3_600_000,
},
100_000,
),
).toBe("7d: 75% left · resets in 2d 5h");
).toBe("75% · ~1.5pp · 2d 5h");
});
test("sidebar reset timing uses compact future units and handles boundaries", () => {
@@ -441,31 +442,31 @@ test("sidebar reset timing uses compact future units and handles boundaries", ()
label: null,
used: 25,
remaining: 75,
sessionConsumed: 1.5,
resetsAt: now + resetIn,
},
now,
);
expect(format(2 * 3_600_000 + 15 * 60_000)).toBe("5h: 75% left · resets in 2h 15m");
expect(format(3_600_000)).toBe("5h: 75% left · resets in 1h");
expect(format(15 * 60_000)).toBe("5h: 75% left · resets in 15m");
expect(format(60_000)).toBe("5h: 75% left · resets in 1m");
expect(format(59_999)).toBe("5h: 75% left · resets in <1m");
expect(format(2 * 3_600_000 + 15 * 60_000)).toBe("75% · ~1.5pp · 2h 15m");
expect(format(3_600_000)).toBe("75% · ~1.5pp · 1h");
expect(format(15 * 60_000)).toBe("75% · ~1.5pp · 15m");
expect(format(60_000)).toBe("75% · ~1.5pp · 1m");
expect(format(59_999)).toBe("75% · ~1.5pp · <1m");
});
test("sidebar reset timing distinguishes stale and unavailable timestamps", () => {
test("sidebar reset timing omits labels and distinguishes stale and unavailable timestamps", () => {
const window = {
seconds: 18_000,
id: "codex:primary",
label: "GPT-5.6 Luna",
used: 99.95,
remaining: 0.05,
sessionConsumed: 1.5,
};
expect(formatQuotaWindow({ ...window, resetsAt: 999 }, 1_000)).toBe(
"GPT-5.6 Luna 5h: <0.1% left · reset due",
);
expect(formatQuotaWindow({ ...window, resetsAt: 999 }, 1_000)).toBe("<0.1% · ~1.5pp · due");
expect(formatQuotaWindow({ ...window, resetsAt: Number.NaN }, 1_000)).toBe(
"GPT-5.6 Luna 5h: <0.1% left · reset unavailable",
"<0.1% · ~1.5pp · unavailable",
);
});
+1 -1
View File
@@ -84,7 +84,7 @@ test("built quota slot stays reactive across pending, empty, failure, update, an
pending.resolve(status("plus", 75));
await setup.waitForFrame((frame) => frame.includes("ChatGPT quota (plus)"));
expect(setup.captureCharFrame()).toContain("5h: 75% left · reset due");
expect(setup.captureCharFrame()).toContain("75% · ~1.5pp · due");
responses.push(async () => null);
notifyUpdated();