feat+test+docs: D51 — dashboard.html full multi-panel UI (Phase 3) (#28)

* feat+test+docs: D51 — dashboard.html full multi-panel UI (Phase 3)

Fourth Phase 3 D-day. Replaces the D50 dashboard.html placeholder with
the full 4-panel UI per ADR 0008 § 6. Vanilla HTML + JS + fetch — no
build step, no framework, no CDN (Lane 1 = A). 30s page poll with
document.visibilityState pause/resume (Lane 4 = A).

4 PANELS (all rendered from /v0/management/dashboard-data — single
backing endpoint per Lane 2 in-memory query model):

  Panel 1 — Per-provider quota
    Table: { Provider | Available | Status }. Null available → "n/a"
    pill; provider.quotaStatus() error → red status pill (graceful
    degradation per ADR § 9).

  Panel 2 — Last 24h: request count + cache hit + fallback rate
    Per-provider row: { Requests | Cache hit % | Fallback rate % }.
    Cache hit from cache_hit_24h.by_provider[p].hit_rate; fallback
    rate computed from window_24h.by_provider[p].fallback_count/count.

  Panel 3 — Request count last 30 days (SVG sparkline)
    Vanilla SVG bar chart with <title> tooltips showing per-day per-
    provider breakdown. Y-axis: requests per day (max-scaled). X-axis:
    30 daily UTC buckets.

  Panel 4 — Top fallback chains (last 24h)
    Numbered table: { # | Chain (monospace, arrow-joined) | Count |
    First seen | Last seen }.

POLL + VISIBILITYCHANGE PAUSE (ADR 0008 § 6.5):

  - setInterval(refresh, 30000) after initial fetch.
  - document.addEventListener('visibilitychange') → stopPolling() on
    hidden / refresh()+startPolling() on visible.
  - Prevents 2880 background polls/day per owner when tab hidden.

ERROR HANDLING:

  - 401 from dashboard-data → in-page error banner explains owner-tier
    requirement + suggests SSH-tunnel + header-injection workaround.
  - Other HTTP errors → generic "HTTP <code>" banner; console.warn
    for operator debugging.
  - Per-panel empty states ("Loading…", "No requests in window.",
    "No fallback chains triggered.").

CRITICAL CORRECTNESS INVARIANTS (ADR 0008 § 6 + Lane 1 = A):

  - No <script src> — entire JS inline (Suite 25d asserts).
  - No <link rel="stylesheet" href> — all CSS in <style> (25d).
  - Only one backing endpoint hit: /v0/management/dashboard-data
    (Suite 25e asserts).
  - 401 path keeps panels in last-good state rather than clearing —
    operator sees the error banner + can debug.

TESTS — Suite 25, +6 (582 → 588):

  25a: owner /dashboard response contains all 4 panel container IDs
  25b: JS declares POLL_INTERVAL_MS = 30000 + setInterval/clearInterval
  25c: visibilitychange listener + document.visibilityState check
  25d: NO external script src / NO external stylesheet href (Lane 1 = A
    pinning)
  25e: dashboard JS fetches /v0/management/dashboard-data only
  25f: 401 in-page error banner mentions owner-tier guidance

MANUAL SMOKE (ADR 0008 § 10 #12):

  Dashboard renders without console errors in a real browser when
  served by a running OLP instance + owner-tier Bearer via SSH-tunnel
  + header-injection extension. Not automated at Phase 3.

DOCUMENTATION:

  - AGENTS.md: dashboard.html marker promoted 🟡 D50 placeholder → 
    D51 full UI.
  - CHANGELOG.md: D51 entry under Unreleased per release_kit overlay.

NOT IN D51:

  - Daily audit rotation (D52)
  - tried_providers schema fix (D53; D45 P2 deferral)
  - Phase 3 close (D55; v0.3.0; maintainer-triggered)

Test count: 582 → 588 (+6). Verified locally via npm test.

AUTHORITY:

  - ADR 0008 § 6 (panels + refresh + localhost) + § 6.5 (poll +
    visibilityState pause) + Lane 1 = A (no build step) + Lane 4 = A
    (30s poll) + Lane 5 = B (full 4-panel scope).
  - ADR 0008 § 9 (graceful degradation surfaced in Panel 1).
  - ADR 0008 § 10 #12 (HTML smoke criterion satisfied at server-side
    level).
  - CLAUDE.md release_kit overlay phase_rolling_mode — under
    Unreleased.
  - Standing autopilot grant.

ALIGNMENT.md scope check: this PR replaces an existing entry-surface
static file (dashboard.html). Per Rule 5: management surface, not
OpenAI-spec-compatible — outside /v1/* spec scope. No code change in
server.mjs / lib/ / providers / IR / models-registry.

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

* fix: D51 fold-in — dashboard.html cosmetic polish (opus P3)

Fresh-context opus reviewer (PR #28) flagged 4 P3 cosmetic findings;
addressing the 2 trivial ones inline. The other 2 (visibilitychange
race + defensive date null-check) are negligible at family-scale per
reviewer; deferred to Phase 4 if UX feedback warrants.

- Line ~193: deleted orphan empty <text> SVG element (no textContent,
  rendered nothing — debris from initial pass).
- Line ~200 comment: was "Date labels (first / mid / last)" but only
  first + last rendered. Tightened to clarify intent + note mid label
  deferred to Phase 4 if needed.

No behavior change. 588/588 tests pass.

Authority: PR #28 fresh-context opus reviewer P3 findings (2 of 4
addressed; remaining 2 documented as negligible).

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

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-05-25 16:11:40 +10:00
committed by GitHub
co-authored by taodeng Claude Opus 4.7
parent f9f2eaa059
commit 251b578114
4 changed files with 453 additions and 39 deletions
+35
View File
@@ -4,6 +4,41 @@ All notable changes to OLP land here. Per `CLAUDE.md` release_kit overlay, this
## Unreleased
### D51 — `dashboard.html` full multi-panel UI (Phase 3)
Fourth Phase 3 D-day. Replaces the D50 `dashboard.html` placeholder with the full 4-panel UI per ADR 0008 § 6. Vanilla HTML + JS + fetch — no build step, no framework, no CDN (Lane 1 = A). 30s page poll with `document.visibilityState` pause/resume (Lane 4 = A).
- **4 panels rendered from `/v0/management/dashboard-data`** (the single backing endpoint, per Lane 2 in-memory query model):
- **Panel 1 — Per-provider quota**: table of `{ Provider | Available | Status }`; surfaces `null` available as "n/a" + capturing per-provider `provider.quotaStatus()` errors as a red status pill (graceful degradation per ADR § 9).
- **Panel 2 — Last 24h: request count + cache hit + fallback rate**: per-provider row of `{ Requests | Cache hit % | Fallback rate % }`. Cache hit sourced from `cache_hit_24h.by_provider[p].hit_rate`; fallback rate computed from `window_24h.by_provider[p].fallback_count / count`.
- **Panel 3 — Request count last 30 days (SVG sparkline)**: vanilla SVG bar chart with `<title>` tooltips showing per-day per-provider breakdown. Y-axis: requests per day (scaled to max); X-axis: 30 daily buckets (UTC). Each bar `<title>` includes the date + total count + provider breakdown.
- **Panel 4 — Top fallback chains (last 24h)**: numbered table of `{ # | Chain | Count | First seen | Last seen }` with chain arrows rendered in monospace (`anthropic → openai`).
- **30s poll + visibilitychange pause** (ADR 0008 § 6.5):
- `setInterval(refresh, 30000)` after the initial fetch.
- `document.addEventListener('visibilitychange', ...)``stopPolling()` on hidden / `refresh() + startPolling()` on visible.
- Per ADR § 6.5 this prevents 2880 background polls/day per owner when the dashboard tab is in the background.
- **Error handling**:
- 401 from `/v0/management/dashboard-data` → in-page error banner explains owner-tier requirement + suggests SSH-tunnel + header-injection workaround (browsers can't natively send `Authorization: Bearer` without a proxy/extension).
- Other HTTP errors → generic "HTTP <code>" banner; console.warn for operator debugging.
- Per-panel "Loading…" / "No requests in window." / "No fallback chains triggered" empty states.
- **DOM helpers**: small `el(tag, attrs, ...children)` + `svgEl(tag, attrs)` factories — no framework, ~10 lines each. Sparkline uses native `<title>` for tooltips (no JS hover handlers).
- **Critical correctness invariants** (per ADR 0008 § 6 + Lane 1 = A):
- No `<script src>` — entire JS inline in `<script>` tag (Suite 25d asserts).
- No `<link rel="stylesheet" href=>` — all CSS in `<style>` tag (Suite 25d asserts).
- Only one backing endpoint hit: `/v0/management/dashboard-data` (Suite 25e asserts). All 4 panels consume slices of its response.
- 401 path keeps panels in last-good state rather than clearing them; operator sees the error banner + can debug.
- **Test surface (Suite 25, +6 tests — 582 → 588):**
- 25a: owner /dashboard response contains all 4 panel container IDs (`panel-quota`, `panel-24h`, `panel-trend`, `panel-chains`).
- 25b: dashboard JS declares `POLL_INTERVAL_MS = 30000` + uses `setInterval` + `clearInterval`.
- 25c: visibilitychange listener wired + checks `document.visibilityState === 'hidden'`.
- 25d: NO external `<script src>` and NO external stylesheet `<link href>` — pinning Lane 1 = A.
- 25e: dashboard JS fetches `/v0/management/dashboard-data` (the single consolidated D50 endpoint).
- 25f: 401 in-page error banner mentions owner-tier so a maintainer who lands on a 401 knows the route forward.
- **Manual smoke (ADR 0008 § 10 #12 manual acceptance)**: the dashboard renders without console errors in a real browser when served by a running OLP instance + owner-tier Bearer token injected via SSH-tunnel + header-injection extension. Not automated at Phase 3 (Lane 4 = A poll model doesn't need playwright; Phase 4+ may add a playwright smoke if dashboard complexity grows).
- **Documentation:** AGENTS.md `dashboard.html` marker promoted from 🟡 D50 placeholder to ✅ D51 full UI.
- **Test count:** 582 → 588 (+6 D51 tests in Suite 25).
- **Authority:** ADR 0008 § 6 (panels + refresh + localhost) + § 6.5 (poll + visibilityState pause) + Lane 1 = A (no build step) + Lane 4 = A (30s poll) + Lane 5 = B (full 4-panel scope); ADR § 9 (graceful degradation surfaced in Panel 1); ADR § 10 criterion #12 (HTML smoke); CLAUDE.md `release_kit overlay phase_rolling_mode` — under Unreleased; standing autopilot grant.
### D50 — `server.mjs` management endpoints (Phase 3 dashboard wire-up)
Third Phase 3 D-day. Wires the D49 `lib/audit-query.mjs` aggregate query layer into 4 owner_only_block HTTP endpoints per ADR 0008 §§ 7-8. Ships a placeholder `dashboard.html` at repo root (D51 lands the full multi-panel UI). All endpoints follow the Phase 2 / D45 auth + audit + touchLastUsed pattern.