feat: add home system status sparklines - #97
Conversation
Replaces the all-time stats card on the resting home state with a 24 hour trend card: executions and p50/p95/p99 latency, polled every 30s. The metrics endpoints return sparse buckets, so each series is aligned onto a full 24 slot grid before plotting. Counts fill gaps with zero, latencies with null so an idle hour does not read as 0ms. formatResponseTime now scales precision with magnitude, since percentiles reach hundreds of milliseconds and rendered as "123.930ms". recharts is isolated in a vendor-charts chunk. Closes #6430 Signed-off-by: Anna Effort <anna.effort@ibm.com>
Latency rows stopped at their first populated bucket, so they rendered as short stubs against a full-width executions line. connectNulls bridges between known points but does not extend to the edges. Idle slots are now flattened to 0 for drawing only. The headline number still reads the nullable series, so an idle window reports the last real measurement rather than 0ms. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Each slot now reports its hour, its value, and the request count behind it. The count is the point: a p95 taken over three samples is not a meaningful percentile, and nothing else on the card showed that. Idle slots report "No requests" rather than the zero they are drawn at. The row value stays text beside the chart, so the tooltip adds a pointer-only path to detail rather than becoming the only route to it. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Hour, value and request count each get their own line, at a uniform light weight. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Each row's chart wrapper is position:relative with z-index auto, so the wrappers paint in document order and an upper row's tooltip landed behind the rows underneath. Only the last row looked correct. Gives the tooltip wrapper z-50, matching the ui/ overlays. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Reads "8 executions" rather than a bare "8". The card passes a pluralized formatter, so the tooltip stays unaware of what it is rendering. Promotes the count line to the foreground colour so the quantities carry more weight than the timestamp above them. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Splits py-4 into pt-4/pb-6 so the last row's line is not tight against the card edge. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Per Figma 5863-13032. The bare "Last 24 hours" marker becomes a titled header, "Traffic, last 24 hours", with a System status button routing to ?view=system. That view has no entry point from the resting home otherwise, since the default row carries only the four source cards. The error and empty messages move below the title, which now owns the left slot they used. Signed-off-by: Anna Effort <anna.effort@ibm.com>
--status-icon is cyan-500, only 2.43:1 on the white light-theme background. cyan-700 matches the MCP server catalog link and clears AA. Dark keeps the brand cyan from the design. Also nudges the card padding and the button's vertical offset. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Adds --sparkline-stroke rather than reusing --status-icon: the lines are non-text graphics needing 3:1, and cyan-500 gave 2.33:1 on the light card. cyan-700 gives 5.13:1. Dark keeps the brand cyan at 14.23:1. Restores aria-hidden on the chart, dropped in 95e7f3a when the tooltip landed, which exposed four unnamed SVGs per card. Marks the button icon decorative, since it sits beside its own text label. Signed-off-by: Anna Effort <anna.effort@ibm.com>
The UI does not use the word in rendered copy. 'restart ContextForge' rather than 'the server', which is ambiguous next to MCP servers and Virtual servers in the nav. Signed-off-by: Anna Effort <anna.effort@ibm.com>
Branch coverage landed at 89.95% against the 90% global threshold. The uncovered paths were all in this PR's new code: the metrics query builder had no test file, and useMetrics left the abort-after-resolve, non-Error rejection and visibility-refetch paths untested. Also drops the interval ref in useMetrics; it was written but never read once the id is captured in the effect closure. Signed-off-by: Anna Effort <anna.effort@ibm.com>
marekdano
left a comment
There was a problem hiding this comment.
1. 🟠 Medium — src/components/dashboard/SystemSparklinesCard.tsx:55
Latency tooltip's request count can mismatch its own percentile bucket
The "N requests" qualifier is derived only from the executions endpoint's populated buckets, but the file's own comment notes that executions and percentiles endpoints can disagree on which buckets are populated.
- Failure scenario: If
percentileshas a bucket for an hour (e.g. p95 = 0.472ms) butexecutionshas no matching bucket,counts[i]= 0 for that slot. The tooltip then shows a real latency value next to "0 requests," contradicting the tooltip's stated purpose of flagging low-sample percentiles. - Why: User-visible, misleading data display — but cosmetic, not a data-integrity or crash issue.
2. 🟠 Medium — src/components/dashboard/systemMetrics.ts:152
Precision change leaks to unrelated call sites
formatResponseTime's new magnitude-based precision silently changes output for other call sites outside this PR's scope (SystemStatsCard, VirtualServersCard).
- Failure scenario: A user viewing System view all-time stats or the Virtual Servers avg response time sees fewer decimal digits than before (e.g. "123.930ms" becomes "124ms"), with no test or changelog covering the behavior change for those unrelated call sites.
- Why: Unintended scope creep affecting shared behavior — no data loss, but an undocumented regression risk elsewhere in the app.
3. 🟡 Low — src/components/dashboard/systemMetrics.ts:156
Magnitude computed pre-rounding causes inconsistent precision
Decimal-place selection uses the pre-rounded value's magnitude, so values that round up across a magnitude boundary get the wrong decimal count.
- Failure scenario:
formatResponseTime(9.996)picks 2 decimals (magnitude < 10) →"10.00ms"instead of the 1-decimal"10.0ms"a true ≥10 value would get. SimilarlyformatResponseTime(99.96)→"100.0ms"instead of"100ms". - Why: Narrow edge case, purely cosmetic formatting inconsistency at specific boundary values.
🧹 Simplification
4. ⚪ Nit — vite.config.ts:51
Dead package-name alternatives in chunk regex
The vendor-charts manualChunks regex includes robust-predicates and delaunator, neither of which appears in package-lock.json's dependency tree for this PR.
- Failure scenario: Not a functional break today, but misleads future maintainers into thinking these are real transitive deps - risking missed cleanup or masking a real config gap if a similarly-named package is added later.
- Why: No functional impact whatsoever; pure maintenance/readability debt.
Implements IBM/mcp-context-forge#6430. Adds the 24 hour trend card to the resting home state: executions plus p50/p95/p99 latency, polled every 30s. Replaces the all-time stats card there, which stays on the System view. Design
sparklines.mp4
Testing
Needs
OBSERVABILITY_ENABLED=true; it defaults false and the endpoints return empty series rather than erroring. Only/rpc,/sse,/mcpand A2A invocation are traced, so browsing the UI will not populate the card. Use a tool Try-it panel, or point an MCP client at it.Follow-ups