feat(storybook): stories for one-box traffic, the pipeline graph and CPU budgets #436

Merged
gmackie merged 1 commit from feat/storybook-traffic-graph-budgets into main 2026-08-25 05:31:55 +00:00
Owner

Why

Three surfaces that landed recently had no story coverage:

Surface Lines From
One-box traffic panel 896 #404 — weighted router in front of production
Pipeline graph 1,156 #411 — ReactFlow stage/target/promotion chart
Runtime CPU budgets ~570 Worker CPU-ms ceiling monitoring

All three spend most of their interesting life in states that last seconds in production — mid-bake, a failing canary, a promotion in flight, a truncated telemetry scan. That's precisely what Storybook is for.

"Beta" isn't a component — it's the beta stage running through all three: the promotion path's left node, the Access-protected beta.* domain convention, and per-stage budgets. It's covered across these stories rather than on its own.

40 stories

  • One-box panel — disabled, idle, canary deploying, baking at 10% / 50% / degraded, automatic rollback, promoting, action pending, stream dropped
  • Pipeline graph — all seven promotion-edge statuses (idle/deploying/verifying/blocked/ready/promoting/promoted), plus the four shapes one-box gives the graph, single-stage, empty, loading, failed
  • CPU budgets — healthy, violating, non-compliant, sampled-and-truncated, unavailable, empty, paging, three error views, and the per-app panel

Fixtures type-check against the app

Rather than hand-writing shapes, the fixtures import and annotate against TrafficData, GraphData, RuntimeBudgetSnapshot, WorkerMetrics and ViolationRow. Shape drift now fails typecheck instead of rendering something plausible and wrong. There are no as never escapes in these stories.

Three fixture bugs the rendered output caught

Typecheck could not have caught any of these — only looking did:

  1. Invented event kinds. I used traffic.weight / traffic.sample; the real vocabulary is traffic.shifted / traffic.metrics / traffic.probe. The feed rendered ? → ? and the bake chip read "awaiting signal" beside a 16% error rate — visibly self-contradictory.
  2. Basis points, not percent. traffic.shifted reads fromBps/toBps.
  3. Sample ordering. BakeStats does [...samples].reverse() because "the router hands us newest-first". My oldest-first fixture plotted every trend backwards — a degrading canary rendered as a recovering one, the exact opposite of the story that panel exists to tell. Caught by measuring rendered polyline coordinates, not by eye.

One design finding, deliberately not fixed

Spark normalises each series to its own min/max and draws each lane as a separate <svg>. So in "p95 latency" and "error rate", the production and one-box lines sit on independent axes under a shared legend implying comparison. A canary at 27% error and a primary at 0.1% both render as full-height squiggles; a flat noisy primary gets stretched into an apparent trend.

Comparing the lanes is the decision that panel asks the operator to make, so it's worth a look — but the fix is a design call (shared axis, overlay, sparkbar), not a mechanical one.

Verified

421/421 stories render clean in a browser. Typecheck, lint and build all pass.

