feat: D82 — dashboard UI Claude.ai-style (Phase 5) (#54)

* feat: D82 — dashboard UI Claude.ai-style per-provider rows (Phase 5)

Implements ADR 0012 D82: restructures dashboard.html to render
quota_v2 data (produced by D81 / PR #53) in a Claude.ai-style
per-provider row layout. Closes v1.x roadmap #8.

## What changed

### dashboard.html (A–G)

A. New "Plan Usage" section at top (full-width, above the 2-col grid):
   - Per-provider rows rendered from `data.quota_v2`
   - Each row: provider badge (colored chip), status dot + chip
     (live/stale/unavailable), schema version tag
   - Two utilization bars (5h + 7d) with:
     - Rounded gradient bar (green <50% / amber 50-80% / red >80%)
     - Label "Current 5-hour session: 49%" / "Weekly all-models: 31%"
     - Right-side reset countdown (see B)
   - Bottom chips: representative-claim badge (purple), overage chip
     (amber/green), fallback-percentage chip, last-fresh "Updated N
     min ago" tag; stale rows show amber ⚠ stale data chip with tooltip
   - Unavailable rows: provider badge + reason text only; no bars

B. formatResetCountdown(epochSeconds):
   - < 1 hour:   "Resets in 23 min"
   - 1–24 hours: "Resets in 12hr 30min"
   - < 7 days:   "Resets Sun 9:00 PM"  (weekday + 12h time)
   - >= 7 days:  "Resets May 31 9:00 PM" (month day + time)
   - past:       "Resetting now…"
   Uses toLocaleString('en-US', { hour12: true }).

C. 60s auto-refresh (quota_v2 only) with visibilityState guard:
   - Separate timer (quotaRefreshTimer); does NOT replace the 30s poll
   - Pauses on 'hidden'; resumes + immediate re-fetch on 'visible'
   - Other 3 panels (24h, 30d, top fallback) keep 30s cadence unchanged

D. Manual refresh button (↻ Refresh) in Plan Usage header:
   - 2-second spam guard (button disables post-click)
   - Spinning ⟳ icon during fetch
   - Re-enables after fetch completes (success or error)

E. Graceful quota_v2 / legacy quota fallback:
   - If data.quota_v2 is present and non-empty → render Plan Usage rows;
     hide legacy "Quota (per provider)" panel
   - If data.quota_v2 is absent/empty → show note in Plan Usage area;
     surface legacy data.quota in the original table panel
   - Guards operator running an older OLP build (pre-D81)

F. Visual polish: rounded bars, gradient fills, airy whitespace, mobile-
   responsive (bars reflow on narrow viewports via flex-wrap). Color
   palette: #10b981 (green), #f59e0b (amber), #ef4444 (red) matching
   Tailwind emerald/amber/red-500 per spec.

G. Other 3 panels (24h, 30d, top fallback) and their 30s poll cadence
   are IDENTICAL to D51. Only the Quota panel restructures.

### docs/v1x-roadmap.md (H)

Marks entry #8 as " CLOSED (D82, v0.5.0)". Adds closure status,
PR ref, and a brief note inside the entry body. Updates reading-order
header paragraph to include #8 in the closed list.

## Authority + citations

- ADR 0012 D82 — Claude.ai-style restructure D-day spec
  (docs/adr/0012-phase-5-charter-quota-probes-dashboard.md § D-day table)
- D81 PR #53 — quota_v2 shape producer (commit 5288493);
  ProviderQuotaEntry shape per ADR 0008 Amendment 1 § 3
- Maintainer reference 2026-05-26 — claude.ai/settings/usage screenshot;
  "Resets in 1hr 6min" / "Resets Sun 9:00 PM" string format
- v1.x roadmap #8 — closed by this commit
  (docs/v1x-roadmap.md #8 — Dashboard enrichment)

## Test impact

npm test: 727 pass / 0 fail (unchanged). dashboard.html is frontend-only;
D83 ships Suite 38/39 (probe unit tests + dashboard smoke tests).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dashboard): D82 reviewer Nit #4 — gate overage chip on real status

When entry.overage = { status: null, disabled_reason: null } (audit-query's
default shape when the provider doesn't supply overage info), the chip rendered
as amber "Overage: —" which falsely suggests a warning state.

Now: chip only renders when entry.overage.status is truthy (i.e., Anthropic
actually returned an overage-status header). Truly-missing overage info shows
no chip at all, matching the maintainer's intent.

Identified by D82 fresh-context reviewer (PR #54 thread) as the only
maintainer-visible nit worth folding in pre-merge. 1-line change. All 727
tests continue to pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-05-26 17:20:49 +10:00
committed by GitHub
co-authored by taodeng Claude Sonnet 4.6
parent 5288493f19
commit a41420d0fc
2 changed files with 551 additions and 26 deletions
+3 -2
View File
@@ -8,7 +8,7 @@
3. **Where** does the work live in the tree today (file + anchor).
4. **When** does it need to land (trigger: load profile, security event, governance amendment).
**Reading order for a v1.x sprint kickoff.** As of 2026-05-25, #1 (streaming SF, D57+D58) and #2 (multi-key auth, Phase 2) are CLOSED, and #4 and #7 closed in D56. Remaining v1.x scope: #3 (soft trigger reactivation), #5 (provider cacheKeyFields mask), #6 (streaming SPAWN_FAILED salvage — unbundled from #1 at #1 close). All three remaining items have explicit "trigger to start" gates that have not fired.
**Reading order for a v1.x sprint kickoff.** As of 2026-05-26, #1 (streaming SF, D57+D58), #2 (multi-key auth, Phase 2), #4 and #7 (closed in D56), and #8 (dashboard enrichment, D82 Phase 5) are CLOSED. Remaining v1.x scope: #3 (soft trigger reactivation), #5 (provider cacheKeyFields mask), #6 (streaming SPAWN_FAILED salvage — unbundled from #1 at #1 close). All three remaining items have explicit "trigger to start" gates that have not fired.
---
@@ -88,8 +88,9 @@
- **Tracking.** Not a GitHub issue. Tracked here.
- **Trigger to start.** First report of streaming-path SPAWN_FAILED mid-stream where partial-chunk salvage would have helped a downstream caller. Practically unlikely at family scale.
## #8 — Dashboard enrichment: per-provider subscription quota + reset times + 1-min refresh + manual refresh (D78 follow-up)
## #8 — Dashboard enrichment: per-provider subscription quota + reset times + 1-min refresh + manual refresh (D78 follow-up) — ✅ **CLOSED (D82, v0.5.0)**
- **Status.** Closed at D82 (Phase 5). `dashboard.html` restructured to Claude.ai-style per-provider rows rendering `quota_v2`. Closed by PR on branch `d82-dashboard-ui-claude-ai-style`; ships with v0.5.0. 60s quota auto-refresh + manual refresh button + visibilityState guard implemented. Graceful fallback to legacy `quota` field when server runs a pre-D81 build.
- **What.** Phase 3 dashboard (D51 `dashboard.html`, v0.3.0) shows: per-provider quota (currently always "n/a — no quota api"), last-24h request count + cache hit + fallback rate, 30d request-count sparkline, top fallback chains. **Maintainer request 2026-05-26 post-D78**: extend to show what each enabled provider's subscription is actually consuming, with reset times visible, refresh once per minute (current 30s is OK but maintainer specified 1min target), and a manual refresh button. Reference design: Claude.ai's own `claude.ai/settings/usage` page — current session bar with "Resets in 1hr 6min", weekly all-models bar with "Resets Sun 9:00 PM", per-model bar (Sonnet only), additional features (routine runs), usage credits + monthly spend limit + auto-reload toggle.
- **Why deferred.** v0.3.0/v0.4.x ships the dashboard frame but `provider.quotaStatus()` returns `null` in all three v0.1 plugins (anthropic / openai / mistral). The ratifying spec in ADR 0004 Amendment 2 punts `quotaStatus()` to v1.x ("soft trigger reactivation") — this dashboard ask is the **operator-facing reason** that work would land.
- **What this requires.** Per-provider plugin work + dashboard.html UI work + audit-query.mjs aggregation: