Verbatim from the two most recent deep reviews. Line numbers are from those runs
and have since moved.
-
packages/app/src/components/Exemplars/exemplarPoints.ts:129 — The window split triggers on ordered.length > ceil(maxExemplars * 0.75) rather than > maxExemplars, so at the default budget of 12 a chart with 12 populated buckets renders only 9 markers and three buckets get none, contradicting the docstring's "more buckets than the marker budget".
- Fix: Gate the split on
ordered.length <= maxExemplars and keep windowCount only as the split width so every bucket that fits the budget still emits its rank-0 marker.
-
packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:135 — A hovered marker's React key and data x are fixed to its timestamp while xAxisDomain shifts every live-tail tick, so the same <g> node slides out from under a stationary cursor with no mouseleave: the card keeps rendering at the x/y captured at mouseenter and isExemplarHovered stays true, suppressing the series tooltip until the pointer moves.
- Fix: Re-read the hovered point's current x/y from
exemplarPoints on every recompute to reposition the card, and re-validate the pointer against the marker's current node so hover-end fires when it no longer sits underneath.
-
packages/app/src/HDXMultiSeriesTimeChart/MemoChart.tsx:202 — ChartComponent swaps between AreaChart and BarChart, a different element type that fully remounts every ExemplarDot without dispatching mouseleave, and the reset guards only fire when the key is absent from exemplarPoints, so switching display type while a card is pinned orphans it over markers that no longer exist.
- Fix: Add an effect keyed on
displayType that clears the hovered and pinned exemplar state before the chart subtree remounts.
-
packages/app/src/components/Exemplars/ExemplarHoverCard.tsx:147 — useExemplarCard destructures only data and isLoading from useExemplarTraceMeta, so a failed trace query and a source whose exemplarTraceSourceId is not a Trace kind both arrive as meta === undefined, isLoading === false and render "Trace not found in source", reporting a misconfiguration or query error as a missing trace.
- Fix: Thread
isError and an unsupported-source state through to the card and render a distinct "could not load trace details" message for each.
-
packages/app/src/HDXMultiSeriesTimeChart/useChartScales.ts:120 — hasSelection alone (without fitYAxisToData) switches yAxisDomain to a numeric pair whose lower bound is the data minimum, so one click on the sole legend entry of a single-series chart makes clampExemplarY return null for every below-floor marker and silently reverts the overlay to a max envelope with no notice.
- Fix: Fit the y-axis floor only when
fitYAxisToData is set, or surface the count of markers dropped by clampExemplarY in the exemplar notice.
-
packages/app/src/hooks/useExemplars/exemplarNormalize.ts:23 — collapsesHistogramBuckets tests the literal substring histogram_quantile( while isPromqlExemplarEligible allows \s* before the paren, so histogram_quantile (0.95, …) passes the toggle gate, keeps le in the group key, and returns dropped: 'multiple-series' with a notice telling the user to aggregate to a single line they already have.
- Fix: Make
collapsesHistogramBuckets use the same whitespace-tolerant, literal-stripped regex the eligibility gate uses so the two checks cannot disagree about one expression.
-
packages/api/src/routers/api/prometheus.ts:232 — The pipeline() catch cannot distinguish a client disconnect from an upstream failure and always returns 502, so ordinary tab closes and live-tail supersessions increment hyperdx.prometheus.query_errors, the counter whose own docblock scopes it to backend health for alerts and SLOs.
- Fix: Detect a client-initiated close in the pipeline catch and return the already-written upstream status so
recordProxyOutcome does not count it.
-
packages/api/src/routers/api/prometheus.ts:165 — proxyToPrometheus never receives req, so the only abort on the outbound fetch is the 90s AbortSignal.timeout; the client forwards its abort signal precisely because live-tail supersedes the request every tick, yet the API keeps executing the superseded upstream query to completion.
- Fix: Pass
req in and combine a signal from its close event with the timeout via AbortSignal.any so a client disconnect cancels the upstream fetch.
-
packages/app/src/hooks/useExemplars/quantize.ts:13 — EXEMPLAR_KEY_QUANTUM_MS is 30s while EXEMPLAR_STALE_TIME_MS is 60s, and fetchRange is part of the query key, so the key changes twice per stale window and the declared 60s staleness tolerance can never suppress a fetch on a live-tail chart.
- Fix: Derive one constant from the other so the key quantum is at least the stale time.
-
packages/app/src/hooks/useExemplars/useExemplarTraceMeta.ts:42 — The hover query is WHERE TraceId = {traceId:String} with no time predicate and enabled the instant a traceId appears, so sweeping the cursor across a marker cluster fires one unbounded trace-table lookup per 9px hit circle crossed with no debounce, even though the exemplar's own timestamp and an EXEMPLAR_TRACE_WINDOW_MS precedent are already available.
- Fix: Bound the query with a window around the exemplar's timestamp and debounce the hovered
traceId by the same grace the close timer already uses.
-
packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:135 — The hover-reset, pin-reset, and suppressNextClickRef guards have no tests, and neither do useExemplarCard's quantized auto-unpin, Escape handler, or the two URL shapes in navigateToExemplarTrace, despite the hook's own docblock stating every bug in this layer came from a marker outliving its data.
- Fix: Add
renderHook tests that drop a hovered and a pinned marker from the rerendered exemplars array, assert the reset callbacks fire, and cover the same-window vs changed-window unpin cases.
-
packages/app/src/components/DBTimeChart/DBTimeChart.tsx:317 — plottedSeriesCount filters out isDashed comparison lines to fix a bug the surrounding comment describes, but DBTimeChart.test.tsx mocks @/hooks/useExemplars wholesale and never inspects its arguments, so re-counting the dashed previous-period line would pass every existing test.
- Fix: Spy on
useExemplars in one test, render with one solid plus one isDashed series, and assert plottedSeriesCount === 1.
-
packages/api/src/routers/api/prometheus.ts:556 — prometheus.test.ts covers only the extracted pure resolveExemplarWindow, leaving the handler's isPrometheusEndpoint branch selection, the ClickHouse-backed empty-success response, and the deliberate 5xx-only error-counter rule unexercised.
- Fix: Add a route-level test with mocked
getConnectionById and fetch asserting the narrowed start is proxied, the ClickHouse branch returns data: [] without fetching, and a 400 does not increment the error counter.
-
packages/api/src/routers/api/prometheus.ts:190 — The new proxy route reaches a user-supplied connection.host with no protocol or private-IP check and with fetch's default redirect: 'follow', while the sibling connection-test path in clickhouseProxy.ts already applies isPrivateIp to the same field.
- Fix: Validate
connection.host with isPrivateIp at write time and pass redirect: 'manual' in proxyToPrometheus.
-
packages/app/src/components/DBTimeChart/useExemplarCard.ts:86 — 'traceSourceId' in source is never true for SourceKind.Metric or SourceKind.Promql (neither MetricSourceSchema nor PromqlSourceSchema declares that field, and zod strips unknown keys), so the documented fallback is dead code and a chart without an explicit exemplarTraceSourceId can never resolve trace metadata.
- Fix: Resolve the fallback by hopping through the metric source's
logSourceId to that log source's traceSourceId, or drop the fallback and require exemplarTraceSourceId.
-
packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:134 — Thinning runs before clamping and the 30s fetch quantum can place exemplars up to 45s past the rendered x-domain at 15-second granularity, so marker-budget slots are spent on points that clampExemplarX then drops, permanently raising a notice that blames a fitted y-axis floor on a correctly configured chart.
- Fix: Trim exemplars to the drawn x-domain before thinning, and exclude quantization-surplus drops from the count reported through
onExemplarsDropped.
-
packages/app/src/hooks/useExemplars/useExemplarTraceMeta.ts:50 — The per-hover trace lookup filters only on TraceId with no time predicate and sets no retry, so each hovered marker can trigger a full trace-table scan retried three times with backoff before the card can report failure.
- Fix: Pass the exemplar's timestamp in and add a bounded
timestampValueExpression BETWEEN predicate, and set retry: 1 to match the sibling hook.
-
packages/app/src/components/Exemplars/exemplarPoints.ts:270 — The one-bucket tolerance is applied to the lower bound as well as the upper one, so exemplars fetched by quantizeStart's widened window are snapped forward onto the first plotted bucket and drawn at a time they did not occur, without being counted as dropped.
- Fix: Make the tolerance one-sided —
if (x < min || x > max + tolerance) return null; — since only the end-exclusive upper bound needs it.
-
packages/app/src/hooks/useExemplars/exemplarNormalize.ts:106 — The full Prometheus body is materialised and run through one ExemplarSchema.safeParse per exemplar on the UI thread before any cap applies, and is discarded wholesale when the response spans multiple series.
- Fix: Decide the multiple-series drop from
seriesLabels before parsing any exemplar, and short-circuit once the parsed count exceeds a hard multiple of EXEMPLAR_QUERY_LIMIT.
-
packages/api/src/routers/api/prometheus.ts:646 — A PromQL chart on a ClickHouse-backed connection passes every client gate and receives {status:'success', data: []}, so the toggle is on, no markers appear, no notice explains it, and a proxy round-trip is paid per quantised window.
- Fix: Return a distinguishable marker such as
unsupported: true for the non-Prometheus branch and surface it as an exemplar notice, or gate the editor toggle on connection.isPrometheusEndpoint.
-
packages/app/src/hooks/useExemplars/useExemplarTraceMeta.ts:36 — Database and table names from the source document are hand-quoted with backticks and spliced into the query, unlike every sibling renderer which binds them as {Identifier} parameters, so a name containing a backtick terminates the quoted identifier.
- Fix: Build the
FROM clause through the parameterized { Identifier: ... } path that renderFrom uses instead of string concatenation.
-
packages/api/src/routers/api/prometheus.ts:633 — The new route adds another entry point into proxyToPrometheus, which fetches a member-writable connection.host server-side with no scheme or address validation and default redirect-following, then streams the body back to the caller.
- Fix: Harden
proxyToPrometheus once for all four callers by rejecting non-http(s) schemes, blocking loopback/link-local/RFC1918 targets, and setting redirect: 'manual'.
-
packages/api/src/routers/api/prometheus.ts:647 — The handler's catch maps every thrown error to HTTP 400 bad_data and increments prometheusQueryErrors unconditionally, so a Mongo failure in getConnectionById reads as a client mistake while a malformed timestamp pollutes the counter that recordProxyOutcome deliberately keeps 5xx-only.
- Fix: Return 5xx for errors that are not recognised input-validation failures, and restrict the counter increment to those.
-
packages/app/src/components/Exemplars/ExemplarDot.tsx:51 — The marker hard-codes hex fallbacks and reuses --color-text-default for its stroke, which agent_docs/data_viz_colors.md explicitly forbids in chart components ("No new hex strings in chart components").
- Fix: Source the fill from
getChartColorWarning() and the outline from a chart border token rather than a text token or a literal hex.
-
packages/app/src/components/Exemplars/ExemplarDot.tsx:37 — The marker <g> carries only onMouseEnter/onMouseLeave/onClick with no tabIndex, role, aria-label, or key handler, and the card only appears on marker hover, so the entire exemplar-to-trace path is unreachable without a pointer.
- Fix: Give the marker a focusable role with an accessible label and open the card on focus and
Enter/Space.
-
packages/app/src/hooks/useExemplars/exemplarNormalize.ts:9 — The hooks layer imports labelDistinguishesSeries/promqlSeriesLabelRule directly from @/components/Exemplars/promqlSeriesLabels, which components/Exemplars/index.ts does not re-export despite documenting itself as the folder's public surface.
- Fix: Move
promqlSeriesLabels.ts into hooks/useExemplars/ since it is pure string logic, or re-export it from the barrel.
-
packages/api/src/mcp/tools/dashboards/schemas.ts:537 — enableExemplars and exemplarTraceSourceId are settable through the MCP dashboard tools, but clickstack_query_tile and clickstack_timeseries only run the tile's main series query, so an agent can enable the overlay and never read back whether markers exist or what traces they point at.
- Fix: Extend the tile/timeseries query tools to run the exemplar query and return the resulting
Exemplar[].
-
packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:57 — Neither this hook nor packages/app/src/components/DBTimeChart/useExemplarCard.ts has any test, leaving the clamp wiring, drop-count reporting, hover/pin reset-on-disappear, post-zoom click swallow, Escape-to-close, and range-quantized unpin entirely unverified while DBTimeChart.test.tsx mocks the data hooks away.
- Fix: Add
renderHook tests for both hooks covering the reset guards, the drop-count effect, the click-swallow branch, and the pin/hover precedence rules.
-
packages/api/src/routers/api/prometheus.ts:570 — packages/api/src/routers/api/__tests__/prometheus.test.ts covers only the pure helpers, so the new handler's branch dispatch, missing-param 400s, connection-not-found 404, unauthenticated rejection, and the 502/504 proxy paths have no route-level coverage.
- Fix: Add supertest cases for the Prometheus-proxy and ClickHouse branches plus the 400/404/502/504 paths.
Follow-up backlog from the review rounds on #2536. Splitting these out so the PR
can merge on its P0/P1s rather than re-litigating the same batch every round.
Why this is safe to defer: the overlay is behind
NEXT_PUBLIC_ENABLE_EXEMPLARS,off by default, and additionally per-chart behind
enableExemplars. None of thebelow is reachable in a default deployment.
Context on the numbers. The deep reviewer returns a similar-sized batch every
run against the whole feature surface — roughly 1 P0/P1 and ~15 P2 each time,
across four runs. It is not a growing defect count; it is a full re-review each
time, with overlap. Several items below started as round-1 P3s and were re-graded
to P2 in later rounds because they were never fixed, which is the argument for
tracking them here instead of descoping them again.
Themes
ExemplarDothas no keyboard focus, no touch path, and noARIA; the hover card is the only route to "Inspect trace", so those users cannot
reach the feature at all. Flagged since round 1.
useExemplarTraceMetahardening. Hand-quoted database/table names, no timepredicate on the trace lookup, a query key that omits the source expressions the
SQL interpolates, and
Number(row.durationMs)on a non-numeric expression.tolerance applies to the lower bound as well as the upper; the
maxExemplars <= 0branch dedupes differently from the budgeted branch.
time bucket" (client, SQL, render), and the code documents that the two backends
therefore show different marker sets for the same chart.
/query_exemplarsmaps every thrown error to 400bad_data; aPromQL chart on a ClickHouse-backed connection passes every client gate and gets
an empty success;
enableExemplarsis settable via MCP on tiles where it doesnothing, with no warning back to the caller.
maxExemplarsaccepts up to 1000 whileEXEMPLAR_QUERY_LIMITis 200;
ExemplarSchema.attributesis never populated or read; several comments intypes.tsandtelemetry-generatordescribe a pipeline that no longer exists.histogram_quantile(...) * 1000to be rejected asa unit mismatch and it was; round 4 flags that rejection, since it is the standard
seconds-to-milliseconds idiom. Needs a decision, not a fix.
Full list
Verbatim from the two most recent deep reviews. Line numbers are from those runs
and have since moved.
packages/app/src/components/Exemplars/exemplarPoints.ts:129— The window split triggers onordered.length > ceil(maxExemplars * 0.75)rather than> maxExemplars, so at the default budget of 12 a chart with 12 populated buckets renders only 9 markers and three buckets get none, contradicting the docstring's "more buckets than the marker budget".ordered.length <= maxExemplarsand keepwindowCountonly as the split width so every bucket that fits the budget still emits its rank-0 marker.packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:135— A hovered marker's React key and data x are fixed to its timestamp whilexAxisDomainshifts every live-tail tick, so the same<g>node slides out from under a stationary cursor with nomouseleave: the card keeps rendering at the x/y captured atmouseenterandisExemplarHoveredstays true, suppressing the series tooltip until the pointer moves.exemplarPointson every recompute to reposition the card, and re-validate the pointer against the marker's current node so hover-end fires when it no longer sits underneath.packages/app/src/HDXMultiSeriesTimeChart/MemoChart.tsx:202—ChartComponentswaps betweenAreaChartandBarChart, a different element type that fully remounts everyExemplarDotwithout dispatchingmouseleave, and the reset guards only fire when the key is absent fromexemplarPoints, so switching display type while a card is pinned orphans it over markers that no longer exist.displayTypethat clears the hovered and pinned exemplar state before the chart subtree remounts.packages/app/src/components/Exemplars/ExemplarHoverCard.tsx:147—useExemplarCarddestructures onlydataandisLoadingfromuseExemplarTraceMeta, so a failed trace query and a source whoseexemplarTraceSourceIdis not a Trace kind both arrive asmeta === undefined, isLoading === falseand render "Trace not found in source", reporting a misconfiguration or query error as a missing trace.isErrorand an unsupported-source state through to the card and render a distinct "could not load trace details" message for each.packages/app/src/HDXMultiSeriesTimeChart/useChartScales.ts:120—hasSelectionalone (withoutfitYAxisToData) switchesyAxisDomainto a numeric pair whose lower bound is the data minimum, so one click on the sole legend entry of a single-series chart makesclampExemplarYreturn null for every below-floor marker and silently reverts the overlay to a max envelope with no notice.fitYAxisToDatais set, or surface the count of markers dropped byclampExemplarYin the exemplar notice.packages/app/src/hooks/useExemplars/exemplarNormalize.ts:23—collapsesHistogramBucketstests the literal substringhistogram_quantile(whileisPromqlExemplarEligibleallows\s*before the paren, sohistogram_quantile (0.95, …)passes the toggle gate, keepslein the group key, and returnsdropped: 'multiple-series'with a notice telling the user to aggregate to a single line they already have.collapsesHistogramBucketsuse the same whitespace-tolerant, literal-stripped regex the eligibility gate uses so the two checks cannot disagree about one expression.packages/api/src/routers/api/prometheus.ts:232— Thepipeline()catch cannot distinguish a client disconnect from an upstream failure and always returns 502, so ordinary tab closes and live-tail supersessions incrementhyperdx.prometheus.query_errors, the counter whose own docblock scopes it to backend health for alerts and SLOs.recordProxyOutcomedoes not count it.packages/api/src/routers/api/prometheus.ts:165—proxyToPrometheusnever receivesreq, so the only abort on the outboundfetchis the 90sAbortSignal.timeout; the client forwards its abort signal precisely because live-tail supersedes the request every tick, yet the API keeps executing the superseded upstream query to completion.reqin and combine a signal from itscloseevent with the timeout viaAbortSignal.anyso a client disconnect cancels the upstream fetch.packages/app/src/hooks/useExemplars/quantize.ts:13—EXEMPLAR_KEY_QUANTUM_MSis 30s whileEXEMPLAR_STALE_TIME_MSis 60s, andfetchRangeis part of the query key, so the key changes twice per stale window and the declared 60s staleness tolerance can never suppress a fetch on a live-tail chart.packages/app/src/hooks/useExemplars/useExemplarTraceMeta.ts:42— The hover query isWHERE TraceId = {traceId:String}with no time predicate andenabledthe instant atraceIdappears, so sweeping the cursor across a marker cluster fires one unbounded trace-table lookup per 9px hit circle crossed with no debounce, even though the exemplar's own timestamp and anEXEMPLAR_TRACE_WINDOW_MSprecedent are already available.traceIdby the same grace the close timer already uses.packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:135— The hover-reset, pin-reset, andsuppressNextClickRefguards have no tests, and neither douseExemplarCard's quantized auto-unpin, Escape handler, or the two URL shapes innavigateToExemplarTrace, despite the hook's own docblock stating every bug in this layer came from a marker outliving its data.renderHooktests that drop a hovered and a pinned marker from the rerenderedexemplarsarray, assert the reset callbacks fire, and cover the same-window vs changed-window unpin cases.packages/app/src/components/DBTimeChart/DBTimeChart.tsx:317—plottedSeriesCountfilters outisDashedcomparison lines to fix a bug the surrounding comment describes, butDBTimeChart.test.tsxmocks@/hooks/useExemplarswholesale and never inspects its arguments, so re-counting the dashed previous-period line would pass every existing test.useExemplarsin one test, render with one solid plus oneisDashedseries, and assertplottedSeriesCount === 1.packages/api/src/routers/api/prometheus.ts:556—prometheus.test.tscovers only the extracted pureresolveExemplarWindow, leaving the handler'sisPrometheusEndpointbranch selection, the ClickHouse-backed empty-success response, and the deliberate 5xx-only error-counter rule unexercised.getConnectionByIdandfetchasserting the narrowedstartis proxied, the ClickHouse branch returnsdata: []without fetching, and a 400 does not increment the error counter.packages/api/src/routers/api/prometheus.ts:190— The new proxy route reaches a user-suppliedconnection.hostwith no protocol or private-IP check and withfetch's defaultredirect: 'follow', while the sibling connection-test path inclickhouseProxy.tsalready appliesisPrivateIpto the same field.connection.hostwithisPrivateIpat write time and passredirect: 'manual'inproxyToPrometheus.packages/app/src/components/DBTimeChart/useExemplarCard.ts:86—'traceSourceId' in sourceis never true forSourceKind.MetricorSourceKind.Promql(neitherMetricSourceSchemanorPromqlSourceSchemadeclares that field, and zod strips unknown keys), so the documented fallback is dead code and a chart without an explicitexemplarTraceSourceIdcan never resolve trace metadata.logSourceIdto that log source'straceSourceId, or drop the fallback and requireexemplarTraceSourceId.packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:134— Thinning runs before clamping and the 30s fetch quantum can place exemplars up to 45s past the rendered x-domain at 15-second granularity, so marker-budget slots are spent on points thatclampExemplarXthen drops, permanently raising a notice that blames a fitted y-axis floor on a correctly configured chart.onExemplarsDropped.packages/app/src/hooks/useExemplars/useExemplarTraceMeta.ts:50— The per-hover trace lookup filters only onTraceIdwith no time predicate and sets noretry, so each hovered marker can trigger a full trace-table scan retried three times with backoff before the card can report failure.timestampValueExpression BETWEENpredicate, and setretry: 1to match the sibling hook.packages/app/src/components/Exemplars/exemplarPoints.ts:270— The one-bucket tolerance is applied to the lower bound as well as the upper one, so exemplars fetched byquantizeStart's widened window are snapped forward onto the first plotted bucket and drawn at a time they did not occur, without being counted as dropped.if (x < min || x > max + tolerance) return null;— since only the end-exclusive upper bound needs it.packages/app/src/hooks/useExemplars/exemplarNormalize.ts:106— The full Prometheus body is materialised and run through oneExemplarSchema.safeParseper exemplar on the UI thread before any cap applies, and is discarded wholesale when the response spans multiple series.seriesLabelsbefore parsing any exemplar, and short-circuit once the parsed count exceeds a hard multiple ofEXEMPLAR_QUERY_LIMIT.packages/api/src/routers/api/prometheus.ts:646— A PromQL chart on a ClickHouse-backed connection passes every client gate and receives{status:'success', data: []}, so the toggle is on, no markers appear, no notice explains it, and a proxy round-trip is paid per quantised window.unsupported: truefor the non-Prometheus branch and surface it as an exemplar notice, or gate the editor toggle onconnection.isPrometheusEndpoint.packages/app/src/hooks/useExemplars/useExemplarTraceMeta.ts:36— Database and table names from the source document are hand-quoted with backticks and spliced into the query, unlike every sibling renderer which binds them as{Identifier}parameters, so a name containing a backtick terminates the quoted identifier.FROMclause through the parameterized{ Identifier: ... }path thatrenderFromuses instead of string concatenation.packages/api/src/routers/api/prometheus.ts:633— The new route adds another entry point intoproxyToPrometheus, which fetches a member-writableconnection.hostserver-side with no scheme or address validation and default redirect-following, then streams the body back to the caller.proxyToPrometheusonce for all four callers by rejecting non-http(s) schemes, blocking loopback/link-local/RFC1918 targets, and settingredirect: 'manual'.packages/api/src/routers/api/prometheus.ts:647— The handler's catch maps every thrown error to HTTP 400bad_dataand incrementsprometheusQueryErrorsunconditionally, so a Mongo failure ingetConnectionByIdreads as a client mistake while a malformed timestamp pollutes the counter thatrecordProxyOutcomedeliberately keeps 5xx-only.packages/app/src/components/Exemplars/ExemplarDot.tsx:51— The marker hard-codes hex fallbacks and reuses--color-text-defaultfor its stroke, whichagent_docs/data_viz_colors.mdexplicitly forbids in chart components ("No new hex strings in chart components").getChartColorWarning()and the outline from a chart border token rather than a text token or a literal hex.packages/app/src/components/Exemplars/ExemplarDot.tsx:37— The marker<g>carries onlyonMouseEnter/onMouseLeave/onClickwith notabIndex,role,aria-label, or key handler, and the card only appears on marker hover, so the entire exemplar-to-trace path is unreachable without a pointer.Enter/Space.packages/app/src/hooks/useExemplars/exemplarNormalize.ts:9— The hooks layer importslabelDistinguishesSeries/promqlSeriesLabelRuledirectly from@/components/Exemplars/promqlSeriesLabels, whichcomponents/Exemplars/index.tsdoes not re-export despite documenting itself as the folder's public surface.promqlSeriesLabels.tsintohooks/useExemplars/since it is pure string logic, or re-export it from the barrel.packages/api/src/mcp/tools/dashboards/schemas.ts:537—enableExemplarsandexemplarTraceSourceIdare settable through the MCP dashboard tools, butclickstack_query_tileandclickstack_timeseriesonly run the tile's main series query, so an agent can enable the overlay and never read back whether markers exist or what traces they point at.Exemplar[].packages/app/src/HDXMultiSeriesTimeChart/useExemplarMarkers.ts:57— Neither this hook norpackages/app/src/components/DBTimeChart/useExemplarCard.tshas any test, leaving the clamp wiring, drop-count reporting, hover/pin reset-on-disappear, post-zoom click swallow, Escape-to-close, and range-quantized unpin entirely unverified whileDBTimeChart.test.tsxmocks the data hooks away.renderHooktests for both hooks covering the reset guards, the drop-count effect, the click-swallow branch, and the pin/hover precedence rules.packages/api/src/routers/api/prometheus.ts:570—packages/api/src/routers/api/__tests__/prometheus.test.tscovers only the pure helpers, so the new handler's branch dispatch, missing-param 400s, connection-not-found 404, unauthenticated rejection, and the 502/504 proxy paths have no route-level coverage.