Files
paseo/packages/app/src/usage/model.ts
T
Mohamed Boudra ff9b09b210 feat(usage): open the pill popover from cache and refresh on demand
Opening the popover no longer forces a vendor fetch; it shows the cached
report. A Refresh control force-refreshes only this report
(usage.list_reports with its single ID and forceRefresh), shows
Refreshing... while it runs, keeps the previous report visible, and says
"Unable to refresh usage" if the request fails.
2026-09-27 12:41:45 +02:00

114 lines
3.4 KiB
TypeScript

import { formatCompactTimeAgoAsProse } from "@/utils/time";
import { usageCopy } from "./copy";
import type { UsageReport, UsageReportEntry, UsageView, UsageWindow } from "./types";
export function usedPercent(window: UsageWindow): number | null {
if (window.usedPct != null) return window.usedPct;
if (window.remainingPct != null) return 100 - window.remainingPct;
return null;
}
/** The window the source marked as its headline. Sources own that choice; there is no fallback. */
export function headlineWindow(report: UsageReport): UsageWindow | null {
return report.windows.find((window) => window.headline === true) ?? null;
}
export interface UsagePill {
icon: string | null;
sourceLabel: string;
/** Headline percent, else the plan label, else nothing beside the icon. */
text: string | null;
}
export function resolveUsagePill(input: {
supportsUsage: boolean;
entry: UsageReportEntry | null | undefined;
}): UsagePill | null {
const { supportsUsage, entry } = input;
if (!supportsUsage || !entry) return null;
const window = headlineWindow(entry.report);
const percent = window ? usedPercent(window) : null;
return {
icon: entry.icon ?? null,
sourceLabel: entry.sourceLabel,
text:
percent != null
? `${Math.round(Math.max(0, Math.min(100, percent)))}%`
: (entry.report.planLabel ?? null),
};
}
/** When a report was fetched, from its compact relative time: "Updated 3m ago". */
export function formatUsageFreshness(compactTimeAgo: string): string {
return `${usageCopy.updated} ${formatCompactTimeAgoAsProse(compactTimeAgo)}`;
}
/** A user-requested refresh of one report. The previous report stays on screen throughout. */
export type UsageRefresh = "idle" | "pending" | "failed";
export function resolveUsageRefresh(mutation: {
isPending: boolean;
error: unknown;
}): UsageRefresh {
if (mutation.isPending) return "pending";
if (mutation.error) return "failed";
return "idle";
}
export interface UsageQueryState {
data: UsageReportEntry[] | undefined;
error: unknown;
isFetching: boolean;
}
export function resolveUsageView(input: {
isConnected: boolean;
supportsUsage: boolean;
query: UsageQueryState | undefined;
}): UsageView {
const { isConnected, supportsUsage, query } = input;
if (!isConnected) return { kind: "unavailable", message: usageCopy.hostUnavailable };
if (!supportsUsage) return { kind: "unavailable", message: usageCopy.hostUpgradeRequired };
if (query?.data) {
return { kind: "ready", reports: query.data, isRefreshing: query.isFetching };
}
if (query?.error) {
return {
kind: "error",
message: query.error instanceof Error ? query.error.message : String(query.error),
};
}
return { kind: "loading" };
}
export interface UsageHost {
serverId: string;
label: string;
isConnected: boolean;
supportsUsage: boolean;
}
export interface UsageHostGroup {
serverId: string;
label: string;
view: UsageView;
}
/** One group per connected host, in host order. */
export function groupUsageByHost(
hosts: readonly UsageHost[],
queries: ReadonlyMap<string, UsageQueryState>,
): UsageHostGroup[] {
return hosts
.filter((host) => host.isConnected)
.map((host) => ({
serverId: host.serverId,
label: host.label,
view: resolveUsageView({
isConnected: true,
supportsUsage: host.supportsUsage,
query: queries.get(host.serverId),
}),
}));
}