diff --git a/src/components/settings/ai/__tests__/ai-usage-card.test.tsx b/src/components/settings/ai/__tests__/ai-usage-card.test.tsx
index 99cf881fd..bddf99e48 100644
--- a/src/components/settings/ai/__tests__/ai-usage-card.test.tsx
+++ b/src/components/settings/ai/__tests__/ai-usage-card.test.tsx
@@ -100,6 +100,39 @@ describe("AiUsageCard", () => {
expect(html).not.toContain('data-slot="ai-usage-use-default"');
});
+ it("follows the server's estimates, not a day threshold of its own", () => {
+ // Two measured days, but the server already published estimates: the
+ // card shows the measured line because the server said it may.
+ const ready = render({ ...CAPPED, measuredDays: 2 });
+ expect(ready).toContain(
+ "Measured: 100,000 tokens a day over the last 2 days",
+ );
+ // Seven days, but the server withheld every estimate: no measured line.
+ const withheld = render({
+ ...CAPPED,
+ options: CAPPED.options.map((o) => ({
+ ...o,
+ estimatedTokensPerDay: null,
+ })),
+ });
+ expect(withheld).toContain(
+ "An estimate per level appears after three days",
+ );
+ });
+
+ it("stacks the estimate under the level on a narrow screen", () => {
+ const html = render(CAPPED);
+ expect(html).toMatch(
+ /class="[^"]*flex-col[^"]*sm:flex-row[^"]*"[^>]*>\s*/,
+ );
+ expect(html).toMatch(
+ /data-slot="ai-usage-estimate" class="[^"]*sm:shrink-0/,
+ );
+ expect(html).not.toMatch(
+ /data-slot="ai-usage-estimate" class="(?:(?!sm:)[^"])*\bshrink-0/,
+ );
+ });
+
it("offers the way back to the default when a choice is stored", () => {
const html = render({
...CAPPED,
diff --git a/src/components/settings/ai/ai-usage-card.tsx b/src/components/settings/ai/ai-usage-card.tsx
index a7817ab78..9075b48bf 100644
--- a/src/components/settings/ai/ai-usage-card.tsx
+++ b/src/components/settings/ai/ai-usage-card.tsx
@@ -70,6 +70,12 @@ export function AiUsageCard() {
const editable = provider.canConfigure && !save.isPending;
const tokens = (value: number) => new Intl.NumberFormat(locale).format(value);
const levelName = (level: AiUsageLevel) => t(LEVEL_KEYS[level].label);
+ // Whether the history suffices for an estimate is the server's call: it
+ // publishes the per-level estimates only once it does. The card reads that
+ // decision instead of repeating its day threshold.
+ const estimatesReady = usage.options.some(
+ (option) => option.estimatedTokensPerDay !== null,
+ );
const allowedLevels = usage.options
.filter((option) => option.allowed)
.map((option) => option.level);
@@ -144,31 +150,35 @@ export function AiUsageCard() {
) : null}
-
-
- {levelName(option.level)}
-
-
- {t(LEVEL_KEYS[option.level].hint)}
-
- {!option.allowed ? (
-
- {t("settings.ai.usage.limited")}
+ {/* Stacked under 640 px so the estimate never squeezes the
+ level's name and hint into a sliver; side by side above. */}
+
+
+
+ {levelName(option.level)}
- ) : null}
-
-
- {option.estimatedTokensPerDay === null
- ? null
- : t("settings.ai.usage.estimate", {
- tokens: tokens(option.estimatedTokensPerDay),
- })}
+
+ {t(LEVEL_KEYS[option.level].hint)}
+
+ {!option.allowed ? (
+
+ {t("settings.ai.usage.limited")}
+
+ ) : null}
+
+
+ {option.estimatedTokensPerDay === null
+ ? null
+ : t("settings.ai.usage.estimate", {
+ tokens: tokens(option.estimatedTokensPerDay),
+ })}
+
);
@@ -179,7 +189,7 @@ export function AiUsageCard() {
data-slot="ai-usage-measured"
className="text-muted-foreground text-xs"
>
- {usage.measuredTokensPerDay === null || usage.measuredDays < 3
+ {usage.measuredTokensPerDay === null || !estimatesReady
? t("settings.ai.usage.noEstimate")
: t("settings.ai.usage.measured", {
tokens: tokens(usage.measuredTokensPerDay),