| author | |
| committer | |
| log | 2625327090115a630d97ff61257758c577589de7 |
| tree | 5f7d1822dc44127644ae6abf88f98f309089cf6c |
| parent | ed0f7b908b2e7b3a22e6fd82a73794c2fe6ee06e |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 20 insertions(+), 3 deletions(-)
apps/mobile/src/features/usage/UsageLimitsPooled.tsx+3-2| ... | @@ -8,7 +8,6 @@ import { | ... | @@ -8,7 +8,6 @@ import { |
| 8 | collectLimitPools, | 8 | collectLimitPools, |
| 9 | cursorUsageWindowDetails, | 9 | cursorUsageWindowDetails, |
| 10 | displayLimitWindows, | 10 | displayLimitWindows, |
| 11 | formatDuration, | ||
| 12 | formatResetsIn, | 11 | formatResetsIn, |
| 13 | remainingPercent, | 12 | remainingPercent, |
| 14 | type LimitAccount, | 13 | type LimitAccount, |
| ... | @@ -123,7 +122,9 @@ function PoolWindowCard({ | ... | @@ -123,7 +122,9 @@ function PoolWindowCard({ |
| 123 | {nextRefill ? ( | 122 | {nextRefill ? ( |
| 124 | <Text className="text-xs tabular-nums text-foreground-muted"> | 123 | <Text className="text-xs tabular-nums text-foreground-muted"> |
| 125 | ↻ +{nextRefill.restoresPercent}%{" "} | 124 | ↻ +{nextRefill.restoresPercent}%{" "} |
| 126 | {nextRefill.at <= now ? "now" : `in ${formatDuration(nextRefill.at - now)}`} | 125 | {formatResetsIn(nextRefill.member.window, now) |
| 126 | ?.replace("resets in ", "in ") | ||
| 127 | .replace("resets now", "now")} | ||
| 127 | </Text> | 128 | </Text> |
| 128 | ) : null} | 129 | ) : null} |
| 129 | <View className="flex-row gap-1"> | 130 | <View className="flex-row gap-1"> |
packages/shared/src/usageLimits.test.ts+14| ... | @@ -56,6 +56,20 @@ function provider(overrides: Partial<ServerProvider>): ServerProvider { | ... | @@ -56,6 +56,20 @@ function provider(overrides: Partial<ServerProvider>): ServerProvider { |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | describe("pace", () => { | 58 | describe("pace", () => { |
| 59 | it.each([ | ||
| 60 | ["2026-09-03T12:01:00.000Z", "resets in 1m"], | ||
| 61 | ["2026-09-03T12:00:59.999Z", "Resets momentarily"], | ||
| 62 | ["2026-09-03T12:00:00.001Z", "Resets momentarily"], | ||
| 63 | ["2026-09-03T12:00:00.000Z", "resets now"], | ||
| 64 | ["2026-09-03T11:59:59.999Z", "resets now"], | ||
| 65 | ])("formats the reset boundary at %s", (resetsAt, label) => { | ||
| 66 | expect(formatResetsIn({ ...window, resetsAt }, now)).toBe(label); | ||
| 67 | }); | ||
| 68 | |||
| 69 | it("ignores invalid reset timestamps", () => { | ||
| 70 | expect(formatResetsIn({ ...window, resetsAt: "invalid" }, now)).toBeNull(); | ||
| 71 | }); | ||
| 72 | |||
| 59 | it("places the clock three fifths through a five-hour window with two hours left", () => { | 73 | it("places the clock three fifths through a five-hour window with two hours left", () => { |
| 60 | expect(elapsedShare(window, now)).toBeCloseTo(0.6); | 74 | expect(elapsedShare(window, now)).toBeCloseTo(0.6); |
| 61 | expect(paceOf(window, now)).toBe("under"); | 75 | expect(paceOf(window, now)).toBe("under"); |
packages/shared/src/usageLimits.ts+3-1| ... | @@ -607,7 +607,9 @@ export function formatDuration(ms: number): string { | ... | @@ -607,7 +607,9 @@ export function formatDuration(ms: number): string { |
| 607 | export function formatResetsIn(window: ServerProviderUsageWindow, now: number): string | null { | 607 | export function formatResetsIn(window: ServerProviderUsageWindow, now: number): string | null { |
| 608 | const resetsAt = resetMillis(window); | 608 | const resetsAt = resetMillis(window); |
| 609 | if (resetsAt === null) return null; | 609 | if (resetsAt === null) return null; |
| 610 | return resetsAt <= now ? "resets now" : `resets in ${formatDuration(resetsAt - now)}`; | 610 | if (resetsAt <= now) return "resets now"; |
| 611 | if (resetsAt - now < MINUTE) return "Resets momentarily"; | ||
| 612 | return `resets in ${formatDuration(resetsAt - now)}`; | ||
| 611 | } | 613 | } |
| 612 | 614 | ||
| 613 | /** Limit commands are served by T3 from the same snapshots as Usage → Limits. */ | 615 | /** Limit commands are served by T3 from the same snapshots as Usage → Limits. */ |