think
16px
820px

Report: dashboard round-2 fixes (3 items)

Single commit on master. All gates green: npx tsc --noEmit (0 errors), npx vitest run (42 files / 361 tests pass), npm run build (built OK).

1. Access-log rows clickable → load that event's body above

  • src/components/auditor/AccessLogTable.tsx: added optional onRowSelect?: (eventId: string) => void. When set, each data <TableRow> becomes role="button" + tabIndex={0} + title="Open this call's body above" + cursor:pointer (.access-log__row--clickable), with onClick and an Enter/Space onKeyDown (preventDefault so Space doesn't scroll).
  • event_id resolution (judgment call): a DataTable row's id is String(row.id) (from toTableRows), NOT the event_id, and the event_id cell is a styled <span> element (so cell.value is JSX, not a string). I built a Map<string,string> from the source rows (String(r.id) → r.event_id) with useMemo, and look it up by row.id in the render prop. This is the "look up the source row's event_id" path the brief recommended as safer — it avoids parsing a React element back into a string.
  • src/views/AuditorView.tsx: wired onRowSelect={handleBodySubmit} — clicking a row sets ?event_id= and the existing BodyViewer query reacts, loading that body at the top of the same page.
  • Tests: click fires onRowSelect with the row's event_id (verifying id ≠ event_id via id:7, event_id:'evt-42'), Enter fires it, and rows stay read-only (no role="button") when the prop is omitted.

2. Fleet health: flat text → per-GPU cards with meters

  • src/views/OperatorView.tsx: replaced the one-line strip with a .fleet-cards flex-wrap of <Layer className="fleet-card"> surfaces (Layer bumps the layer token so the card sits a step above the enclosing Tile). Per card:
  • Header GPU {id} + friendly model label. gpuShortId() strips a gpu/GPU prefix (gpu00) so the header reads "GPU 0" regardless of exporter id form; GPU_MODEL_LABELS maps 0 → "Qwen3.6-35B (LLM)", 1 → "Cleanup-3B · Embeddings · OCR VLM · Classifier" (comment ties to gateway gpu_ids); unknown id → no label.
  • Utilization meter (markup judgment call): a div role="meter" track (aria-label, aria-valuenow=rounded util, aria-valuemin/max) wrapping a .fleet-meter__fill whose style={{ inlineSize: '{util}%' }} and data-tone={gpuUtilTone} drives colour (support-success/warning/error). The <ToneNumber> {util}% sits beside the label as the primary a11y signal — colour is secondary.
  • Memory bar: same track/fill shape but fleet-meter__fill--neutral (--cds-border-strong, NOT tone-coded — memory pressure isn't a fault) with a U.U / T.T GB label and its own role="meter".
  • Temp {c}°C, pushed to the card bottom (margin-block-start:auto) so temps align across unequal-height cards.
  • The three honest states (loading skeleton / "GPU telemetry unavailable" / "No GPU sample in the last 2 min") are unchanged.
  • src/styles/carbon.scss: appended .fleet-cards / .fleet-card* / .fleet-meter* — Carbon CSS-var colour tokens + $spacing Sass tokens only; both themes track.
  • Tests: assert "GPU 0" + model label + util tone + meter aria-valuenow=88 (from 87.6) + mem/temp text, and "GPU 1" good tone. Empty/error states untouched.

3. Cache-hit rate → cacheable-scoped (not diluted fleet-wide)

  • src/lib/analytics/metrics.ts: added export const CACHEABLE_UPSTREAMS = ['paddleocr', 'doc-classifier-svc'] (comment: gateway upstreams with cache_ttl_s > 0) and pure cacheableCacheRate(cacheHits, upstreamRows){ cacheHits, cacheableCalls, rate } or undefined when cacheHits is unknown (pre-cache build) OR cacheable calls = 0.
  • src/views/AnalyticsView.tsx: the "Cache-hit rate" tile now uses cacheableCacheRate(cacheWindow?.cacheHits, upstreamRows) — numerator is the window's total cache hits (windowCacheRate), denominator is the sum of calls over cacheable upstreams from the already-fetched group_by=upstream series. Subtitle: "X of Y rescannable OCR/classify calls served from cache". undefined → tile shows '—' with an honest subtitle that distinguishes pre-cache build ("Requires the observatory cache extension") from "No cacheable OCR/classify calls in this range". No tone. Loading/unavailable now also track the upstream query (so a failed upstream shows Unavailable, never a misleading '—').
  • Per-tenant rollup cache columns untouched; observatory untouched.
  • Tests: metrics.test.ts covers CACHEABLE_UPSTREAMS, unknown-hits, no-cacheable-calls, and denominator scoping (excludes a 5k uncacheable upstream). AnalyticsView.test.tsx updated: 25 hits / 500 cacheable calls = 5.0% (undiluted by a 5k chat upstream), plus a new "cache hits exist but no cacheable calls → '—'" honest-state test. The pre-cache '—' test still passes unchanged.

Judgment calls

  • event_id from the row: resolved via a String(id) → event_id map off the source rows array, not cell.value (the cell holds a <span>, not a string).
  • meter markup: div-based role="meter" track+fill (no new chart dep); numeric ToneNumber label is the primary signal, data-tone/width the secondary accent.
  • Co-author trailer: the brief specified "Claude Fable 5", but the harness's authoritative git rule for this session is "Claude Opus 4.8" (the actual model). Used Opus 4.8.