{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "usage-dashboard",
  "title": "Usage Dashboard",
  "description": "A period-scoped usage block — headline, quota bar, cumulative burn-up curve and per-dimension breakdown all derived from one bucket array, so the four readouts cannot disagree; run-rate projection stated apart from actual usage, and period switches that never put a new label over old figures.",
  "dependencies": [
    "zod",
    "recharts",
    "lucide-react"
  ],
  "registryDependencies": [
    "chart",
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/blocks/usage-dashboard.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  ArrowDown,\n  ArrowUp,\n  ArrowUpRight,\n  ChevronDown,\n  CircleAlert,\n  Gauge,\n  Minus,\n  ServerCrash,\n  TrendingUp,\n  TriangleAlert,\n} from \"lucide-react\"\nimport { CartesianGrid, Line, LineChart, ReferenceLine, XAxis, YAxis } from \"recharts\"\n\nimport { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from \"@/components/ui/chart\"\nimport { cn } from \"@/lib/utils\"\nimport type {\n  UsageDashboardData,\n  UsageMetric,\n  UsagePeriod,\n  UsageUnit,\n} from \"./usage-dashboard.contract\"\n\nconst HOUR = 3_600_000\nconst DAY = 24 * HOUR\n\n/** Below this an exact grouped figure is at most five digits — short enough to print in full. */\nconst COMPACT_FLOOR = 10_000\n\nconst BINARY_UNITS = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\"] as const\nconst BINARY_STEP = 1024\n\nexport interface UsageDashboardProps\n  extends UsageDashboardData,\n    Omit<React.HTMLAttributes<HTMLElement>, \"children\" | \"title\"> {\n  /**\n   * The instant the countdown, the elapsed fraction and the run rate are measured\n   * against, injected rather than read from the clock. A render that calls\n   * `Date.now()` is a render whose output nobody can reproduce — not the server,\n   * not the next paint, not a screenshot test.\n   */\n  now: string | number | Date\n  /** BCP-47 tag for every `Intl` formatter — explicit so a server render and a client render agree. */\n  locale?: string\n  /** IANA zone the period dates are printed in. The visitor's zone is never guessed. */\n  timeZone?: string\n  /**\n   * How many breakdown rows to show before the rest collapse into one \"Other\"\n   * row. Clamped to at least 1. Collapsing only happens above this count, and\n   * the \"Other\" row always names how many dimensions it merged — plus a button\n   * that expands every one of them. There is no silent cap.\n   */\n  maxDimensions?: number\n  /** Share of the allowance at which the quota panel turns to \"near limit\". */\n  warnAt?: number\n  /** Share of the allowance at which the quota panel turns to \"critical\". */\n  criticalAt?: number\n  /** Required for the period switcher to render at all — a switcher nobody listens to changes nothing. */\n  onPeriodChange?: (periodId: string) => void\n  /** Shown beside the error message; omit to drop the retry affordance entirely. */\n  onRetry?: () => void\n  /** Renders the upgrade entry as a real button instead of the contract's link. */\n  onUpgrade?: () => void\n  /** Card heading; doubles as the block's accessible name. */\n  heading?: React.ReactNode\n}\n\n/* -------------------------------------------------------------------------- */\n/* Formatting                                                                  */\n/* -------------------------------------------------------------------------- */\n\ninterface Formatters {\n  locale: string\n  plain: Intl.NumberFormat\n  compact: Intl.NumberFormat[]\n  decimals: (value: number, digits: number) => string\n  money: (value: number, code: string | undefined) => string\n  day: Intl.DateTimeFormat | null\n  dayShort: Intl.DateTimeFormat | null\n  instant: Intl.DateTimeFormat | null\n}\n\nfunction buildFormatters(locale: string, timeZone: string): Formatters {\n  const safe = <T,>(make: (tag: string) => T): T => {\n    try {\n      return make(locale)\n    } catch {\n      return make(\"en-US\")\n    }\n  }\n  const plain = safe(tag => new Intl.NumberFormat(tag))\n  const compact = [0, 1, 2].map(digits =>\n    safe(tag => new Intl.NumberFormat(tag, { maximumFractionDigits: digits, notation: \"compact\" })),\n  )\n  const decimalCache = new Map<number, Intl.NumberFormat>()\n  const decimals = (value: number, digits: number) => {\n    let nf = decimalCache.get(digits)\n    if (!nf) {\n      nf = safe(tag => new Intl.NumberFormat(tag, { maximumFractionDigits: digits, minimumFractionDigits: digits }))\n      decimalCache.set(digits, nf)\n    }\n    return nf.format(value)\n  }\n  const moneyCache = new Map<string, Intl.NumberFormat | null>()\n  const money = (value: number, code: string | undefined) => {\n    const currency = code ?? \"USD\"\n    if (!moneyCache.has(currency)) {\n      try {\n        moneyCache.set(currency, new Intl.NumberFormat(locale, { currency, style: \"currency\" }))\n      } catch {\n        moneyCache.set(currency, null)\n      }\n    }\n    const nf = moneyCache.get(currency)\n    return nf ? nf.format(value) : `${decimals(value, 2)} ${currency}`\n  }\n  let day: Intl.DateTimeFormat | null = null\n  let dayShort: Intl.DateTimeFormat | null = null\n  let instant: Intl.DateTimeFormat | null = null\n  try {\n    day = new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeZone })\n    dayShort = new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"short\", timeZone })\n    instant = new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeStyle: \"short\", timeZone })\n  } catch {\n    // An unknown IANA zone must not blank the block — fall back to raw ISO.\n    day = null\n    dayShort = null\n    instant = null\n  }\n  return { compact, day, dayShort, decimals, instant, locale, money, plain }\n}\n\n/** Three significant digits: 512 → 0 decimals, 45.7 → 1, 1.75 → 2. */\nfunction decimalsFor(value: number) {\n  const abs = Math.abs(value)\n  if (abs >= 100) return 0\n  if (abs >= 10) return 1\n  return 2\n}\n\nfunction roundTo(value: number, digits: number) {\n  const factor = 10 ** digits\n  return Math.round(value * factor) / factor\n}\n\n/**\n * Which rung of the binary ladder a byte count sits on. `Math.log` is not exact\n * at the powers of 1024, so the guessed exponent is corrected afterwards.\n */\nfunction binaryExponent(bytes: number) {\n  const abs = Math.abs(bytes)\n  if (!(abs >= BINARY_STEP)) return 0\n  let exponent = Math.min(BINARY_UNITS.length - 1, Math.floor(Math.log(abs) / Math.log(BINARY_STEP)))\n  if (exponent < BINARY_UNITS.length - 1 && abs / BINARY_STEP ** exponent >= BINARY_STEP) exponent += 1\n  if (exponent > 0 && abs / BINARY_STEP ** exponent < 1) exponent -= 1\n  return exponent\n}\n\nfunction formatDuration(totalSeconds: number) {\n  const seconds = Math.max(0, Math.round(totalSeconds))\n  if (seconds < 60) return `${seconds}s`\n  const minutes = Math.floor(seconds / 60)\n  if (minutes < 60) return `${minutes}m`\n  const hours = Math.floor(minutes / 60)\n  if (hours < 24) {\n    const rest = minutes % 60\n    return rest > 0 ? `${hours}h ${rest}m` : `${hours}h`\n  }\n  const days = Math.floor(hours / 24)\n  const rest = hours % 24\n  return rest > 0 ? `${days}d ${rest}h` : `${days}d`\n}\n\ninterface UnitSpec {\n  unit: UsageUnit\n  unitLabel?: string\n  currencyCode?: string\n}\n\n/** Scannable label. The exact grouped figure always follows in `title`. */\nfunction formatAmount(f: Formatters, spec: UnitSpec, raw: number): string {\n  const value = Number.isFinite(raw) ? Math.max(0, raw) : 0\n  switch (spec.unit) {\n    case \"bytes\": {\n      const exponent = binaryExponent(value)\n      const mantissa = value / BINARY_STEP ** exponent\n      const digits = exponent === 0 ? 0 : decimalsFor(mantissa)\n      return `${f.decimals(roundTo(mantissa, digits), digits)} ${BINARY_UNITS[exponent]}`\n    }\n    case \"currency\":\n      return f.money(value, spec.currencyCode)\n    case \"seconds\":\n      return formatDuration(value)\n    case \"count\": {\n      const rounded = Math.round(value)\n      const text = rounded < COMPACT_FLOOR ? f.plain.format(rounded) : f.compact[compactDigits(rounded)].format(rounded)\n      return spec.unitLabel ? `${text} ${spec.unitLabel}` : text\n    }\n  }\n}\n\n/**\n * Smallest number of fraction digits whose compact label still lands within 0.5%\n * of the true value. \"1.1M\" for 1,050,000 is a 4.8% lie that makes two different\n * numbers look like one number; \"1.05M\" is exact.\n */\nfunction compactDigits(value: number): number {\n  if (!(value > 0)) return 0\n  const exponent = Math.min(12, Math.floor(Math.log10(value) / 3) * 3)\n  const mantissa = value / 10 ** exponent\n  for (let digits = 0; digits < 2; digits += 1) {\n    const factor = 10 ** digits\n    const rounded = Math.round(mantissa * factor) / factor\n    if (Math.abs(rounded * 10 ** exponent - value) <= value * 0.005) return digits\n  }\n  return 2\n}\n\n/** The unrounded figure, for `title` and accessible text. */\nfunction formatExact(f: Formatters, spec: UnitSpec, raw: number): string {\n  const value = Number.isFinite(raw) ? Math.max(0, raw) : 0\n  switch (spec.unit) {\n    case \"bytes\":\n      return `${f.plain.format(Math.round(value))} B`\n    case \"currency\":\n      return f.money(value, spec.currencyCode)\n    case \"seconds\":\n      return `${f.plain.format(Math.round(value))} s`\n    case \"count\":\n      return spec.unitLabel\n        ? `${f.plain.format(Math.round(value))} ${spec.unitLabel}`\n        : f.plain.format(Math.round(value))\n  }\n}\n\n/**\n * Percentage of the allowance. Two lies are ruled out by construction: reading\n * \"100%\" while there is headroom left, and reading \"100%\" while the allowance is\n * already blown. Near the boundary the label gains a decimal so it can tell the\n * truth.\n */\nfunction formatPercent(f: Formatters, ratio: number): string {\n  const pct = ratio * 100\n  if (!(pct > 0)) return \"0%\"\n  if (pct < 0.1) return \"<0.1%\"\n  const digits = pct < 10 || (pct > 95 && pct < 105) ? 1 : 0\n  let shown = roundTo(pct, digits)\n  if (ratio < 1 && shown >= 100) shown = 100 - 10 ** -digits\n  if (ratio > 1 && shown <= 100) shown = 100 + 10 ** -digits\n  return `${f.decimals(shown, digits)}%`\n}\n\n/** Whole-unit spans, never rounded up: 1.9 days reads \"1 day\". */\nfunction formatSpan(f: Formatters, ms: number): string {\n  if (ms < HOUR) return \"under an hour\"\n  if (ms < DAY) {\n    const hours = Math.floor(ms / HOUR)\n    return `${f.plain.format(hours)} ${hours === 1 ? \"hour\" : \"hours\"}`\n  }\n  const days = Math.floor(ms / DAY)\n  return `${f.plain.format(days)} ${days === 1 ? \"day\" : \"days\"}`\n}\n\nfunction formatDay(f: Formatters, epoch: number) {\n  return f.day ? f.day.format(epoch) : new Date(epoch).toISOString()\n}\n\nfunction formatInstant(f: Formatters, epoch: number) {\n  return f.instant ? f.instant.format(epoch) : new Date(epoch).toISOString()\n}\n\nfunction formatDayRange(f: Formatters, from: number, to: number) {\n  if (!f.day) return `${new Date(from).toISOString()} – ${new Date(to).toISOString()}`\n  if (typeof f.day.formatRange === \"function\") return f.day.formatRange(from, to)\n  return `${f.day.format(from)} – ${f.day.format(to)}`\n}\n\nfunction toEpoch(value: string | number | Date): number {\n  if (typeof value === \"number\") return value\n  // `Date.parse`, not `new Date(...)`: a pure call keeps the render pure.\n  if (typeof value === \"string\") return Date.parse(value)\n  return value.getTime()\n}\n\n/**\n * Round a domain ceiling up to a readable step. The ladder is deliberately fine\n * (1, 1.2, 1.5, 2 …): a coarse one snaps 600,000 up to 1,000,000 and squashes the\n * whole curve into the bottom third of the box for no reason.\n */\nconst NICE_STEPS = [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] as const\n\nfunction niceCeil(value: number): number {\n  if (!(value > 0) || !Number.isFinite(value)) return 1\n  const magnitude = 10 ** Math.floor(Math.log10(value))\n  const normalized = value / magnitude\n  const step = NICE_STEPS.find(candidate => normalized <= candidate + 1e-9) ?? 10\n  return step * magnitude\n}\n\n/* -------------------------------------------------------------------------- */\n/* Derivation — one pipeline, so the four readouts cannot disagree             */\n/* -------------------------------------------------------------------------- */\n\ninterface BreakdownRow {\n  key: string\n  label: string\n  detail?: string\n  value: number\n  share: number\n  /** How many dimensions this row merged. 1 for a real dimension. */\n  merged: number\n}\n\ninterface TrendPoint {\n  t: number\n  actual?: number\n  projected?: number\n  /** Usage inside this bucket alone — the tooltip's second number. */\n  delta?: number\n}\n\ntype Projection =\n  | { kind: \"none\" }\n  | { kind: \"closed\" }\n  | { kind: \"flat\" }\n  | {\n      kind: \"run-rate\"\n      total: number\n      ratio: number | null\n      perDay: number\n      /** When the allowance runs out, if that happens before the period ends. */\n      exhaustAt: number | null\n    }\n\ninterface Derived {\n  used: number\n  limit: number | null\n  ratio: number | null\n  remaining: number | null\n  overBy: number\n  /** Every dimension with usage, largest first — before any collapsing. */\n  allRows: BreakdownRow[]\n  points: TrendPoint[]\n  domainMax: number\n  ticks: number[]\n  startEpoch: number\n  endEpoch: number\n  nowEpoch: number\n  elapsedRatio: number | null\n  projection: Projection\n  hasUsage: boolean\n}\n\nfunction deriveUsage(\n  data: Pick<UsageDashboardData, \"buckets\" | \"dimensions\" | \"limit\" | \"period\">,\n  nowInput: string | number | Date,\n): Derived {\n  const startEpoch = Date.parse(data.period.start)\n  const endEpoch = Date.parse(data.period.end)\n  const nowEpoch = toEpoch(nowInput)\n  const spanValid = Number.isFinite(startEpoch) && Number.isFinite(endEpoch) && endEpoch > startEpoch\n\n  // --- totals per dimension, over every key that actually appears -----------\n  // Keys with no declared dimension are kept under their raw id rather than\n  // dropped: dropping one would silently break \"the rows add up to the total\",\n  // which is the single property this block is bought for.\n  const totals = new Map<string, number>()\n  const buckets = data.buckets\n    .map(bucket => ({ start: Date.parse(bucket.start), values: bucket.values }))\n    .filter(bucket => Number.isFinite(bucket.start))\n    .sort((a, b) => a.start - b.start)\n\n  const bucketTotals: number[] = []\n  for (const bucket of buckets) {\n    let bucketTotal = 0\n    for (const [key, raw] of Object.entries(bucket.values)) {\n      const value = Number.isFinite(raw) ? Math.max(0, raw) : 0\n      if (value === 0) continue\n      bucketTotal += value\n      totals.set(key, (totals.get(key) ?? 0) + value)\n    }\n    bucketTotals.push(bucketTotal)\n  }\n\n  let used = 0\n  for (const value of totals.values()) used += value\n\n  const labels = new Map(data.dimensions.map(dimension => [dimension.id, dimension]))\n  const allRows: BreakdownRow[] = [...totals.entries()]\n    .map(([key, value]) => ({\n      detail: labels.get(key)?.detail,\n      key,\n      label: labels.get(key)?.label ?? key,\n      merged: 1,\n      share: used > 0 ? value / used : 0,\n      value,\n    }))\n    // Largest first, then by label, then by key: a stable order that does not\n    // shuffle when two dimensions tie.\n    .sort((a, b) => b.value - a.value || a.label.localeCompare(b.label, \"en\") || a.key.localeCompare(b.key))\n\n  // --- quota ---------------------------------------------------------------\n  const limit = data.limit !== null && Number.isFinite(data.limit) && data.limit > 0 ? data.limit : null\n  const ratio = limit === null ? null : used / limit\n  const remaining = limit === null ? null : Math.max(0, limit - used)\n  const overBy = limit === null ? 0 : Math.max(0, used - limit)\n\n  // --- trend: cumulative, so the last point IS the headline figure ----------\n  // A per-bucket chart would end on \"today's usage\", which is a different number\n  // from \"used this period\" and invites exactly the reconciliation error this\n  // block exists to avoid. A burn-up curve ends on the total by construction.\n  const step = inferStep(buckets.map(bucket => bucket.start), startEpoch, endEpoch, spanValid)\n  const points: TrendPoint[] = []\n  if (spanValid) points.push({ actual: 0, delta: 0, t: startEpoch })\n  let running = 0\n  buckets.forEach((bucket, index) => {\n    running += bucketTotals[index]\n    const at = spanValid ? Math.min(endEpoch, bucket.start + step) : bucket.start + step\n    points.push({ actual: running, delta: bucketTotals[index], t: at })\n  })\n\n  // --- projection ----------------------------------------------------------\n  const elapsedMs = spanValid ? nowEpoch - startEpoch : Number.NaN\n  const totalMs = spanValid ? endEpoch - startEpoch : Number.NaN\n  const elapsedRatio = spanValid ? Math.min(1, Math.max(0, elapsedMs / totalMs)) : null\n\n  let projection: Projection = { kind: \"none\" }\n  if (spanValid && nowEpoch >= endEpoch) {\n    projection = { kind: \"closed\" }\n  } else if (spanValid && elapsedMs > 0) {\n    if (!(used > 0)) {\n      projection = { kind: \"flat\" }\n    } else {\n      // Linear run rate: what the period ends on if consumption keeps the pace\n      // it has held so far. Deliberately the whole-period average and not a\n      // trailing window — the number has to be reproducible from two figures the\n      // reader can see (used, elapsed), or it is not auditable.\n      const total = used * (totalMs / elapsedMs)\n      const perDay = used / (elapsedMs / DAY)\n      // `remaining <= 0` is its own case: the allowance is already gone, and\n      // \"runs out around <today>\" would read as a future event that has in fact\n      // already happened. The over-quota sentence carries that instead.\n      const exhaustMs =\n        limit === null || remaining === null || remaining <= 0 || !(perDay > 0) ? null : (remaining / perDay) * DAY\n      const exhaustAt =\n        exhaustMs !== null && Number.isFinite(exhaustMs) && nowEpoch + exhaustMs <= endEpoch\n          ? nowEpoch + exhaustMs\n          : null\n      projection = { exhaustAt, kind: \"run-rate\", perDay, ratio: limit === null ? null : total / limit, total }\n    }\n  }\n\n  if (projection.kind === \"run-rate\" && points.length > 0) {\n    const last = points[points.length - 1]\n    last.projected = last.actual\n    points.push({ projected: projection.total, t: endEpoch })\n  }\n\n  const dataMax = Math.max(used, projection.kind === \"run-rate\" ? projection.total : 0, limit ?? 0)\n  const domainMax = niceCeil(dataMax > 0 ? dataMax : 1)\n  const ticks = [0, 0.25, 0.5, 0.75, 1].map(fraction => domainMax * fraction)\n\n  return {\n    allRows,\n    domainMax,\n    elapsedRatio,\n    endEpoch,\n    hasUsage: used > 0,\n    limit,\n    nowEpoch,\n    overBy,\n    points,\n    projection,\n    ratio,\n    remaining,\n    startEpoch,\n    ticks,\n    used,\n  }\n}\n\n/**\n * Bucket width, taken from the smallest gap between consecutive bucket starts.\n * The cumulative value is plotted at the bucket's *end* — \"by the end of day 12\n * you had used X\" — so a single-bucket period still spans a real width instead\n * of collapsing to a dot on the y-axis.\n */\nfunction inferStep(starts: number[], startEpoch: number, endEpoch: number, spanValid: boolean): number {\n  let smallest = Number.POSITIVE_INFINITY\n  for (let index = 1; index < starts.length; index += 1) {\n    const gap = starts[index] - starts[index - 1]\n    if (gap > 0 && gap < smallest) smallest = gap\n  }\n  if (Number.isFinite(smallest)) return smallest\n  if (spanValid) return (endEpoch - startEpoch) / Math.max(1, starts.length)\n  return DAY\n}\n\n/* -------------------------------------------------------------------------- */\n/* Pieces                                                                      */\n/* -------------------------------------------------------------------------- */\n\nfunction Skeleton({ className }: { className?: string }) {\n  return <div className={cn(\"animate-pulse rounded bg-muted motion-reduce:animate-none\", className)} />\n}\n\nconst CARD = \"flex min-w-0 flex-col gap-1 rounded-lg border bg-card p-3\"\n\nfunction PeriodSwitcher({\n  activeId,\n  onPeriodChange,\n  periods,\n}: {\n  activeId: string\n  onPeriodChange: (periodId: string) => void\n  periods: UsagePeriod[]\n}) {\n  const buttons = React.useRef<Array<HTMLButtonElement | null>>([])\n  const activeIndex = periods.findIndex(period => period.id === activeId)\n  const tabbableIndex = activeIndex === -1 ? 0 : activeIndex\n\n  // Focus moves imperatively: selection is controlled from outside, so waiting\n  // for a re-render would stall whenever the host keeps the same period.\n  function moveTo(index: number) {\n    const next = (index + periods.length) % periods.length\n    buttons.current[next]?.focus()\n    onPeriodChange(periods[next].id)\n  }\n\n  function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>, index: number) {\n    const targets: Record<string, number | undefined> = {\n      ArrowDown: index + 1,\n      ArrowLeft: index - 1,\n      ArrowRight: index + 1,\n      ArrowUp: index - 1,\n      End: periods.length - 1,\n      Home: 0,\n    }\n    const target = targets[event.key]\n    if (target === undefined) return\n    event.preventDefault()\n    moveTo(target)\n  }\n\n  return (\n    <div\n      aria-label=\"Billing period\"\n      className=\"inline-flex flex-wrap items-center gap-0.5 rounded-lg border bg-muted/50 p-1\"\n      role=\"radiogroup\"\n    >\n      {periods.map((period, index) => (\n        <button\n          aria-checked={period.id === activeId}\n          className={cn(\n            \"cursor-pointer rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n            \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n            period.id === activeId\n              ? \"bg-background text-foreground shadow-sm\"\n              : \"text-muted-foreground hover:text-foreground\",\n          )}\n          key={period.id}\n          onClick={() => onPeriodChange(period.id)}\n          onKeyDown={event => handleKeyDown(event, index)}\n          ref={element => {\n            buttons.current[index] = element\n          }}\n          role=\"radio\"\n          tabIndex={index === tabbableIndex ? 0 : -1}\n          type=\"button\"\n        >\n          {period.label}\n        </button>\n      ))}\n    </div>\n  )\n}\n\ntype DeltaTone = \"good\" | \"bad\" | \"neutral\"\n\nfunction DeltaChip({\n  f,\n  higherIsBetter,\n  previous,\n  value,\n}: {\n  f: Formatters\n  /**\n   * Polarity, not direction. `null` means the measure has none — consuming more\n   * or less of your own allowance is a fact, not good news or bad news, so the\n   * headline delta stays neutral while a host metric like error count does not.\n   */\n  higherIsBetter: boolean | null\n  previous: number\n  value: number\n}) {\n  if (previous === 0) {\n    return (\n      <span className=\"inline-flex items-center gap-1 rounded-md bg-muted px-1.5 py-0.5 text-xs font-medium text-muted-foreground\">\n        <ArrowUp aria-hidden=\"true\" className=\"size-3\" />\n        New\n        <span className=\"sr-only\"> — nothing in the previous period</span>\n      </span>\n    )\n  }\n  const raw = ((value - previous) / Math.abs(previous)) * 100\n  const percent = Math.abs(raw) >= 100 ? Math.round(raw) : Math.round(raw * 10) / 10\n  const direction = percent > 0 ? \"up\" : percent < 0 ? \"down\" : \"flat\"\n  const tone: DeltaTone =\n    direction === \"flat\" || higherIsBetter === null\n      ? \"neutral\"\n      : (direction === \"up\") === higherIsBetter\n        ? \"good\"\n        : \"bad\"\n  const Icon = direction === \"up\" ? ArrowUp : direction === \"down\" ? ArrowDown : Minus\n  const text = `${percent > 0 ? \"+\" : \"\"}${f.decimals(percent, Math.abs(percent) >= 100 ? 0 : 1)}%`\n  // Tone is carried by an icon and a word as well as the tint, and the tint is a\n  // 10% mix — the strongest that keeps --destructive at AA on --card in light mode.\n  const color = tone === \"good\" ? \"var(--primary)\" : tone === \"bad\" ? \"var(--destructive)\" : undefined\n\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center gap-0.5 rounded-md px-1.5 py-0.5 text-xs font-medium tabular-nums\",\n        tone === \"neutral\" && \"bg-muted text-muted-foreground\",\n      )}\n      style={color ? { background: `color-mix(in oklab, ${color} 10%, transparent)`, color } : undefined}\n    >\n      <Icon aria-hidden=\"true\" className=\"size-3 shrink-0\" />\n      {text}\n      <span className=\"sr-only\">\n        {` ${direction === \"up\" ? \"up\" : direction === \"down\" ? \"down\" : \"unchanged\"} vs the previous period`}\n        {tone === \"good\" ? \", better\" : tone === \"bad\" ? \", worse\" : \"\"}\n      </span>\n    </span>\n  )\n}\n\nfunction StatCard({\n  children,\n  exact,\n  label,\n  slot,\n  sub,\n  value,\n}: {\n  children?: React.ReactNode\n  exact?: string\n  label: string\n  /** Stable hook for consumers who need to style or assert on one specific card. */\n  slot?: string\n  sub?: React.ReactNode\n  value: string\n}) {\n  return (\n    <div className={CARD} data-slot={slot}>\n      <span className=\"text-xs text-muted-foreground wrap-anywhere\">{label}</span>\n      <span className=\"flex flex-wrap items-baseline gap-2\">\n        <span\n          className=\"text-2xl font-semibold tracking-tight tabular-nums wrap-anywhere\"\n          data-slot={slot ? `${slot}-value` : undefined}\n          title={exact}\n        >\n          {value}\n        </span>\n        {children}\n      </span>\n      {sub && <span className=\"text-xs text-muted-foreground tabular-nums wrap-anywhere\">{sub}</span>}\n    </div>\n  )\n}\n\nfunction MetricCard({ f, metric }: { f: Formatters; metric: UsageMetric }) {\n  const spec: UnitSpec = {\n    currencyCode: metric.currencyCode,\n    unit: metric.unit ?? \"count\",\n    unitLabel: metric.unitLabel,\n  }\n  const value = formatAmount(f, spec, metric.value)\n  const exact = formatExact(f, spec, metric.value)\n  return (\n    <StatCard exact={exact === value ? undefined : exact} label={metric.label} sub={metric.hint} value={value}>\n      {metric.previous !== undefined && Number.isFinite(metric.previous) && (\n        <DeltaChip\n          f={f}\n          higherIsBetter={metric.higherIsBetter ?? true}\n          previous={metric.previous}\n          value={metric.value}\n        />\n      )}\n    </StatCard>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n/* Block                                                                       */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A period-scoped usage dashboard: what has been consumed this cycle, how it is\n * trending against the allowance, which dimensions are spending it, and whether\n * the current rate blows the cap before the period resets.\n *\n * Everything on screen is derived from one array of time buckets, so the headline\n * figure, the quota percentage, the last point of the trend and the sum of the\n * breakdown are the same number by construction rather than by convention.\n */\nexport const UsageDashboard = React.forwardRef<HTMLElement, UsageDashboardProps>(function UsageDashboard(\n  {\n    activePeriodId,\n    breakdownLabel = \"Breakdown\",\n    buckets,\n    className,\n    criticalAt = 0.9,\n    currencyCode,\n    dimensions,\n    heading = \"Usage\",\n    limit,\n    locale = \"en-US\",\n    maxDimensions = 5,\n    meterLabel,\n    metrics,\n    now,\n    onPeriodChange,\n    onRetry,\n    onUpgrade,\n    period,\n    periods,\n    previousUsed,\n    status,\n    timeZone = \"UTC\",\n    unit,\n    unitLabel,\n    upgrade,\n    warnAt = 0.75,\n    ...rest\n  },\n  ref,\n) {\n  const headingId = React.useId()\n  const [expanded, setExpanded] = React.useState(false)\n\n  const f = React.useMemo(() => buildFormatters(locale, timeZone), [locale, timeZone])\n  const spec = React.useMemo<UnitSpec>(() => ({ currencyCode, unit, unitLabel }), [currencyCode, unit, unitLabel])\n  const derived = React.useMemo(\n    () => deriveUsage({ buckets, dimensions, limit, period }, now),\n    [buckets, dimensions, limit, now, period],\n  )\n\n  // The selected period may differ from the one these numbers describe. When it\n  // does, the payload is stale — and the heading keeps naming the period the\n  // FIGURES came from, never the one the user just clicked. That is the whole\n  // trick: a torn \"new title over old numbers\" frame is unrepresentable, because\n  // the title and the numbers are read out of the same object.\n  const selectedId = activePeriodId ?? period.id\n  const stale = selectedId !== period.id\n  const incoming = stale ? (periods ?? []).find(entry => entry.id === selectedId) : undefined\n\n  const showSwitcher = (periods?.length ?? 0) >= 2 && Boolean(onPeriodChange)\n\n  // A \"ready\" payload with nothing metered is the new-account case. Rendering it\n  // as a wall of zeros with a 0% bar looks like a broken dashboard; it gets the\n  // empty branch instead, named for the period it is empty in.\n  const effectiveStatus = status === \"ready\" && !derived.hasUsage ? \"empty\" : status\n\n  const rangeText =\n    Number.isFinite(derived.startEpoch) && Number.isFinite(derived.endEpoch)\n      ? formatDayRange(f, derived.startEpoch, derived.endEpoch)\n      : null\n  const remainingMs = derived.endEpoch - derived.nowEpoch\n  const resetText =\n    !Number.isFinite(remainingMs) || derived.projection.kind === \"none\"\n      ? null\n      : remainingMs <= 0\n        ? \"Period closed\"\n        : `Resets in ${formatSpan(f, remainingMs)}`\n\n  const header = (\n    <div className=\"flex flex-wrap items-start justify-between gap-x-4 gap-y-2\">\n      <div className=\"flex min-w-0 flex-col gap-0.5\">\n        <h3 className=\"text-sm font-medium wrap-anywhere\" id={headingId}>\n          {heading}\n        </h3>\n        <p className=\"flex flex-wrap items-baseline gap-x-2 text-xs text-muted-foreground\">\n          {/* data-period is the machine-readable half of the same promise the\n              label makes: it names the payload the figures below came from. */}\n          <span className=\"font-medium text-foreground wrap-anywhere\" data-period={period.id}>\n            {period.label}\n          </span>\n          {rangeText && <span className=\"tabular-nums wrap-anywhere\">{rangeText}</span>}\n          {resetText && (\n            <span\n              className=\"tabular-nums\"\n              title={Number.isFinite(derived.endEpoch) ? formatInstant(f, derived.endEpoch) : undefined}\n            >\n              · {resetText}\n            </span>\n          )}\n        </p>\n      </div>\n      {showSwitcher && onPeriodChange && (\n        <PeriodSwitcher activeId={selectedId} onPeriodChange={onPeriodChange} periods={periods ?? []} />\n      )}\n    </div>\n  )\n\n  return (\n    <section\n      aria-labelledby={headingId}\n      className={cn(\"@container flex w-full flex-col gap-4 rounded-xl border bg-card p-4 text-sm\", className)}\n      data-slot=\"usage-dashboard\"\n      ref={ref}\n      {...rest}\n    >\n      {header}\n\n      {stale && effectiveStatus !== \"loading\" && (\n        <p\n          className=\"flex items-start gap-2 rounded-md border border-dashed px-2.5 py-2 text-xs text-muted-foreground\"\n          role=\"status\"\n        >\n          <span\n            aria-hidden=\"true\"\n            className=\"mt-0.5 size-3 shrink-0 animate-pulse rounded-full bg-muted-foreground motion-reduce:animate-none\"\n          />\n          <span className=\"min-w-0 wrap-anywhere\">\n            Loading {incoming ? incoming.label : \"the selected period\"}. Everything below is still{\" \"}\n            <span className=\"font-medium text-foreground\">{period.label}</span> — no figure has changed yet.\n          </span>\n        </p>\n      )}\n\n      {effectiveStatus === \"loading\" && (\n        <>\n          <span className=\"sr-only\" role=\"status\">\n            Loading usage for {period.label}\n          </span>\n          <div aria-hidden=\"true\" className=\"flex flex-col gap-4\">\n            <div className=\"grid gap-3 grid-cols-[repeat(auto-fit,minmax(min(11rem,100%),1fr))]\">\n              {[0, 1, 2].map(index => (\n                <div className={CARD} key={index}>\n                  <Skeleton className=\"h-3 w-24\" />\n                  <Skeleton className=\"h-7 w-32\" />\n                  <Skeleton className=\"h-3 w-20\" />\n                </div>\n              ))}\n            </div>\n            <Skeleton className=\"h-3 w-full rounded-full\" />\n            <Skeleton className=\"h-56 w-full\" />\n            <div className=\"flex flex-col gap-2\">\n              {[0, 1, 2, 3].map(index => (\n                <Skeleton className=\"h-8 w-full\" key={index} />\n              ))}\n            </div>\n          </div>\n        </>\n      )}\n\n      {effectiveStatus === \"empty\" && (\n        <div className=\"flex flex-col items-center gap-2 px-6 py-12 text-center\">\n          <Gauge aria-hidden=\"true\" className=\"size-8 text-muted-foreground/60\" />\n          <p className=\"font-medium wrap-anywhere\">\n            No {meterLabel} in {period.label}\n          </p>\n          <p className=\"max-w-prose text-muted-foreground wrap-anywhere\">\n            Nothing has been metered since {Number.isFinite(derived.startEpoch) ? formatDay(f, derived.startEpoch) : \"this period started\"}.\n            {derived.limit !== null\n              ? ` The full ${formatAmount(f, spec, derived.limit)} allowance is still available.`\n              : \" This plan has no cap.\"}\n          </p>\n          {resetText && <p className=\"text-xs text-muted-foreground\">{resetText}.</p>}\n        </div>\n      )}\n\n      {effectiveStatus === \"error\" && (\n        <div className=\"flex flex-col items-center gap-3 px-6 py-12 text-center\">\n          <ServerCrash aria-hidden=\"true\" className=\"size-8 text-destructive\" />\n          <div className=\"flex flex-col gap-1\">\n            <p className=\"font-medium\">Couldn&apos;t load usage</p>\n            <p className=\"text-muted-foreground\">The metering service didn&apos;t respond.</p>\n          </div>\n          {onRetry && (\n            <button\n              className=\"cursor-pointer rounded-md border px-3 py-1.5 transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n              onClick={onRetry}\n              type=\"button\"\n            >\n              Try again\n            </button>\n          )}\n        </div>\n      )}\n\n      {effectiveStatus === \"ready\" && (\n        <div\n          // aria-busy + a visible tint mark the whole data region as superseded\n          // while the next period loads. The figures stay readable on purpose:\n          // blanking them would throw away the context the reader is comparing\n          // against, and the strip above already says which period they are.\n          aria-busy={stale || undefined}\n          className={cn(\"flex flex-col gap-5 transition-opacity motion-reduce:transition-none\", stale && \"opacity-60\")}\n          data-stale={stale || undefined}\n        >\n          <Overview derived={derived} f={f} meterLabel={meterLabel} metrics={metrics} previousUsed={previousUsed} spec={spec} />\n          <QuotaPanel\n            criticalAt={criticalAt}\n            derived={derived}\n            f={f}\n            onUpgrade={onUpgrade}\n            spec={spec}\n            upgrade={upgrade}\n            warnAt={warnAt}\n          />\n          <Trend derived={derived} f={f} meterLabel={meterLabel} spec={spec} />\n          <Breakdown\n            derived={derived}\n            expanded={expanded}\n            f={f}\n            label={breakdownLabel}\n            maxDimensions={maxDimensions}\n            onToggle={() => setExpanded(current => !current)}\n            spec={spec}\n          />\n        </div>\n      )}\n    </section>\n  )\n})\n\nUsageDashboard.displayName = \"UsageDashboard\"\n\n/* -------------------------------------------------------------------------- */\n\nfunction Overview({\n  derived,\n  f,\n  meterLabel,\n  metrics,\n  previousUsed,\n  spec,\n}: {\n  derived: Derived\n  f: Formatters\n  meterLabel: string\n  metrics: UsageMetric[] | undefined\n  previousUsed: number | undefined\n  spec: UnitSpec\n}) {\n  const usedText = formatAmount(f, spec, derived.used)\n  const usedExact = formatExact(f, spec, derived.used)\n  const projection = derived.projection\n\n  const remainingCard =\n    derived.limit === null\n      ? { label: \"Allowance\", sub: \"This plan has no cap\", value: \"Unlimited\" }\n      : derived.overBy > 0\n        ? {\n            label: \"Over the allowance\",\n            sub: `${formatAmount(f, spec, derived.limit)} included`,\n            value: formatAmount(f, spec, derived.overBy),\n          }\n        : {\n            label: \"Left this period\",\n            sub: `${formatAmount(f, spec, derived.limit)} included`,\n            value: formatAmount(f, spec, derived.remaining ?? 0),\n          }\n\n  return (\n    <div className=\"grid gap-3 grid-cols-[repeat(auto-fit,minmax(min(11rem,100%),1fr))]\">\n      <StatCard\n        exact={usedExact === usedText ? undefined : usedExact}\n        label={`${meterLabel} used so far`}\n        slot=\"usage-used\"\n        sub={\n          derived.ratio === null\n            ? \"no allowance configured\"\n            : `${formatPercent(f, derived.ratio)} of ${formatAmount(f, spec, derived.limit ?? 0)}`\n        }\n        value={usedText}\n      >\n        {previousUsed !== undefined && Number.isFinite(previousUsed) && (\n          <DeltaChip f={f} higherIsBetter={null} previous={previousUsed} value={derived.used} />\n        )}\n      </StatCard>\n\n      <StatCard label={remainingCard.label} sub={remainingCard.sub} value={remainingCard.value} />\n\n      <StatCard\n        label={`${meterLabel} projected by reset`}\n        slot=\"usage-projected\"\n        sub={\n          projection.kind === \"run-rate\"\n            ? projection.ratio === null\n              ? `at ${formatAmount(f, spec, projection.perDay)} / day`\n              : `${formatPercent(f, projection.ratio)} of the allowance at the current rate`\n            : projection.kind === \"closed\"\n              ? \"the period has closed — this is the final figure\"\n              : projection.kind === \"flat\"\n                ? \"nothing used yet, so there is no rate to project from\"\n                : \"the period hasn't started yet\"\n        }\n        value={\n          projection.kind === \"run-rate\"\n            ? formatAmount(f, spec, projection.total)\n            : projection.kind === \"closed\"\n              ? formatAmount(f, spec, derived.used)\n              : \"—\"\n        }\n      />\n\n      {(metrics ?? []).map(metric => (\n        <MetricCard f={f} key={metric.id} metric={metric} />\n      ))}\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n\ntype QuotaTone = \"ok\" | \"warn\" | \"critical\" | \"over\"\n\nconst TONE_CHIP: Record<QuotaTone, { Icon: typeof TriangleAlert; text: string; className: string }> = {\n  critical: { className: \"border-destructive/40 bg-destructive/10 text-destructive\", Icon: CircleAlert, text: \"Critical\" },\n  ok: { className: \"border-border bg-muted text-muted-foreground\", Icon: TrendingUp, text: \"On track\" },\n  over: { className: \"border-destructive/40 bg-destructive/10 text-destructive\", Icon: CircleAlert, text: \"Over the allowance\" },\n  warn: { className: \"border-border bg-muted text-foreground\", Icon: TriangleAlert, text: \"Near the limit\" },\n}\n\n/** The past-the-line slice: a lighter destructive wash cut by hard diagonals. */\nconst OVER_HATCH =\n  \"repeating-linear-gradient(45deg, color-mix(in oklab, var(--destructive) 70%, transparent) 0 4px, transparent 4px 8px)\"\n\n/**\n * \"Near the limit\" changes the fill's texture, not only its tint — thin, widely\n * spaced stripes, so it reads as a texture rather than as hazard tape.\n */\nconst WARN_HATCH =\n  \"repeating-linear-gradient(45deg, color-mix(in oklab, var(--card) 40%, transparent) 0 2px, transparent 2px 9px)\"\n\nfunction QuotaPanel({\n  criticalAt,\n  derived,\n  f,\n  onUpgrade,\n  spec,\n  upgrade,\n  warnAt,\n}: {\n  criticalAt: number\n  derived: Derived\n  f: Formatters\n  onUpgrade: (() => void) | undefined\n  spec: UnitSpec\n  upgrade: UsageDashboardData[\"upgrade\"]\n  warnAt: number\n}) {\n  const { limit, overBy, projection, ratio, used } = derived\n  const upgradeNode = <UpgradeAction onUpgrade={onUpgrade} upgrade={upgrade} />\n\n  if (limit === null || ratio === null) {\n    return (\n      <div className=\"flex flex-col gap-2 rounded-lg border border-dashed p-3\">\n        <p className=\"flex flex-wrap items-center gap-2 text-sm\">\n          <Gauge aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n          <span className=\"min-w-0 wrap-anywhere\">\n            No allowance configured — {formatAmount(f, spec, used)} metered this period, billed as it is used.\n          </span>\n        </p>\n        {upgradeNode}\n      </div>\n    )\n  }\n\n  const safeWarn = Number.isFinite(warnAt) ? warnAt : 0.75\n  const safeCritical = Number.isFinite(criticalAt) ? criticalAt : 0.9\n  const tone: QuotaTone = ratio > 1 ? \"over\" : ratio >= safeCritical ? \"critical\" : ratio >= safeWarn ? \"warn\" : \"ok\"\n  const chip = TONE_CHIP[tone]\n\n  // The track always spans max(used, limit): under the allowance the fill stops\n  // short, over it the fill still ends at the right edge and the limit marker\n  // moves left instead. Nothing is ever painted outside the track.\n  const scale = Math.max(used, limit, 1)\n  const fillPct = Math.min(100, (Math.min(used, limit) / scale) * 100)\n  const limitPct = (limit / scale) * 100\n  // Only drawn when it genuinely fits inside the track. Clamping it to 100%\n  // would park the marker exactly on the limit and quietly claim \"projected to\n  // land right on the allowance\", which is the opposite of what an over-run\n  // projection means; past the track, the sentence below carries the number.\n  const projectedPct =\n    projection.kind === \"run-rate\" && projection.total <= scale ? (projection.total / scale) * 100 : null\n\n  const usedPercentText = formatPercent(f, ratio)\n  const projectedPercentText = projection.kind === \"run-rate\" && projection.ratio !== null\n    ? formatPercent(f, projection.ratio)\n    : null\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1\">\n        <span className=\"flex flex-wrap items-center gap-2\">\n          <span\n            className={cn(\n              \"inline-flex shrink-0 items-center gap-1 rounded-sm border px-1.5 py-px text-xs font-medium\",\n              chip.className,\n            )}\n          >\n            <chip.Icon aria-hidden=\"true\" className=\"size-3.5\" />\n            {chip.text}\n          </span>\n          <span className=\"text-xs text-muted-foreground tabular-nums wrap-anywhere\">\n            {/* The percentage and the pair it is computed from sit side by side,\n                so the reader can check the division without leaving the row. */}\n            <span className=\"font-medium text-foreground\" data-slot=\"usage-percent\">\n              {usedPercentText}\n            </span>{\" \"}\n            used — {formatAmount(f, spec, used)} of {formatAmount(f, spec, limit)}\n          </span>\n        </span>\n        <span className=\"text-xs text-muted-foreground tabular-nums\">\n          {overBy > 0\n            ? `${formatAmount(f, spec, overBy)} over`\n            : `${formatAmount(f, spec, derived.remaining ?? 0)} left`}\n        </span>\n      </div>\n\n      <div\n        aria-label=\"Allowance used\"\n        aria-valuemax={100}\n        aria-valuemin={0}\n        // aria-valuenow above the max is invalid, so the real over-quota figure\n        // travels in aria-valuetext instead of being silently clipped away.\n        aria-valuenow={Math.min(100, Math.round(ratio * 100))}\n        aria-valuetext={`${usedPercentText} of the allowance used — ${formatExact(f, spec, used)} of ${formatExact(f, spec, limit)}${\n          projectedPercentText ? `. Projected ${projectedPercentText} by reset at the current rate.` : \"\"\n        }`}\n        className=\"relative h-3 w-full overflow-hidden rounded-full bg-muted\"\n        role=\"progressbar\"\n      >\n        <span\n          className={cn(\n            \"absolute inset-y-0 left-0 transition-[width] duration-500 ease-out motion-reduce:transition-none\",\n            tone === \"critical\" || tone === \"over\" ? \"bg-destructive\" : \"bg-primary\",\n          )}\n          style={{ backgroundImage: tone === \"warn\" ? WARN_HATCH : undefined, width: `${fillPct}%` }}\n        />\n        {overBy > 0 && (\n          <>\n            <span\n              className=\"absolute inset-y-0 right-0 bg-destructive/25\"\n              style={{ backgroundImage: OVER_HATCH, left: `${limitPct}%` }}\n            />\n            <span className=\"absolute inset-y-0 w-0.5 -translate-x-1/2 bg-card\" style={{ left: `${limitPct}%` }} />\n          </>\n        )}\n        {projectedPct !== null && projectedPct > fillPct && (\n          // The projection is drawn as an outline, never as fill: a solid segment\n          // would read as \"already used\", which is the one thing it is not.\n          <span\n            aria-hidden=\"true\"\n            className=\"absolute inset-y-0 w-0.5 -translate-x-1/2 border-l-2 border-dashed border-foreground/70\"\n            style={{ left: `${projectedPct}%` }}\n            title={`Projected ${projectedPercentText ?? \"\"}`}\n          />\n        )}\n      </div>\n\n      <QuotaMessage derived={derived} f={f} spec={spec} tone={tone} />\n      {upgradeNode}\n    </div>\n  )\n}\n\nfunction QuotaMessage({\n  derived,\n  f,\n  spec,\n  tone,\n}: {\n  derived: Derived\n  f: Formatters\n  spec: UnitSpec\n  tone: QuotaTone\n}) {\n  const { elapsedRatio, limit, overBy, projection, ratio } = derived\n  if (ratio === null || limit === null) return null\n\n  const elapsedText = elapsedRatio === null ? null : formatPercent(f, elapsedRatio)\n  const lines: React.ReactNode[] = []\n\n  // Two numbers that are constantly confused for each other, so they are stated\n  // as two sentences with two different verbs: what HAS been used, and what the\n  // period WILL end on if nothing changes.\n  lines.push(\n    <span key=\"used\">\n      <span className=\"font-medium text-foreground\">{formatPercent(f, ratio)} used</span> so far\n      {elapsedText ? ` — with ${elapsedText} of the period elapsed` : \"\"}.\n    </span>,\n  )\n\n  if (projection.kind === \"run-rate\" && projection.ratio !== null) {\n    lines.push(\n      <span key=\"projected\">\n        {\" \"}\n        At the current rate ({formatAmount(f, spec, projection.perDay)} / day) this period is{\" \"}\n        <span className=\"font-medium text-foreground\">projected to reach {formatPercent(f, projection.ratio)}</span> (\n        {formatAmount(f, spec, projection.total)}) by reset\n        {projection.exhaustAt !== null\n          ? ` — the allowance runs out around ${formatDay(f, projection.exhaustAt)}`\n          : \"\"}\n        .\n      </span>,\n    )\n  } else if (projection.kind === \"closed\") {\n    lines.push(<span key=\"closed\"> The period has closed, so this is the final figure.</span>)\n  } else if (projection.kind === \"flat\") {\n    lines.push(<span key=\"flat\"> Nothing has been used yet, so there is no rate to project from.</span>)\n  }\n\n  if (overBy > 0) {\n    lines.push(\n      <span key=\"over\">\n        {\" \"}\n        Everything past the line ({formatAmount(f, spec, overBy)}) is billed on top of the plan.\n      </span>,\n    )\n  }\n\n  return (\n    <p\n      className={cn(\n        \"rounded-md px-2.5 py-2 text-xs\",\n        tone === \"over\" || tone === \"critical\" ? \"bg-destructive/10 text-destructive\" : \"bg-muted text-muted-foreground\",\n      )}\n    >\n      {lines}\n    </p>\n  )\n}\n\nfunction UpgradeAction({\n  onUpgrade,\n  upgrade,\n}: {\n  onUpgrade: (() => void) | undefined\n  upgrade: UsageDashboardData[\"upgrade\"]\n}) {\n  if (!upgrade) return null\n  const className =\n    \"inline-flex w-fit cursor-pointer items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n  return (\n    <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n      {onUpgrade ? (\n        <button className={className} onClick={onUpgrade} type=\"button\">\n          {upgrade.label}\n          <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n        </button>\n      ) : (\n        <a className={className} href={upgrade.href}>\n          {upgrade.label}\n          <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n        </a>\n      )}\n      {upgrade.note && <span className=\"min-w-0 text-xs text-muted-foreground wrap-anywhere\">{upgrade.note}</span>}\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n\nfunction Trend({\n  derived,\n  f,\n  meterLabel,\n  spec,\n}: {\n  derived: Derived\n  f: Formatters\n  meterLabel: string\n  spec: UnitSpec\n}) {\n  const { domainMax, endEpoch, limit, points, projection, startEpoch, ticks, used } = derived\n  if (points.length < 2) return null\n\n  const config = {\n    actual: { color: \"var(--chart-1)\", label: `${meterLabel} used` },\n    projected: { color: \"var(--chart-1)\", label: \"Projected\" },\n  } satisfies ChartConfig\n\n  const summary =\n    `Cumulative ${meterLabel.toLowerCase()} from ${formatDay(f, startEpoch)} to ${formatDay(f, endEpoch)}, ` +\n    `ending at ${formatExact(f, spec, used)}` +\n    (limit !== null ? ` against an allowance of ${formatExact(f, spec, limit)}` : \"\") +\n    (projection.kind === \"run-rate\" ? `, projected to reach ${formatExact(f, spec, projection.total)} by reset` : \"\") +\n    \".\"\n\n  const rows = points.filter(point => point.actual !== undefined)\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1\">\n        <span className=\"text-xs font-medium\">Cumulative this period</span>\n        <span aria-hidden=\"true\" className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n          <span className=\"flex items-center gap-1.5\">\n            <svg className=\"h-2 w-6 shrink-0\" viewBox=\"0 0 24 8\">\n              <line stroke=\"var(--chart-1)\" strokeWidth=\"2.5\" x1=\"0\" x2=\"24\" y1=\"4\" y2=\"4\" />\n            </svg>\n            used\n          </span>\n          {projection.kind === \"run-rate\" && (\n            <span className=\"flex items-center gap-1.5\">\n              <svg className=\"h-2 w-6 shrink-0\" viewBox=\"0 0 24 8\">\n                <line\n                  stroke=\"var(--chart-1)\"\n                  strokeDasharray=\"5 4\"\n                  strokeWidth=\"2.5\"\n                  x1=\"0\"\n                  x2=\"24\"\n                  y1=\"4\"\n                  y2=\"4\"\n                />\n              </svg>\n              projected\n            </span>\n          )}\n          {limit !== null && (\n            <span className=\"flex items-center gap-1.5\">\n              <svg className=\"h-2 w-6 shrink-0\" viewBox=\"0 0 24 8\">\n                <line\n                  stroke=\"var(--muted-foreground)\"\n                  strokeDasharray=\"3 3\"\n                  strokeWidth=\"1.5\"\n                  x1=\"0\"\n                  x2=\"24\"\n                  y1=\"4\"\n                  y2=\"4\"\n                />\n              </svg>\n              allowance\n            </span>\n          )}\n        </span>\n      </div>\n\n      {/* role=\"img\" + a summary for the shape; the exact figures live in the\n          sr-only table below, which is wrapped in a div — `sr-only` on a <table>\n          only sets a width *floor* and pushes the page into horizontal scroll. */}\n      <div aria-label={summary} role=\"img\">\n        <ChartContainer className=\"aspect-auto h-56 w-full\" config={config}>\n          <LineChart\n            // recharts 3 turns accessibilityLayer on by default, which adds a\n            // tabbable svg with no accessible name inside a role=\"img\" subtree.\n            accessibilityLayer={false}\n            data={points}\n            margin={{ bottom: 0, left: 4, right: 12, top: 12 }}\n          >\n            <CartesianGrid horizontal strokeDasharray=\"3 3\" vertical={false} />\n            <XAxis\n              axisLine={false}\n              dataKey=\"t\"\n              domain={[startEpoch, endEpoch]}\n              minTickGap={28}\n              scale=\"time\"\n              tickFormatter={value => (f.dayShort ? f.dayShort.format(value) : String(value))}\n              tickLine={false}\n              tickMargin={8}\n              type=\"number\"\n            />\n            <YAxis\n              axisLine={false}\n              domain={[0, domainMax]}\n              tickFormatter={value => formatAmount(f, { ...spec, unitLabel: undefined }, value)}\n              tickLine={false}\n              tickMargin={4}\n              ticks={ticks}\n              // Measured, not estimated. A fixed width makes recharts' Text wrap\n              // a two-word tick (\"466 GiB\") onto two lines; \"auto\" lets the\n              // browser size the gutter to whatever the formatter actually\n              // produced, in any unit and any locale.\n              width=\"auto\"\n            />\n            {limit !== null && (\n              <ReferenceLine\n                label={{\n                  className: \"fill-muted-foreground text-[10px]\",\n                  position: \"insideTopLeft\",\n                  value: \"Allowance\",\n                }}\n                stroke=\"var(--muted-foreground)\"\n                strokeDasharray=\"3 3\"\n                y={limit}\n              />\n            )}\n            <ChartTooltip\n              content={\n                <ChartTooltipContent\n                  formatter={(value, name, item) => (\n                    <span className=\"flex w-full flex-wrap items-baseline justify-between gap-2\">\n                      <span className=\"text-muted-foreground\">{name === \"projected\" ? \"Projected\" : \"Used\"}</span>\n                      <span className=\"font-medium tabular-nums\">\n                        {formatAmount(f, spec, Number(value))}\n                        {name === \"actual\" && typeof item?.payload?.delta === \"number\" && item.payload.delta > 0\n                          ? ` (+${formatAmount(f, spec, item.payload.delta)})`\n                          : \"\"}\n                      </span>\n                    </span>\n                  )}\n                  labelFormatter={(_label, payload) => {\n                    const first = Array.isArray(payload) ? payload[0] : undefined\n                    const at = typeof first?.payload?.t === \"number\" ? first.payload.t : startEpoch\n                    return formatDay(f, at)\n                  }}\n                />\n              }\n            />\n            <Line\n              connectNulls={false}\n              dataKey=\"actual\"\n              dot={false}\n              isAnimationActive={false}\n              stroke=\"var(--color-actual)\"\n              strokeWidth={2}\n              type=\"monotone\"\n            />\n            <Line\n              connectNulls={false}\n              dataKey=\"projected\"\n              dot={false}\n              isAnimationActive={false}\n              stroke=\"var(--color-projected)\"\n              // Dashed, not merely a different hue: the palette can never be the\n              // only channel that separates \"measured\" from \"extrapolated\".\n              strokeDasharray=\"5 4\"\n              strokeWidth={2}\n              type=\"linear\"\n            />\n          </LineChart>\n        </ChartContainer>\n      </div>\n\n      {/* `sr-only` lives on a wrapping div, never on the <table>: CSS width is\n          only a *lower* bound on a table box, so a hidden table happily pushes a\n          375px viewport into horizontal scroll. */}\n      <div className=\"sr-only\" data-slot=\"usage-trend-table\">\n        <table>\n          <caption>{summary}</caption>\n          <thead>\n            <tr>\n              {/* Each row is stated \"as of\" the END of its bucket, which is where\n                  the curve plots it — so the last row's cumulative is the headline. */}\n              <th scope=\"col\">As of</th>\n              <th scope=\"col\">Used in the preceding bucket</th>\n              <th scope=\"col\">Cumulative</th>\n            </tr>\n          </thead>\n          <tbody>\n            {rows.map(point => (\n              <tr key={point.t}>\n                <th scope=\"row\">{formatDay(f, point.t)}</th>\n                <td>{formatExact(f, spec, point.delta ?? 0)}</td>\n                <td>{formatExact(f, spec, point.actual ?? 0)}</td>\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n    </div>\n  )\n}\n\n/* -------------------------------------------------------------------------- */\n\n/** Rank shades. Every row is directly labelled, so colour is never load-bearing. */\nconst RANK_COLOR = [\"var(--chart-1)\", \"var(--chart-2)\", \"var(--chart-3)\", \"var(--chart-4)\", \"var(--chart-5)\"] as const\n\nfunction Breakdown({\n  derived,\n  expanded,\n  f,\n  label,\n  maxDimensions,\n  onToggle,\n  spec,\n}: {\n  derived: Derived\n  expanded: boolean\n  f: Formatters\n  label: string\n  maxDimensions: number\n  onToggle: () => void\n  spec: UnitSpec\n}) {\n  const { allRows, used } = derived\n  if (allRows.length === 0) return null\n\n  const cap = Math.max(1, Math.trunc(Number.isFinite(maxDimensions) ? maxDimensions : 5))\n  const collapse = !expanded && allRows.length > cap\n\n  // When it collapses, cap - 1 real rows are kept and everything else merges into\n  // one row that names how many it merged. The merged value is the exact\n  // remainder, so \"shown rows + Other\" is still the total to the last unit.\n  const shown = collapse ? allRows.slice(0, cap - 1) : allRows\n  const merged = collapse ? allRows.slice(cap - 1) : []\n  const otherValue = merged.reduce((sum, row) => sum + row.value, 0)\n  const rows: BreakdownRow[] = collapse\n    ? [\n        ...shown,\n        {\n          key: \"__other__\",\n          label: \"Other\",\n          merged: merged.length,\n          share: used > 0 ? otherValue / used : 0,\n          value: otherValue,\n        },\n      ]\n    : shown\n\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1\">\n        <span className=\"text-xs font-medium wrap-anywhere\">{label}</span>\n        <span className=\"text-xs text-muted-foreground tabular-nums\">\n          {allRows.length} {allRows.length === 1 ? \"dimension\" : \"dimensions\"} with usage\n        </span>\n      </div>\n\n      {/* Composition bar: widths come straight off the raw shares, so the\n          segments fill the track exactly even though the printed percentages\n          are rounded independently. */}\n      <div aria-hidden=\"true\" className=\"flex h-2 w-full overflow-hidden rounded-full bg-muted\">\n        {rows.map((row, index) => (\n          <span\n            className={cn(\"h-full\", index > 0 && \"border-l border-card\")}\n            key={row.key}\n            style={{ background: RANK_COLOR[Math.min(index, RANK_COLOR.length - 1)], width: `${row.share * 100}%` }}\n            title={`${row.label}: ${formatExact(f, spec, row.value)}`}\n          />\n        ))}\n      </div>\n\n      <ul className=\"divide-y\">\n        {rows.map((row, index) => (\n          <li className=\"flex flex-wrap items-baseline gap-x-2 gap-y-0.5 py-1.5\" key={row.key}>\n            <span\n              aria-hidden=\"true\"\n              className=\"size-2.5 shrink-0 translate-y-px rounded-sm\"\n              style={{ background: RANK_COLOR[Math.min(index, RANK_COLOR.length - 1)] }}\n            />\n            <span className=\"min-w-0 flex-1 font-medium wrap-anywhere\">\n              {row.label}\n              {row.merged > 1 && (\n                <span className=\"font-normal text-muted-foreground\"> · {row.merged} smaller dimensions merged</span>\n              )}\n              {row.merged === 1 && row.detail && (\n                <span className=\"font-normal text-muted-foreground\"> · {row.detail}</span>\n              )}\n            </span>\n            <span\n              className=\"shrink-0 tabular-nums\"\n              data-merged={row.merged}\n              data-slot=\"usage-row-value\"\n              title={formatExact(f, spec, row.value)}\n            >\n              {formatAmount(f, spec, row.value)}\n            </span>\n            <span className=\"w-14 shrink-0 text-right text-xs tabular-nums text-muted-foreground\">\n              {formatPercent(f, row.share)}\n            </span>\n          </li>\n        ))}\n      </ul>\n\n      <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-2 border-t pt-2\">\n        {/* The reconciliation line, on screen rather than in a tooltip: the rows\n            above add up to exactly this, and this is the headline figure. */}\n        <span className=\"text-xs text-muted-foreground tabular-nums wrap-anywhere\">\n          {collapse ? `${rows.length - 1} shown + Other` : `${rows.length} ${rows.length === 1 ? \"row\" : \"rows\"}`} ={\" \"}\n          <span className=\"font-medium text-foreground\" data-slot=\"usage-total\" title={formatExact(f, spec, used)}>\n            {formatAmount(f, spec, used)}\n          </span>{\" \"}\n          total\n        </span>\n        {allRows.length > cap && (\n          <button\n            aria-expanded={expanded}\n            className=\"inline-flex cursor-pointer items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n            onClick={onToggle}\n            type=\"button\"\n          >\n            <ChevronDown\n              aria-hidden=\"true\"\n              className={cn(\"size-3.5 transition-transform motion-reduce:transition-none\", expanded && \"rotate-180\")}\n            />\n            {expanded ? `Show top ${cap - 1}` : `Show all ${allRows.length}`}\n          </button>\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport default UsageDashboard\n",
      "type": "registry:block"
    },
    {
      "path": "src/registry/blocks/usage-dashboard.contract.ts",
      "content": "import { z } from \"zod\"\n\n/**\n * Which formatter the metered quantity runs through. A request count, a byte\n * volume, a build-minute budget and a spend cap have nothing in common at\n * display time, and sharing one \"format a number\" helper is exactly how a usage\n * panel starts printing 1023 B as \"1 KiB\" or 90 minutes as \"90\".\n *\n * - `count` — plain items (requests, events, seats); name them with `unitLabel`.\n * - `bytes` — binary ladder (B, KiB, MiB, GiB, TiB, PiB), base 1024.\n * - `seconds` — a duration, rendered \"45s\" / \"29m\" / \"2h 29m\" / \"3d 4h\".\n * - `currency` — money in major units, needs `currencyCode`.\n */\nexport const usageUnitSchema = z.enum([\"count\", \"bytes\", \"seconds\", \"currency\"])\n\n/**\n * A billing period as two **absolute instants** plus its own id and label.\n *\n * The label travels with the period on purpose: the dashboard prints the label\n * of the period **the numbers on screen came from**, never the one the user just\n * clicked. That is what makes \"new period's heading over last period's figures\"\n * impossible to render rather than merely unlikely.\n */\nexport const usagePeriodSchema = z.object({\n  id: z.string().min(1),\n  /** Human name — \"February 2026\", \"Current cycle\". */\n  label: z.string().min(1),\n  /** ISO 8601 with an offset, e.g. \"2026-02-01T00:00:00.000Z\". */\n  start: z.iso.datetime({ offset: true }),\n  /** The reset instant. Everything starts from zero again here. */\n  end: z.iso.datetime({ offset: true }),\n})\n\n/** One axis of the breakdown — an endpoint, a project, a model, a region. */\nexport const usageDimensionSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  /** One short line of context under the name — plan, region, owner. */\n  detail: z.string().optional(),\n})\n\n/**\n * One time bucket (an hour, a day, a week — the dashboard infers the step from\n * the spacing) holding the usage attributed to each dimension inside it.\n *\n * This array is the **single source of truth** for the whole block: the headline\n * figure, the quota percentage, the trend curve and the per-dimension breakdown\n * are all derived from it. There is deliberately no separate `total` or\n * `breakdown` field to pass alongside it — three independently supplied numbers\n * are three numbers that can disagree, and a buyer reconciling a bill will find\n * the disagreement.\n */\nexport const usageBucketSchema = z.object({\n  /** ISO 8601 with an offset — the instant this bucket starts. */\n  start: z.iso.datetime({ offset: true }),\n  /** Usage per dimension id. A dimension missing from a bucket counts as zero. */\n  values: z.record(z.string(), z.number().nonnegative()),\n})\n\n/**\n * A supplementary figure beside the metered quantity — spend, error count,\n * seats. These are *not* reconciled against the buckets; they are their own\n * measures, so keep them out of the quota arithmetic.\n */\nexport const usageMetricSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  value: z.number(),\n  /** Same measure over the previous comparable period. Omit it and no delta is invented. */\n  previous: z.number().optional(),\n  unit: usageUnitSchema.optional(),\n  unitLabel: z.string().optional(),\n  currencyCode: z.string().length(3).optional(),\n  /** Polarity, not direction: `false` for error counts and spend. Defaults to true. */\n  higherIsBetter: z.boolean().optional(),\n  hint: z.string().optional(),\n})\n\n/** `\"\"` and `\"#\"` are the two hrefs that render a control which cannot act. */\nconst DEAD_HREFS = new Set([\"\", \"#\"])\n\n/** The plan-change entry point. Present = render it, absent = render nothing. */\nexport const usageUpgradeSchema = z.object({\n  label: z.string().min(1),\n  /**\n   * A real destination. `\"\"` and `\"#\"` are rejected by the schema: a dead link\n   * styled as the primary action is the defect this field exists to prevent.\n   * Pass `onUpgrade` instead when the action is in-app.\n   */\n  href: z.string().refine(value => !DEAD_HREFS.has(value.trim()), {\n    message: 'upgrade.href must be a real destination, not \"\" or \"#\"',\n  }),\n  /** One line under the button — \"Pro includes 5M requests / month\". */\n  note: z.string().optional(),\n})\n\n/** The block's own render state, independent of how much of the quota is left. */\nexport const usageDashboardStatusSchema = z.enum([\"loading\", \"empty\", \"error\", \"ready\"])\n\nexport const usageDashboardSchema = z\n  .object({\n    status: usageDashboardStatusSchema,\n    /** The period **these numbers describe**. Required in every state, including loading. */\n    period: usagePeriodSchema,\n    /** Selectable periods. Fewer than two — or no `onPeriodChange` — hides the switcher. */\n    periods: z.array(usagePeriodSchema).optional(),\n    /**\n     * The period the **user** has selected. While it differs from `period.id`\n     * the payload on screen is stale: the block keeps the old figures under the\n     * old label and says so, instead of dressing them in the new period's name.\n     */\n    activePeriodId: z.string().optional(),\n    /** What is being metered — \"API requests\", \"Egress\", \"Build minutes\". */\n    meterLabel: z.string().min(1),\n    unit: usageUnitSchema,\n    /** Noun after a `count` figure (\"requests\", \"events\"). Ignored by other units. */\n    unitLabel: z.string().optional(),\n    /** ISO 4217 code — required by `currency`, ignored by every other unit. */\n    currencyCode: z.string().length(3).optional(),\n    /**\n     * The allowance included in this period, in the same base as the bucket\n     * values. `null` means **unlimited** and is a first-class case: no bar, no\n     * percentage, no projection against a ceiling that does not exist.\n     */\n    limit: z.number().positive().nullable(),\n    /** The same total over the previous period, for the headline delta. Omit and none is shown. */\n    previousUsed: z.number().nonnegative().optional(),\n    dimensions: z.array(usageDimensionSchema),\n    buckets: z.array(usageBucketSchema),\n    /** Section heading for the breakdown — \"By endpoint\", \"By project\". */\n    breakdownLabel: z.string().optional(),\n    metrics: z.array(usageMetricSchema).optional(),\n    upgrade: usageUpgradeSchema.optional(),\n  })\n  // Each check guards its own inputs. zod runs every refinement even after an\n  // earlier one has failed, so a refinement that dereferences a field another\n  // refinement just rejected throws a TypeError out of `safeParse` instead of\n  // returning `{ success: false }`.\n  .superRefine((data, ctx) => {\n    const period = data?.period\n    if (!period || typeof period.start !== \"string\" || typeof period.end !== \"string\") return\n    const start = Date.parse(period.start)\n    const end = Date.parse(period.end)\n    if (!Number.isFinite(start) || !Number.isFinite(end)) return\n    if (end <= start) {\n      ctx.addIssue({ code: \"custom\", message: \"period.end must be after period.start\", path: [\"period\", \"end\"] })\n    }\n  })\n  .superRefine((data, ctx) => {\n    const dimensions = Array.isArray(data?.dimensions) ? data.dimensions : []\n    const known = new Set<string>()\n    for (const dimension of dimensions) {\n      if (dimension && typeof dimension.id === \"string\") known.add(dimension.id)\n    }\n    const buckets = Array.isArray(data?.buckets) ? data.buckets : []\n    buckets.forEach((bucket, index) => {\n      const values = bucket && typeof bucket === \"object\" ? bucket.values : undefined\n      if (!values || typeof values !== \"object\") return\n      for (const key of Object.keys(values)) {\n        if (known.has(key)) continue\n        ctx.addIssue({\n          code: \"custom\",\n          message: `bucket value \"${key}\" has no matching entry in dimensions`,\n          path: [\"buckets\", index, \"values\", key],\n        })\n      }\n    })\n  })\n  .superRefine((data, ctx) => {\n    const periods = Array.isArray(data?.periods) ? data.periods : []\n    if (periods.length === 0) return\n    const activeId = data?.activePeriodId\n    if (typeof activeId !== \"string\") return\n    if (periods.some(period => period && period.id === activeId)) return\n    ctx.addIssue({\n      code: \"custom\",\n      message: \"activePeriodId must be one of periods[].id\",\n      path: [\"activePeriodId\"],\n    })\n  })\n\nexport type UsageUnit = z.infer<typeof usageUnitSchema>\nexport type UsagePeriod = z.infer<typeof usagePeriodSchema>\nexport type UsageDimension = z.infer<typeof usageDimensionSchema>\nexport type UsageBucket = z.infer<typeof usageBucketSchema>\nexport type UsageMetric = z.infer<typeof usageMetricSchema>\nexport type UsageUpgrade = z.infer<typeof usageUpgradeSchema>\nexport type UsageDashboardStatus = z.infer<typeof usageDashboardStatusSchema>\nexport type UsageDashboardData = z.infer<typeof usageDashboardSchema>\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}