feat: color latency percentiles by magnitude#256
Merged
Conversation
P50/P95/P99 rendered as flat, uncolored mono text everywhere — services table, operations/dependencies tabs, service map, Cloudflare/PlanetScale infra tables, chat renderers. Every cell in a percentile column looked identical, so finding the slow service in a 40-row table meant reading every number. Error rate already had per-site tone helpers; latency had none. Adds one shared scale in @maple/ui and applies it to 17 call sites. Tone is value ÷ budget(scale), with per-percentile budgets (p50 300ms, avg 500ms, p95 1s, p99 2s, cpu 50ms) rather than one absolute threshold — p99 is *expected* to exceed p50, so a single threshold would paint every p99 column red. A healthy service now reads flat across all three. The p95 budget's slow/critical boundaries land exactly on P95_DEGRADED_MS / P95_UNHEALTHY_MS from service-health.ts, so a cell tone cannot disagree with the health badge on its own row. `cpu` exists because Cloudflare Worker CPU time is ~20x tighter than wall-clock; on the p99 budget every worker collapsed to the quiet step. The ramp is warm-only — nothing is colored for being fine, so fast values recede and the eye lands on outliers: muted → foreground/70 → foreground → severity-warn → severity-error It reuses existing tokens only; no new design tokens, check:tokens still passes. The quiet step is plain `text-muted-foreground` rather than a dimmer opacity variant: eyebrow labels beside these values already sit at `text-muted-foreground/60`, and a value fainter than its own label reads as the label instead of the number. Where a stronger signal already exists it wins, and the ramp is only the fallback: an open incident/anomaly in service-health-section, and the "p95 > 3x this node's own p50" tail check in the service map. Also deletes the third private copy of formatLatency (services-table.tsx) and routes web's percentile cells onto the @maple/ui formatter, which continues past seconds — a 90s p99 now renders 1.5min, not 90.00s. Verified: typecheck green across all 32 packages; 10 new boundary tests covering every level per scale, the service-health agreement, the budgetMs override and the 0/NaN/Infinity guard; ramp confirmed monotonic and legible in both light and dark via getComputedStyle against the live stylesheet. Not done: no authenticated pass over the live UI — local sign-in needs a password, so these tones have not been seen against real warehouse data. /services and a service's Operations tab are the views to eyeball. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Your Pullfrog Router balance is exhausted. You have a card on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer. Top up balance → · Enable auto-reload →
|
`latencyLevel` ran its break-point scan as `for (const [i, brk] of BREAKS.entries())`. On the service map that function is called three times per node per frame (120 calls/frame at the bench's 40 nodes), so it tiers up quickly — and when JavaScriptCore's DFG tier compiles the iterator-plus-array-destructuring form it hits an assertion in VirtualRegisterAllocationPhase and SIGTRAPs the whole WebKit content process. That took out the webkit-smoke leg of the cross-browser perf gate: `/service-map-bench` died with "Target page, context or browser has been closed" on every attempt, while chromium and firefox passed the same segment. Reproduced locally and confirmed from the macOS crash report (EXC_BREAKPOINT in JSC::DFG::VirtualRegisterAllocationPhase::run). Swapping in a plain indexed loop sidesteps it and does less work per call. Behaviour is unchanged — the 10 boundary tests still pass — and webkit/chromium/firefox smoke legs are all green locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Adds a shared, percentile-aware magnitude→tone scale in
@maple/uiand applies it to every P50/P95/P99 value across web and local-ui — services table, operations/dependencies tabs, service-map hover cards + node metric cells, Cloudflare/PlanetScale infra tables, and the chat result renderers (17 call sites).Fast values recede and slow ones advance, so a percentile column can be scanned instead of read.
Why
P50/P95/P99 rendered as flat, uncolored mono text everywhere. Every cell in a percentile column looked identical, so finding the slow service meant reading every number. Error rate already had per-site tone helpers; latency had none.
How it works
packages/ui/src/lib/latency-tone.ts— pure logic + class maps, a sibling of the existinginfra/severity-tokens.tsvocabulary.value ÷ budget(scale), with per-percentile budgets (p50 300ms, avg 500ms, p95 1s, p99 2s, cpu 50ms) rather than one absolute threshold. p99 is expected to exceed p50, so a single threshold would paint every p99 column red; per-budget means a healthy service reads flat across all three.P95_DEGRADED_MS/P95_UNHEALTHY_MSfromservice-health.ts, so a cell tone can't disagree with the health badge on its own row.cpuscale exists because Cloudflare Worker CPU time is ~20× tighter than wall-clock; on the p99 budget every worker collapsed to the quiet step.muted → foreground/70 → foreground → severity-warn → severity-error. Reuses existing tokens only; no new design tokens (check:tokensstill passes at 148 shared tokens). Tone is never the sole signal — the number carries the meaning and eachLatencyValuegets a plain-wordtitle(fast/normal/…/very slow).packages/ui/src/components/latency-value.tsx—<LatencyValue ms={} scale="p95" />, the ergonomic for the ~90% case (format + tone in one span);latencyToneClass()for sites that already own their element (e.g. service-mapMetricCell, stat cards).Judgment calls a reviewer should check
service-health-section, and the "p95 > 3× this node's own p50" tail check still wins in the service map. Magnitude tone applies only when neither fires.quietis plaintext-muted-foreground, not a dimmer opacity variant (the plan said/60). Eyebrow labels beside these values already sit attext-muted-foreground/60; a value fainter than its own label reads as the label. Confirmed monotonic + legible in both themes viagetComputedStyle.@maple/uiformatLatency, which continues past seconds — a 90s p99 renders1.5mininstead of90.00s. Also deletes the third private copy offormatLatencythat lived insideservices-table.tsx.Testing
bun typecheck— green across all 32 packages (also re-verified@maple/ui,@maple/web,@maple/local-uibuilding at this commit in an isolated worktree, i.e. without the unrelated in-flight work that shares my working tree).apps/web/src/lib/latency-tone.test.ts): every level per scale, the service-health threshold agreement, thebudgetMsoverride, and the0/NaN/Infinity→quietguard.Not done
No authenticated pass over the live UI. Local sign-in needs a password, which I don't enter, so these tones haven't been seen against real warehouse data — only against the real module + real stylesheet.
/servicesand a service's Operations tab are the two views to eyeball.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.