Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4cba149
feat(usage): read cursor, opencode, and antigravity history
maria-rcks Sep 6, 2026
2574209
fix(usage): preserve bubble totals and merge antigravity aliases
maria-rcks Sep 6, 2026
6e632a4
fix(usage): read headless cursor account history
maria-rcks Sep 6, 2026
0d1357f
fix(usage): reconcile cursor pagination and remove overlapping fallback
maria-rcks Sep 6, 2026
d26a54e
Merge remote-tracking branch 'origin/main' into t3code/add-harness-us…
Yash-Singh1 Sep 25, 2026
aa20d44
fix(usage): keep OpenCode parser internal
Yash-Singh1 Sep 25, 2026
bb67896
fix(usage): honor active Cursor credential store
Yash-Singh1 Sep 25, 2026
9e1ed75
chore(usage): explain Node reader imports
Yash-Singh1 Sep 25, 2026
f9c6a28
fix(usage): use shared Crypto service for profile lookup
Yash-Singh1 Sep 25, 2026
393a8f3
fix(usage): handle paginated and aliased history
Yash-Singh1 Sep 25, 2026
62a1eac
fix(usage): keep large and partial histories accurate
Yash-Singh1 Sep 25, 2026
ced8da6
fix(usage): retain new cells from partial scans
Yash-Singh1 Sep 25, 2026
25426df
fix(usage): reconcile overlapping partial cells
Yash-Singh1 Sep 25, 2026
e06cad6
fix(usage): preserve complete overlapping cells
Yash-Singh1 Sep 25, 2026
d49bba0
feat(usage): add opt-in Cursor Keychain access
Yash-Singh1 Sep 25, 2026
d99c43b
Merge remote-tracking branch 'origin/main' into t3code/add-harness-us…
Yash-Singh1 Sep 25, 2026
73854af
fix(usage): keep Cursor Keychain reads responsive
Yash-Singh1 Sep 25, 2026
f91fa81
fix(usage): require HTTPS for Cursor Keychain limits
Yash-Singh1 Sep 25, 2026
b5e2544
fix(usage): keep Cursor Keychain token on its API origin
Yash-Singh1 Sep 25, 2026
40854e1
fix(usage): show Cursor enable action in provider list
Yash-Singh1 Sep 25, 2026
4e5a7bb
Merge remote-tracking branch 'origin/main' into t3code/add-harness-us…
Yash-Singh1 Sep 25, 2026
c06aa01
fix(usage): align Cursor opt-in with provider rows
Yash-Singh1 Sep 25, 2026
58123ef
Merge remote-tracking branch 'origin/main' into t3code/add-harness-us…
Yash-Singh1 Sep 25, 2026
0979931
fix(usage): mark new provider records as standard speed
Yash-Singh1 Sep 25, 2026
6135e1a
fix(usage): refresh Cursor limits after opt-in
Yash-Singh1 Sep 25, 2026
23d8b0e
fix(usage): match Antigravity step models by generation
Yash-Singh1 Sep 25, 2026
0bd2f00
fix(usage): ignore unused large Antigravity varints
Yash-Singh1 Sep 25, 2026
a60e3bc
fix(usage): explain Cursor monthly allowances
Yash-Singh1 Sep 25, 2026
fe90238
fix(usage): keep Cursor window ranking private
Yash-Singh1 Sep 25, 2026
f24f90f
fix(usage): focus Cursor limits on usable pools
Yash-Singh1 Sep 25, 2026
3f510e0
fix(usage): simplify single-account limit bars
Yash-Singh1 Sep 25, 2026
003f3c8
fix(usage): show tiny positive shares as less than 0.1 percent
Yash-Singh1 Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 51 additions & 22 deletions apps/mobile/src/features/usage/UsageLimitsPooled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import {
collectLimitAccounts,
collectLimitNotices,
collectLimitPools,
cursorUsageWindowDetails,
displayLimitWindows,
formatDuration,
formatResetsIn,
remainingPercent,
type LimitAccount,
type LimitPoolWindow,
} from "@t3tools/shared/usageLimits";
import { useId, useState } from "react";
import { Fragment, type ReactNode, useId, useState } from "react";
import { Pressable, ScrollView, View } from "react-native";
import { Defs, Path, Pattern, Rect, Svg } from "react-native-svg";
import { useSafeAreaInsets } from "react-native-safe-area-context";
Expand Down Expand Up @@ -70,11 +72,15 @@ function PoolWindowCard({
color,
now,
environmentIds,
label,
description,
}: {
readonly pool: LimitPoolWindow;
readonly color: string;
readonly now: number;
readonly environmentIds: readonly string[] | null;
readonly label?: string;
readonly description?: string;
}) {
const navigation = useNavigation();
const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0);
Expand All @@ -96,7 +102,7 @@ function PoolWindowCard({
<View className="gap-3 rounded-[24px] border-continuous bg-card p-4">
<View className="flex-row items-start justify-between gap-3">
<View className="gap-1">
<Text className="text-sm font-t3-medium text-foreground">{pool.label}</Text>
<Text className="text-sm font-t3-medium text-foreground">{label ?? pool.label}</Text>
<View className="flex-row items-baseline gap-1.5">
<Text className="text-3xl font-t3-bold tabular-nums text-foreground">
{pool.remainingPercent}%
Expand All @@ -108,6 +114,7 @@ function PoolWindowCard({
<Text className="text-xs text-foreground-tertiary">{PACE_LABEL[pool.pace]}</Text>
) : null}
</View>
{description ? <Text className="text-xs text-foreground-muted">{description}</Text> : null}
{nextRefill ? (
<Text className="text-xs tabular-nums text-foreground-muted">
↻ +{nextRefill.restoresPercent}%{" "}
Expand Down Expand Up @@ -196,10 +203,12 @@ export function UsageLimitsSection({
now,
failedLabels,
selectedEnvironmentIds,
cursorPrompt,
}: {
readonly now: number;
readonly failedLabels: readonly string[];
readonly selectedEnvironmentIds: ReadonlySet<EnvironmentId> | null;
readonly cursorPrompt?: ReactNode;
}) {
const presentations = useAtomValue(environmentPresentations.presentationsAtom);
const selected =
Expand All @@ -209,34 +218,54 @@ export function UsageLimitsSection({
const pools = collectLimitPools(collectLimitAccounts(selected), now);
const notices = collectLimitNotices(selected);
const colors = useProviderColors();
const cursorPromptAt =
Math.max(
pools.findIndex((pool) => pool.driver === "codex"),
pools.findIndex((pool) => pool.driver === "claudeAgent"),
) + 1;
return (
<View className="gap-6">
{pools.length === 0 && notices.length === 0 && failedLabels.length === 0 ? (
{pools.length === 0 && notices.length === 0 && failedLabels.length === 0 && !cursorPrompt ? (
<Text className="py-12 text-center text-base text-foreground-muted">
{selected.size === 0
? "Select an environment to see limits."
: "No provider on the selected environments reports subscription limits."}
</Text>
) : null}
{pools.map((pool) => (
<View key={pool.driver} className="gap-3">
<View className="flex-row items-center gap-2 px-1">
<ProviderIcon provider={pool.driver} size={18} />
<Text className="text-base font-t3-medium text-foreground">
{DRIVER_LABEL[pool.driver] ?? pool.driver}
</Text>
</View>
{pool.windows.map((window) => (
<PoolWindowCard
key={`${window.kind}:${window.id}`}
pool={window}
color={pool.driver === "claudeAgent" ? colors.claude : colors.codex}
now={now}
environmentIds={selectedEnvironmentIds === null ? null : [...selectedEnvironmentIds]}
/>
))}
</View>
))}
{pools.map((pool, index) => {
const windows = displayLimitWindows(pool);
return (
<Fragment key={pool.driver}>
{index === cursorPromptAt ? cursorPrompt : null}
<View className="gap-3">
<View className="flex-row items-center gap-2 px-1">
<ProviderIcon provider={pool.driver} size={18} />
<Text className="text-base font-t3-medium text-foreground">
{DRIVER_LABEL[pool.driver] ?? pool.driver}
</Text>
</View>
{windows.map((window) => {
const details =
pool.driver === "cursor" ? cursorUsageWindowDetails(window.id) : undefined;
return (
<PoolWindowCard
key={`${window.kind}:${window.id}`}
pool={window}
color={pool.driver === "claudeAgent" ? colors.claude : colors.codex}
now={now}
environmentIds={
selectedEnvironmentIds === null ? null : [...selectedEnvironmentIds]
}
label={details?.label}
description={details?.description}
/>
);
})}
</View>
</Fragment>
);
})}
{cursorPromptAt === pools.length ? cursorPrompt : null}
{notices.length > 0 || failedLabels.length > 0 ? (
<View
accessible
Expand Down
11 changes: 9 additions & 2 deletions apps/mobile/src/features/usage/UsageLimitsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export function useRefreshLimits(
const [failedEnvironments, setFailedEnvironments] = useState<
readonly { environmentId: EnvironmentId; label: string }[]
>([]);
const refresh = async (automatic = false) => {
const refresh = async (automatic = false, afterPending = false) => {
const connected = [...presentations].filter(
([environmentId, presentation]) =>
presentation.connection.phase === "connected" &&
Expand All @@ -307,6 +307,7 @@ export function useRefreshLimits(
environmentId,
() => refreshProviders({ environmentId, input: {} }),
automatic,
afterPending,
);
if (result === undefined) return;
setFailedEnvironments((previous) => [
Expand Down Expand Up @@ -354,5 +355,11 @@ export function useRefreshLimits(
selectedEnvironmentIds === null || selectedEnvironmentIds.has(environmentId),
)
.map(({ label }) => label);
return { now, refreshing, failedLabels, refresh: refreshManually };
return {
now,
refreshing,
failedLabels,
refresh: refreshManually,
refreshAfterEnable: () => refresh(false, true),
};
}
Loading
Loading