Repository navigation
feat(usage): show reserve and deficit pace on Limits #11095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
7eb49ab
548dce3
8b8ba6a
fb13de3
faf72c4
420c3da
5e4c6e6
2df926f
4f6eac3
fbf8764
214799d
27d375c
d41d87d
7aa257e
a947549
59a00fe
bfb4a87
dec6095
556653d
a9b22b5
cf8b578
c35da71
440b932
3ac110d
65c7be4
50fbc74
cf63c8f
98aec80
58584be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,11 @@ import { | |
| collectLimitAccounts, | ||
| collectLimitNotices, | ||
| collectLimitPools, | ||
| evenPaceRemainingPercent, | ||
| formatAllowancePace, | ||
| formatDuration, | ||
| formatResetsIn, | ||
| paceDetail, | ||
| remainingPercent, | ||
| type LimitAccount, | ||
| type LimitPoolWindow, | ||
|
|
@@ -26,7 +29,6 @@ import { ResetCredits } from "./UsageLimitsSection"; | |
| import { useProviderColors } from "./usageProviders"; | ||
|
|
||
| const DRIVER_LABEL: Partial<Record<string, string>> = { codex: "Codex", claudeAgent: "Claude" }; | ||
| const PACE_LABEL = { ahead: "Ahead of pace", on: "On pace", under: "Under pace" } as const; | ||
|
|
||
| function accountName(account: LimitAccount) { | ||
| if (account.displayName) return account.displayName; | ||
|
|
@@ -79,6 +81,7 @@ function PoolWindowCard({ | |
| }) { | ||
| const navigation = useNavigation(); | ||
| const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0); | ||
| const paceReadout = pool.paceDetail ? formatAllowancePace(pool.paceDetail) : null; | ||
| const openAccount = (account: LimitAccount) => | ||
| navigation.navigate("SettingsSheet", { | ||
| screen: "SettingsContent", | ||
|
|
@@ -105,8 +108,17 @@ function PoolWindowCard({ | |
| <Text className="text-sm text-foreground-muted">left</Text> | ||
| </View> | ||
| </View> | ||
| {pool.pace ? ( | ||
| <Text className="text-xs text-foreground-tertiary">{PACE_LABEL[pool.pace]}</Text> | ||
| {paceReadout ? ( | ||
| <Text | ||
| className={ | ||
| pool.paceDetail?.status === "deficit" | ||
| ? "max-w-[11rem] text-right text-xs text-red-500" | ||
| : "max-w-[11rem] text-right text-xs text-emerald-500" | ||
| } | ||
| accessibilityLabel={paceReadout.explanation} | ||
| > | ||
| {paceReadout.percent} | ||
| </Text> | ||
| ) : null} | ||
| </View> | ||
| {nextRefill ? ( | ||
|
|
@@ -118,20 +130,39 @@ function PoolWindowCard({ | |
| <View className="flex-row gap-1"> | ||
| {pool.columns.map(({ account, window }, index) => { | ||
| if (!window) return <View key={account.key} className="h-7 min-w-0 flex-1" />; | ||
| const rowPace = paceDetail(window, now); | ||
| return ( | ||
| <Pressable | ||
| key={account.key} | ||
| accessibilityRole="button" | ||
| accessibilityLabel={`Segment ${index + 1}, ${accountName(account)}, ${remainingPercent(window)}% left`} | ||
| accessibilityLabel={`Segment ${index + 1}, ${accountName(account)}, ${remainingPercent(window)}% left${rowPace ? `, ${formatAllowancePace(rowPace).marker}` : ""}`} | ||
| accessibilityHint="Show account details" | ||
| onPress={() => openAccount(account)} | ||
| className="h-7 min-w-0 flex-1 overflow-hidden rounded-md bg-subtle" | ||
| className="h-7 min-w-0 flex-1 overflow-visible rounded-md bg-transparent" | ||
| > | ||
| <AccountSegment | ||
| remaining={remainingPercent(window)} | ||
| color={color} | ||
| pending={Boolean(window.resetsAt)} | ||
| /> | ||
| <View className="absolute inset-0 overflow-hidden rounded-md bg-subtle"> | ||
| <AccountSegment | ||
| remaining={remainingPercent(window)} | ||
| color={color} | ||
| pending={Boolean(window.resetsAt)} | ||
| /> | ||
| </View> | ||
| {rowPace ? ( | ||
| <View | ||
| pointerEvents="none" | ||
| className={ | ||
| rowPace.status === "deficit" | ||
| ? "absolute z-10 w-0.5 rounded-full bg-red-500" | ||
| : "absolute z-10 w-0.5 rounded-full bg-emerald-500" | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
| style={{ | ||
| left: `${evenPaceRemainingPercent(rowPace)}%`, | ||
| marginLeft: -1, | ||
| top: "-5%", | ||
| height: "110%", | ||
| }} | ||
| /> | ||
| ) : null} | ||
| <View pointerEvents="none" className="absolute inset-0 items-center justify-center"> | ||
| <Text className="text-xs font-t3-medium tabular-nums text-foreground"> | ||
| {index + 1} | ||
|
|
@@ -177,7 +208,9 @@ function PoolWindowCard({ | |
| ) : null} | ||
| {credits ? ( | ||
| <> | ||
| {resetsIn ? <Text className="text-xs text-foreground-tertiary">·</Text> : null} | ||
| {resetsIn ? ( | ||
| <Text className="text-xs text-foreground-tertiary">·</Text> | ||
| ) : null} | ||
| <SymbolView name="ticket" size={13} tintColorClassName="accent-icon" /> | ||
| <Text className="text-xs font-t3-medium tabular-nums text-foreground"> | ||
| {credits} | ||
|
|
@@ -279,6 +312,9 @@ export function UsageLimitAccountScreen({ route }: AccountScreenProps) { | |
| ?.windows.find((candidate) => candidate.id === windowId && candidate.kind === windowKind); | ||
| const window = pool?.members.find((member) => member.account.key === accountKey)?.window; | ||
| const reset = pool?.resets.find((candidate) => candidate.member.account.key === accountKey); | ||
| const accountPaceDetail = window ? paceDetail(window, now) : null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium The displayed 🤖 Copy this AI Prompt to have your agent fix this:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reset timer doesn't live update so this would either need that to live update too, or the pace vs reset timer would be desynced
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'm unable to act on this request because you do not have permissions within this repository. |
||
| const accountPace = | ||
| account && window && accountPaceDetail ? formatAllowancePace(accountPaceDetail) : null; | ||
| const [revealed, setRevealed] = useState(false); | ||
| return ( | ||
| <View collapsable={false} className="flex-1 bg-sheet"> | ||
|
|
@@ -329,6 +365,14 @@ export function UsageLimitAccountScreen({ route }: AccountScreenProps) { | |
| <Text className="text-3xl font-t3-bold tabular-nums text-foreground"> | ||
| {remainingPercent(window)}% left | ||
| </Text> | ||
| {accountPace ? ( | ||
| <Text | ||
| className="text-sm text-foreground-muted" | ||
| accessibilityLabel={accountPace.explanation} | ||
| > | ||
| {accountPace.marker} | ||
| </Text> | ||
| ) : null} | ||
| {window.resetsAt ? ( | ||
| <Text selectable className="text-sm text-foreground-muted"> | ||
| Resets{" "} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,11 +9,12 @@ import type { | |
| UsageProviderKind, | ||
| } from "@t3tools/contracts"; | ||
| import { | ||
| elapsedShare, | ||
| evenPaceRemainingPercent, | ||
| formatAllowancePace, | ||
| formatDuration, | ||
| formatResetsIn, | ||
| limitsNotice, | ||
| paceOf, | ||
| paceDetail, | ||
| remainingPercent, | ||
| } from "@t3tools/shared/usageLimits"; | ||
| import { type ReactNode, useState } from "react"; | ||
|
|
@@ -26,8 +27,6 @@ import { serverEnvironment } from "../../state/server"; | |
| import { useAtomCommand } from "../../state/use-atom-command"; | ||
| import { useProviderColors } from "./usageProviders"; | ||
|
|
||
| const PACE_LABEL = { ahead: "ahead of pace", on: "on pace", under: "under pace" } as const; | ||
|
|
||
| type Driver = ServerProvider["driver"]; | ||
|
|
||
| /** The series colour the usage chart uses for this driver, so the two views read as one. */ | ||
|
|
@@ -40,9 +39,8 @@ function useBarColor(driver: Driver): string | null { | |
|
|
||
| /** | ||
| * One window as a bar spanning its whole duration: the fill is quota left, | ||
| * the hairline is how much of the window is left, so even spending keeps the | ||
| * fill on the line. Pace sits under the left edge, the countdown under the | ||
| * right, so a row reads in one glance. | ||
| * a colored pill sits at even pace when reserve or deficit is large enough. | ||
| * The pace chip rides the header row; the countdown sits under the bar. | ||
| */ | ||
| function WindowRow(props: { | ||
| readonly window: ServerProviderUsageWindow; | ||
|
|
@@ -51,19 +49,28 @@ function WindowRow(props: { | |
| }) { | ||
| const { window, now } = props; | ||
| const remaining = remainingPercent(window); | ||
| const elapsed = elapsedShare(window, now); | ||
| const timeLeft = elapsed === null ? null : Math.round((1 - elapsed) * 100); | ||
| const pace = paceOf(window, now); | ||
| const detail = paceDetail(window, now); | ||
| const pace = detail ? formatAllowancePace(detail) : null; | ||
| const resetsIn = formatResetsIn(window, now); | ||
| return ( | ||
| <View className="gap-1"> | ||
| <View className="flex-row items-baseline justify-between gap-3"> | ||
| <Text className="text-sm text-foreground">{window.label}</Text> | ||
| <Text className="text-sm font-t3-medium tabular-nums text-foreground"> | ||
| {remaining}% left | ||
| </Text> | ||
| <View className="flex-row items-center gap-1.5"> | ||
| <Text className="text-sm font-t3-medium tabular-nums text-foreground"> | ||
| {remaining}% left | ||
| </Text> | ||
| {pace ? ( | ||
| <Text | ||
| className="text-xs tabular-nums text-foreground-tertiary" | ||
| accessibilityLabel={pace.explanation} | ||
| > | ||
| {pace.percent} | ||
| </Text> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Whenever pace is available, this renders only AGENTS.md reference: AGENTS.md:L65-L70 Useful? React with 👍 / 👎. |
||
| ) : null} | ||
| </View> | ||
| </View> | ||
| <View className="h-3 justify-center"> | ||
| <View className="h-3.5 justify-center"> | ||
| <View className="h-1.5 flex-row overflow-hidden rounded-full bg-subtle"> | ||
| <View | ||
| className={ | ||
|
|
@@ -80,18 +87,19 @@ function WindowRow(props: { | |
| /> | ||
| <View style={{ flex: 100 - remaining }} /> | ||
| </View> | ||
| {timeLeft !== null ? ( | ||
| {detail ? ( | ||
| <View | ||
| className="absolute top-0 bottom-0 w-px bg-foreground" | ||
| style={{ left: `${timeLeft}%`, opacity: 0.6 }} | ||
| className={ | ||
| detail.status === "deficit" | ||
| ? "absolute top-0 h-3.5 w-0.5 rounded-full bg-red-500" | ||
| : "absolute top-0 h-3.5 w-0.5 rounded-full bg-emerald-500" | ||
| } | ||
| style={{ left: `${evenPaceRemainingPercent(detail)}%`, marginLeft: -1 }} | ||
| /> | ||
| ) : null} | ||
| </View> | ||
| {pace || resetsIn ? ( | ||
| <View className="flex-row justify-between gap-3"> | ||
| <Text className="text-xs text-foreground-tertiary">{pace ? PACE_LABEL[pace] : ""}</Text> | ||
| <Text className="text-xs tabular-nums text-foreground-tertiary">{resetsIn ?? ""}</Text> | ||
| </View> | ||
| {resetsIn ? ( | ||
| <Text className="text-xs tabular-nums text-foreground-tertiary">{resetsIn}</Text> | ||
| ) : null} | ||
| </View> | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
usage/UsageLimitsPooled.tsx:116The header displays the same bare value such as
20%for both reserve and deficit, so single-account cards do not communicate whether the pace is ahead or behind. The status distinction is currently available only throughaccessibilityLabeland the separate tick; includepaceReadout.markerin the visible readout.🤖 Copy this AI Prompt to have your agent fix this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is invalid, it would render as
24% 24% in reserve.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.