## Why Three surfaces that landed recently had no story coverage: | Surface | Lines | From | |---|---|---| | One-box traffic panel | 896 | #404 — weighted router in front of production | | Pipeline graph | 1,156 | #411 — ReactFlow stage/target/promotion chart | | Runtime CPU budgets | ~570 | Worker CPU-ms ceiling monitoring | All three spend most of their interesting life in states that last **seconds** in production — mid-bake, a failing canary, a promotion in flight, a truncated telemetry scan. That's precisely what Storybook is for. **"Beta" isn't a component** — it's the beta stage running through all three: the promotion path's left node, the Access-protected `beta.*` domain convention, and per-stage budgets. It's covered across these stories rather than on its own. ## 40 stories - **One-box panel** — disabled, idle, canary deploying, baking at 10% / 50% / degraded, automatic rollback, promoting, action pending, stream dropped - **Pipeline graph** — all seven promotion-edge statuses (`idle`/`deploying`/`verifying`/`blocked`/`ready`/`promoting`/`promoted`), plus the four shapes one-box gives the graph, single-stage, empty, loading, failed - **CPU budgets** — healthy, violating, non-compliant, sampled-and-truncated, unavailable, empty, paging, three error views, and the per-app panel ## Fixtures type-check against the app Rather than hand-writing shapes, the fixtures import and annotate against `TrafficData`, `GraphData`, `RuntimeBudgetSnapshot`, `WorkerMetrics` and `ViolationRow`. Shape drift now **fails typecheck** instead of rendering something plausible and wrong. There are no `as never` escapes in these stories. ## Three fixture bugs the rendered output caught Typecheck could not have caught any of these — only looking did: 1. **Invented event kinds.** I used `traffic.weight` / `traffic.sample`; the real vocabulary is `traffic.shifted` / `traffic.metrics` / `traffic.probe`. The feed rendered `? → ?` and the bake chip read "awaiting signal" beside a 16% error rate — visibly self-contradictory. 2. **Basis points, not percent.** `traffic.shifted` reads `fromBps`/`toBps`. 3. **Sample ordering.** `BakeStats` does `[...samples].reverse()` because *"the router hands us newest-first"*. My oldest-first fixture plotted every trend backwards — **a degrading canary rendered as a recovering one**, the exact opposite of the story that panel exists to tell. Caught by measuring rendered polyline coordinates, not by eye. ## One design finding, deliberately not fixed `Spark` normalises **each series to its own min/max** and draws each lane as a separate `<svg>`. So in "p95 latency" and "error rate", the production and one-box lines sit on **independent axes** under a shared legend implying comparison. A canary at 27% error and a primary at 0.1% both render as full-height squiggles; a flat noisy primary gets stretched into an apparent trend. Comparing the lanes is the decision that panel asks the operator to make, so it's worth a look — but the fix is a design call (shared axis, overlay, sparkbar), not a mechanical one. ## Verified **421/421 stories render clean** in a browser. Typecheck, lint and build all pass.
feat(storybook): stories for one-box traffic, the pipeline graph and CPU budgets
All checks were successful
CI / gitleaks (pull_request) Successful in 6s
CI / storybook (pull_request) Successful in 1m25s
forgegraph/ci CI passed
CI / ci (pull_request) Successful in 9m53s
ecce2110e1
Three surfaces that landed recently had no coverage: the weighted one-box
router (#404), the ReactFlow pipeline graph (#411), and Worker CPU-budget
monitoring. All three spend most of their interesting life in states that last
seconds in production, which is exactly what Storybook is for.

40 stories:

  - one-box panel   disabled, idle, canary deploying, baking at three weights,
                    a canary failing its bake, automatic rollback, promoting,
                    action pending, stream dropped
  - pipeline graph  all seven promotion-edge statuses, plus the four shapes
                    one-box gives the graph (off / idle / baking / rolled back),
                    single-stage, empty, loading, failed
  - CPU budgets     healthy, violating, non-compliant, sampled-and-truncated,
                    unavailable, empty, paging, three error views, and the
                    per-app panel

Fixtures are annotated against the app's own exported types (TrafficData,
GraphData, RuntimeBudgetSnapshot, WorkerMetrics, ViolationRow) rather than
hand-written shapes, so drift fails typecheck instead of rendering something
plausible and wrong. No `as never` escapes in these stories.

Three fixture bugs the rendered output caught, none of which typecheck could:

  - invented event kinds (traffic.weight/traffic.sample) where the real
    vocabulary is traffic.shifted/metrics/probe — the feed showed "? → ?" and
    the bake chip read "awaiting signal" beside a 16% error rate
  - traffic.shifted carries basis points, not percent
  - bakeSamples must be newest-first, because BakeStats reverses them; the
    oldest-first version plotted a degrading canary as a recovering one

Worth a separate look, left alone here: Spark normalises each series to its own
min/max and draws each lane as its own svg, so the production and one-box lines
in "p95 latency" and "error rate" sit on independent axes under a shared
legend. Comparing the lanes is the decision that panel exists to support.

Verified: 421/421 stories render clean in a browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

Preview environment is live: https://pr-436-forgegraph.forgegraf.com

Deployed ecce2110 with the beta stage's environment. It redeploys on every push and is destroyed when this PR closes.

Preview environment is live: https://pr-436-forgegraph.forgegraf.com Deployed `ecce2110` with the beta stage's environment. It redeploys on every push and is destroyed when this PR closes.
gmackie deleted branch feat/storybook-traffic-graph-budgets 2026-08-25 05:31:56 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gmackie/ForgeGraph!436
No description provided.