Frontend-coverage gate
What it is
Every backend capability probectl ships needs somewhere a user can actually use it — normally a first-party screen, or an API/CLI when no visual workflow is needed. The frontend-coverage gate is a test that fails the build if any declared capability has lost its user-facing surface. It exists so backend and frontend can't silently drift apart: you add a feature to the API, forget to wire up the screen, and nothing notices. This gate notices.
Two pieces make it work:
- The contract — a registry,
web/src/surfaces.ts. Every capability is one entry that declares where it surfaces. - The enforcement — a test,
web/src/test/surface-coverage.test.tsx, run as the Frontend-coverage gate step of CI'swebjob (it runsnpm run coverage-gate, which isvitest run src/test/surface-coverage.test.tsx). - The rendered a11y proof — a Chromium gate,
npm run a11y:browser, run by CI'sweb-rendered-a11yjob in the digest-pinned Playwright container. It starts the real Vite app, renders every native route under the light and dark themes, runs axe with browser-computed WCAG tags, and checks keyboard focus, focus-obscured, positivetabindex, and 24px minimum interactive targets. The/dashboardsroute has an extra data-depth assertion in that same browser pass: active tests, BGP, flow, device, eBPF, cost, threat, and tenant-health dashboard tables must all render tenant-scoped fixture rows; tenant name, immutable tenant ID, absolute time, preset, and coverage details must be visible; the report destination must remain local/no-outbound; and browser requests must not carry atenant_idquery parameter.
Think of the registry as a passenger manifest and the gate as the headcount: drift in either direction — someone aboard who isn't on the list, or someone on the list who isn't aboard — fails the count. That bidirectionality is the point; a one-way check would let the registry rot into fiction.
The registry
Every entry declares one of three Surface kinds, and the gate checks a different thing for each:
| Kind | Meaning | What the gate verifies |
|---|---|---|
native |
A first-class screen in the app. | The route renders a real screen (not the "coming soon" placeholder), has a <main> landmark (the HTML element assistive technology uses to jump straight to the page's content) and an <h1>, passes the jsdom axe screen test, and passes the rendered Chromium gate in both shipped themes at desktop and mobile widths for browser-computed contrast, keyboard focus visibility/order safety, focus-obscured, and minimum interactive target size. Native entries may also declare API/CLI evidence for PRD row parity. |
federated |
Served through an interoperability surface by design — Prometheus-compatible API, OTLP, raw API, or CLI. External clients remain optional. | The declared evidence actually exists: a file:<repo path> is present on disk, an openapi:<path> is a real route in the control plane's OpenAPI spec (internal/control/openapi.json), or a cli:<probectl command> is registered in the terminal surface (internal/cli/surfaces.go). Federated surfaces count only where an API/CLI workflow is the product contract; they never substitute for a native dashboard or other visual workflow. |
none-by-design |
Deliberately no current surface. | The entry must carry a rationale and must not declare a route or federated evidence. Future or out-of-GA capabilities stay visible in the denominator without pretending a user-facing screen exists. |
The gate fails on drift either way:
- a nav destination that nobody registered;
- a routed entry that isn't reachable from the nav (unless it is explicitly
marked
offNav— the provider/operator console is deliberately hidden from the tenant app); - a
nativeclaim whose route renders the placeholder; - a
none-by-designclaim with no rationale, or one that incorrectly declares a route or federated evidence; federatedevidence that has disappeared;- PRD rows whose delivered capability no longer traces to native, API, CLI,
federated, or explicit
none-by-designevidence; - and, as a consistency check, any orphaned
routes/*.module.cssstylesheet that no route file imports.
Working with it
Shipping a new surface means: add the screen and its registry entry in the
same pull request. Moving a capability from none-by-design to a real
surface means replacing the rationale with a native route or federated
evidence in that same change.
That's the whole discipline — the registry is the one place a declaration
becomes executable, so a forgotten screen turns into a red build instead of a
silent gap.
What the gate deliberately does not judge is design polish — it checks that
a capability is present, reachable, and accessible, not that it looks good. The
deeper per-page accessibility checks (empty states, loading states, data states)
live in web/src/test/a11y.test.tsx, while browser-only accessibility checks
live in scripts/web_rendered_a11y.mjs; visual quality stays a design-led,
human-reviewed concern.