From 62c7a29b46a748a929cd5bb61d48689a95c2de6f Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Wed, 2 Sep 2026 11:58:33 +0200 Subject: [PATCH] fix(web): MCP Usage page tolerates a backend without the tenant quota fields The organization-quota card called toLocaleString on undefined when the backend predates the tenant quota (deploy window, local dev against prod), tripping the error boundary. Tenant fields are optional on the client now; the card renders only when they are present. Co-Authored-By: Claude Fable 5.1 --- .../reporting/sections/SectionMcpUsage.tsx | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx b/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx index aecd3a3d..15a183ed 100644 --- a/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx +++ b/src/Web/autopilot-monitor-web/app/settings/reporting/sections/SectionMcpUsage.tsx @@ -25,22 +25,34 @@ interface DailyAggregate { } // GetMyMcpUsageResponse.quota — the caller's own windows plus the organization-wide windows every -// member shares. 0 = unlimited; -1 used = counters temporarily unavailable. +// member shares. 0 = unlimited; -1 used = counters temporarily unavailable. The tenant fields are +// optional on the client: a backend that predates the organization-wide quota omits them, and the +// page must keep working through that deploy window (web ships before or after the backend). interface QuotaState { dailyLimit: number; monthlyLimit: number; dailyUsed: number; monthlyUsed: number; - tenantPlan: string; - tenantDailyLimit: number; - tenantMonthlyLimit: number; - tenantDailyUsed: number; - tenantMonthlyUsed: number; + tenantPlan?: string; + tenantDailyLimit?: number; + tenantMonthlyLimit?: number; + tenantDailyUsed?: number; + tenantMonthlyUsed?: number; } -function QuotaBar({ label, used, limit }: { label: string; used: number; limit: number }) { - const unavailable = used < 0; - const unlimited = limit <= 0; +function hasTenantQuota(q: QuotaState): q is QuotaState & Required> { + return ( + typeof q.tenantDailyLimit === "number" && + typeof q.tenantMonthlyLimit === "number" && + typeof q.tenantDailyUsed === "number" && + typeof q.tenantMonthlyUsed === "number" + ); +} + +function QuotaBar({ label, used, limit }: { label: string; used: number | undefined; limit: number | undefined }) { + // Missing numbers (older backend, partial payload) render as unavailable instead of throwing. + const unavailable = typeof used !== "number" || used < 0; + const unlimited = typeof limit !== "number" || limit <= 0; const pct = unavailable || unlimited ? 0 : Math.min(100, Math.round((used / limit) * 100)); const tone = pct >= 100 ? "bg-red-500" : pct >= 80 ? "bg-amber-500" : "bg-indigo-500"; return ( @@ -50,7 +62,7 @@ function QuotaBar({ label, used, limit }: { label: string; used: number; limit: {unavailable ? "n/a" : used.toLocaleString()} {" / "} - {unlimited ? "unlimited" : limit.toLocaleString()} + {typeof limit !== "number" ? "n/a" : unlimited ? "unlimited" : limit.toLocaleString()}
@@ -200,18 +212,20 @@ export function SectionMcpUsage() {
-
-
-

Organization quota

- tenant plan {quota.tenantPlan} + {hasTenantQuota(quota) && ( +
+
+

Organization quota

+ {quota.tenantPlan && tenant plan {quota.tenantPlan}} +
+ + +

+ Shared by every account in your tenant. A personal plan override widens only the account's own + windows, never these. +

- - -

- Shared by every account in your tenant. A personal plan override widens only the account's own - windows, never these. -

-
+ )}
)}