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 optionalonRowSelect?: (eventId: string) => void. When set, each data<TableRow>becomesrole="button"+tabIndex={0}+title="Open this call's body above"+cursor:pointer(.access-log__row--clickable), withonClickand an Enter/SpaceonKeyDown(preventDefault so Space doesn't scroll).- event_id resolution (judgment call): a DataTable row's
idisString(row.id)(fromtoTableRows), NOT the event_id, and the event_id cell is a styled<span>element (socell.valueis JSX, not a string). I built aMap<string,string>from the sourcerows(String(r.id) → r.event_id) withuseMemo, and look it up byrow.idin 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: wiredonRowSelect={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
onRowSelectwith the row'sevent_id(verifying id ≠ event_id viaid:7, event_id:'evt-42'), Enter fires it, and rows stay read-only (norole="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-cardsflex-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 agpu/GPUprefix (gpu0→0) so the header reads "GPU 0" regardless of exporter id form;GPU_MODEL_LABELSmaps0 → "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__fillwhosestyle={{ inlineSize: '{util}%' }}anddata-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 aU.U / T.T GBlabel and its ownrole="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 +$spacingSass 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: addedexport const CACHEABLE_UPSTREAMS = ['paddleocr', 'doc-classifier-svc'](comment: gateway upstreams withcache_ttl_s > 0) and purecacheableCacheRate(cacheHits, upstreamRows)→{ cacheHits, cacheableCalls, rate }orundefinedwhencacheHitsis unknown (pre-cache build) OR cacheable calls = 0.src/views/AnalyticsView.tsx: the "Cache-hit rate" tile now usescacheableCacheRate(cacheWindow?.cacheHits, upstreamRows)— numerator is the window's total cache hits (windowCacheRate), denominator is the sum ofcallsover cacheable upstreams from the already-fetchedgroup_by=upstreamseries. 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.tscoversCACHEABLE_UPSTREAMS, unknown-hits, no-cacheable-calls, and denominator scoping (excludes a 5k uncacheable upstream).AnalyticsView.test.tsxupdated: 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_idmap off the sourcerowsarray, notcell.value(the cell holds a<span>, not a string). - meter markup: div-based
role="meter"track+fill (no new chart dep); numericToneNumberlabel 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.