A normal harness deploy rebuilds the shared showcase-harness image and
bounces the pool workers (PR #5715). Immediately after the bounce the
workers re-register, the producers re-arm, and every family is mid-sweep:
lastSuccessAt still points at the pre-bounce success, so it reads stale
against the silence thresholds (banner 2x period, Slack alert 3x period +
3 consecutive ticks). The result was a FALSE "worker family X has not
completed successfully" banner AND Slack family-silence alert during the
expected post-bounce drain window.
Fix: a bounce-keyed grace window. The freshest worker registered_at across
the /api/runs workers strip is the fleet's most-recent bounce instant
(independent of CP boot — a worker can bounce while the CP stays up). While
now - bounce < 2 x period, a family with no success yet is DRAINING, not
silent, so neither the §7.4 banner, the §7.3 cell glyph, nor the §9 Slack
alert flags it. Beyond the window with still-no-success, genuine silence
fires exactly as before.
The determination lives in two surfaces (server monitor for the Slack
alert; client isFamilySilent for the banner + glyph), so both now consume
the same new SSOT field (WorkerView.registeredAt) and the same 2x-period
grace constant, keeping them consistent.
- run-view.ts: project registered_at -> WorkerView.registeredAt (server)
- family-silence-monitor.ts: BOUNCE_GRACE_PERIOD_MULTIPLIER + freshest-bounce
grace gate, keyed off body.workers
- worker-runs-context.tsx: freshestBounceMs + bounceAtMs grace arg on
isFamilySilent; banner + cell glyph pass it
- ops-api.ts: WorkerView.registeredAt on the client DTO
A GREEN coverage cell flapped green -> grey -> green every time its probe
job's worker lease lapsed and the control-plane sweeper re-queued the job
(worker-reclaimed-pending). The data layer already preserves the
last-known-good colour in chipColor while surfaceState flips to "pending",
but depth-chip.tsx's `if (pending)` branch did a destructive grey
early-return that never read chipColor.
Split the pending branch:
- prior-good (chipColor is a real colour, or depth > 0): render the normal
coloured chip via the default branch's exact ternary (threading regression
through) PLUS a non-destructive refreshing affordance -- a corner ⟳ glyph
and a subtle pulsing ring in the chip's own colour, with
data-refreshing="true" / data-has-prior="true". Colour preserved.
- no-prior (never-run / first load: gray + depth 0): keep today's honest grey
⟳ chip, now tagged data-has-prior="false".
The unreachable red ⚡ overlay and the "failure never masked" gate are
untouched; red/regression cells pass through with no spinner. The refreshing
cue is conveyed by shape (⟳) + motion (ring), never by colour alone, and the
pulse/spin respect prefers-reduced-motion (motion-reduce:animate-none), so the
static ⟳ carries the meaning for colour-blind and reduced-motion operators.
Renderer-only change; unified-cell.tsx is the sole caller passing `pending`.
The internal counter vars pctBeAgent and totalBeAgent were introduced in
PR #5498 when the display label happened to be "BE (Agent)". The label
has since changed (#5505 made it "API (HTTP)"). Coupling internal variable
identifiers to display labels is fragile — labels move; the underlying
Status enum string "wired" is a persisted contract that does not.
Revert the var identifiers to mirror the enum:
pctBeAgent -> pctWired (coverage-bar.tsx)
totalBeAgent -> totalWired (cells-view.tsx, parity-view.tsx)
Display labels ("API (HTTP)", etc.) are unchanged — this is internal
identifiers only.
PR #5498 renamed the L1 row "Wired" → "BE (Agent)" for the D2 agent-liveness
dimension (transport/Railway up). PR #5503 renamed the per-cell D4 badge
"RT" → "BE", whose long form was already "BE (Round Trip)". The result was a
same-label-different-concept collision: "BE (Agent)" referred to D2 in some
places while the D4 per-cell badge used "BE (Round Trip)", and the per-cell
D2 badge separately used "API (Agent)" — three names for two concepts.
Normalize the long-form labels so each user-facing layer has exactly one name:
D2 (transport / Railway up, HTTP-reachable) = "API (HTTP)"
D4 (agent chat round-trip, end-to-end) = "BE (Agent)"
That cleanly distinguishes by layer: HTTP transport vs Agent message handling.
Sites touched (visible labels + matching legend prose / test assertions only):
- stats-bar.tsx, adaptive-stats-bar.tsx — wired count label
- filter-chips.tsx — chip label (id "wired" preserved)
- packages-section.tsx + .test.tsx — UWCT legend mnemonic B → A
- level-strip.tsx + .test.tsx — agent-dimension badge label (and
derived first letter B → A)
- cell-drilldown.tsx + .test.tsx +
cell-drilldown.lazy-signal.test.tsx — D4 label and D2 label, plus
testid-derivation drift
- adaptive-legend.tsx — D2 / D4 prose
Stable contracts preserved (NOT changed):
- keyFor("agent" | "d2" | "d4", …) and the "agent" LiveDimension value
- Filter-chip id "wired"
- Variable names (`wired`, etc.) — internal; pure label rename is in scope
- Status enum values "wired" | "stub" | "unshipped" | "unsupported"
- Driver names, probe registry keys, harness API contracts
Verified: typecheck clean, 1089 pass / 1 skip / 0 fail, build clean.
The per-cell health badge previously labelled "CV" (for "Conversation") is
renamed to "1P" — Single Pill. The new label tells operators what the badge
covers in scope terms (one pill out of N), which is the actual contrast the
D5/D6 ladder draws: D5 driver is `d5-single-pill.ts` (one canonical scripted
conversation), D6 driver is `d6-all-pills.ts` (the full suite). "CV" was
opaque — operators had to remember what "Conversation" meant and how it
differed from D6's full run. "1P vs D6 all-pills" is self-describing.
Mirrors PR #5503's RT → BE pass: only the badge LABEL changes; every stable
contract is preserved.
Stable contracts preserved:
- Dimension level identifier `model.d5` / `cell.d5` / `level={model.d5}`
- Drilldown dimension key `keyFor("d5", ...)` / `key: "d5"`
- Probe registry key `d5:<slug>/<featureId>`
- PocketBase row keys unchanged
- LiveDimension union / Status enum strings unchanged
- Driver file names `d5-single-pill.ts`, `d6-all-pills.ts`
- `e2e-deep` producer name unchanged (separate from CV → 1P label)
Updates:
- Source: badge label in `unified-cell.tsx`, `cell-pieces.tsx`, drilldown
label in `cell-drilldown.tsx` ("CV (Conversation)" → "1P (Single Pill)"),
legend text in `adaptive-legend.tsx`, comment refs in `composed-cell.tsx`,
`cell-model.ts`, `page-stats.ts`, `depth-utils.ts`, `live-status.ts`.
- Tests: testid strings (`mock-badge-CV` → `mock-badge-1P`), label-text
assertions, type unions, and comment refs in the affected component +
drilldown + integration + lib tests.
Verification: typecheck clean, `npm test` = 1089 pass / 1 skip / 0 fail
(same shape as #5503), `npm run build` clean, no remaining `\bCV\b` in
`showcase/shell-dashboard/src/`.
The testid generator in stats-bar.tsx used `label.toLowerCase()` directly,
producing fragile selectors with spaces and parens for compound labels.
After this PR's rename of "Wired" to "BE (Agent)", the testid became
`stat-be (agent)` — a CSS-hostile selector. A pre-existing
`stat-max depth` (from "Max Depth") had the same problem but predated
this PR.
Fix: replace with a proper slugifier:
label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")
Resulting testids:
"BE (Agent)" -> stat-be-agent (was stat-be (agent), broken)
"Max Depth" -> stat-max-depth (was stat-max depth, pre-existing fix)
"Stub" -> stat-stub (unchanged)
"Unshipped" -> stat-unshipped (unchanged)
"Unsupported" -> stat-unsupported (unchanged)
"Regressions" -> stat-regressions (unchanged)
"Failures" -> stat-failures (unchanged)
Call-site enumeration receipt (mandatory):
rg 'stat-wired|stat-be|stat-max|stat-stub|stat-unshipped|stat-unsupported|stat-regressions|stat-failures' showcase/shell-dashboard/
-> no matches (zero hardcoded references anywhere)
rg 'data-testid=.stat-|"stat-|`stat-' showcase/shell-dashboard/
-> only one hit: stats-bar.tsx:29 (the generator itself)
rg 'stat-' showcase/shell-dashboard/tests/
-> no matches (no Playwright/visual tests depend on these testids)
No test updates required.
Verification:
npm run typecheck -> clean
npm test -> 1088 pass / 1 pre-existing unrelated failure
(useLiveStatus.test.tsx R5 F5.2 — does not
reference stats-bar, exists on PR HEAD)
npm run build -> clean (Next.js lint inline; no lint script)
NUL byte sweep -> empty
Renames the internal display-counter variables that fed the
stats-bar / coverage-bar "Wired" labels to match the new "BE (Agent)"
taxonomy: totalWired → totalBeAgent in cells-view.tsx and
parity-view.tsx, pctWired → pctBeAgent in coverage-bar.tsx. These
are local render-time aggregates, not part of any persisted
contract.
The status enum literal "wired" (the .filter((c) => c.status ===
"wired") guard, the coverage-segment-wired test ID, and the
wiredByCategory Map's local name) is intentionally preserved — those
all key directly off the persisted catalog Status enum and changing
them would expand scope into the catalog contract.
Unifies the catalog integration status display label with the same
"BE (Agent)" taxonomy as the live-probe agent dimension. The
underlying Status enum literal ("wired"), the catalog.metadata.wired
data field, and the filter chip id ("wired" → cell-matrix.tsx:311
status === "wired" filter key) are all PRESERVED — they are
persisted catalog data + filter state contracts that must not move.
Only the user-facing display label on stats-bar, adaptive-stats-bar,
and the filter chip flips.
This collapses the two distinct dashboard "Wired" surfaces (the L1
live-probe dimension and the per-cell build-state count) under one
unified label, matching the user-confirmed Path B.
Unifies the L1 "agent" live-probe display label with the taxonomy
convention established by #5473 (UI (Frontend), E2E, CV, D6 — layer
descriptor in parentheses). The dimension name stays "agent" in code
(PocketBase row keys agent:<slug>, LiveDimension union, keyFor
lookups are all unchanged stable contracts) — only the visible label
changes.
Updates the level-strip L1 badge label and the packages-section
L1-L4 header legend (W → B, "Wired" → "BE (Agent)") so the
level-strip's ToneChip first-letter abbreviation matches the legend
key. Test assertions covering the rendered letter, the legend text,
and the degraded-tone test's local variable follow suit.
The smoke probe was the same HTTP contract as /health on the same
service (200-OK JSON body), so every tick paid two HTTP calls for the
same liveness signal. Drop the /smoke GET + the smoke:<slug> primary
ProbeResult; the driver now emits health:<slug> as the primary and
agent:<slug> via writer side-emit (half the per-tick cost).
The driver's registry kind stays "smoke" so existing YAML configs and
orchestrator family wiring keep routing to this driver — the
emission key (health:<slug>) is the taxonomy contract that matters.
Dashboard:
- Drilldown: D3/e2e row labelled "UI (Frontend)" (was "E2E (Demo)");
Smoke row dropped (CellState.smoke field retained for back-compat).
- Cell badges: short label "UI" (was "E2E") in cell-pieces + unified-cell.
- Legend: D3 = "UI (Frontend): demo page renders in browser (Playwright)"
plus an explicit Health row at the top.
Tests updated to assert new labels:
- cell-drilldown.test.tsx: 6 dimensions (no Smoke); UI (Frontend) label.
- cell-drilldown.lazy-signal.test.tsx: drilldown-badge-ui--frontend- testid.
- cell-pieces.test.tsx + .signal-degrade.test.tsx: badge name "UI".
- unified-cell.test.tsx: mock-badge-UI.
- overlay-selector-integration.test.tsx: "UI" in place of "E2E".
- dashboard-color-matrix.test.tsx: badge: "UI" case names.
- liveness.test.ts: two-call contract (health + agent), regression guard
asserting no smoke:<slug> ever emitted.
Underlying probe key (e2e:<slug>/<feature>) preserved on PocketBase so
historical rows render correctly during the rename window.
Adds the dashboard Ops worker-runs section — family table, worker strip,
run-history drill-down, D0-from-staleness vs D0-from-failure family
annotation with clock glyph, and the per-family silence banner on the
coverage tab. Wires the data layer: DTOs, /api/runs fetchers, polling
hook, and a worker-runs context provider. cell-drilldown / cell-pieces
gain family-aware rendering.
Treat `signal` as optional on legacy LiveBadge rows (neutral 'unknown' marker
when absent), so the trimmed initial projection cannot misrender or throw.
Fetch `signal` on demand for genuinely-failing badges (excluding staleness-
downgraded greens via fail_count), with per-badge loading/error resolution that
distinguishes in-flight, partial-failure (requested-but-absent), and resolved
states. Injection-safe pb.filter OR query; object-valued fields render as JSON.
Banner visibility still derives from projection-safe key+state on the live rows;
detail copy self-fetches `signal` (via pb.filter placeholders, page size = key
count) only when omitted by the trimmed projection, with an identity-tagged cache
(id + transitioned_at) that won't serve a stale signal across content changes.
Thread a `stale` flag (muted, non-authoritative counts during reconnect-with-rows,
distinct from the no-rows loading state), fix the amber-chip/green-D5 cell to
classify into the health dimension, and surface the hook's `degraded` flag in the
header LiveIndicator (offline outranks degraded). Wire `degraded` from
useLiveStatus through to FeatureGrid in dashboard-page.
A connecting status with an empty status map now renders a loading
affordance instead of authoritative checkmark-0 / D0 cells and
zeroed column tallies. Make loading required on the tally type so the
contract is explicit, and default the fail-safe undefined-tally
fallback to loading rather than fake zeros. Adds coverage for the
loading-state quadrants across computeColumnTally /
computeColumnTallyDetail and the OverlayColumnHeader affordance.
A STARTER-row column for an integration that has NO starter (the 7 columns
absent from STARTER_TO_COLUMN) was rendering a grey "✗" with a "no starter
for this integration" tooltip. That mis-communicates the truth: a grey/red
✗ reads as "we expected data and got none" or "a probe ran and failed",
when the reality is "this integration is architecturally unsupported in the
starter row".
buildStarterBadge's mapping-derived !isSupported branch now returns the same
🚫 "Not supported by this framework" treatment depth-chip/unified-cell
already use for unsupported cells. Columns that DO have a starter keep
surfacing their real probe status — a genuinely-red starter still renders
red ✗ and is never masked as 🚫.
PR #5226 added the "Starter" pseudo-category row-group (StarterSection,
using resolveStarterRow/buildStarterBadge) only to CellMatrix in
cell-matrix.tsx. But the live DashboardPage renders the matrix tab via
FeatureGrid (feature-grid.tsx); CellMatrix is reached only through
cells-view.tsx, which no live route imports — it is dead/legacy. So the
Starter row shipped in code but could never render (the served prod bundle
had zero StarterSection/starter-row-/"no starter" strings).
Port the StarterSection into FeatureGrid: render the four fixed sub-rows
(health/agent/chat/interaction) after the feature categories, resolving
each cell via the existing S3 helpers (resolveStarterRow + buildStarterBadge,
5-state vocab) and structurally excluded from the feature rollup/column tally
(never calls renderCell/buildCellModel). Aligned to FeatureGrid's table
structure (Feature col + optional parity ref-depth spacer + categoryColSpan).
Remove the duplicate from the dead CellMatrix and move its dedicated tests
to feature-grid.test.tsx, which exercises the live registry-backed grid.
Render proof: production next build now bakes starter-row-/starter-cell-/
"no starter for this integration" into the served page.js (client + server)
bundles. Full dashboard suite green (815 passed, 1 skipped).
The starter_smoke probe runs hourly (`schedule: "40 * * * *"`), but
STARTER_STALE_AFTER_MS was set to 13h with a comment claiming a 6h cadence
— ~13 missed hourly ticks before amber, defeating the intended two-miss
flip. Re-derive the window from the 1h probe period to 2.5h: strictly
> 2 periods (so two consecutive misses, last row ~3h old, flip amber) yet
< 3h (so a single missed/slow-wake tick, last row ~2h old, stays green,
absorbing a scale-to-zero cold-start). Reconcile both the staleness.ts and
starter_smoke.yml comments to the same hourly basis + 2.5h window in
lockstep (no more "6h" in the starter context). Extend the staleness tests
with explicit hourly-tick boundaries (1 miss → green, 2 misses → amber).
Also drop the inaccurate "hollow" from the not-supported ✗ comments — there
is no hollow render variant; the state renders as grey ✗ text.
Builds the dashboard UI for the starter-smoke dimension: resolveStarterRow
(flat starter:<col>/<level> lookup) + buildStarterBadge implementing the full
5-state cell vocabulary (green ✓, red ✗ smoke-failed, amber ~ stale, gray ?
not-yet-run, grey ✗ not-supported keyed off the S1 mapping), the StarterSection
row-group, rollup-exclusion so starter rows don't skew column aggregates, and a
13h staleness window (> 2× the probe cadence).
UI slot (S3) of the starter-row-group spec (model B).
The depth-distribution row showed permanent-zero D2/D1 rows while the computed
D0 bucket (wired-but-unverified cells) was never rendered, so wired cells
vanished from the row and it never summed to the "Wired" count.
buildCellModel().achievedDepth is typed 0|3|4|5|6 and can never be 1 or 2.
- Remove unreachable d1/d2 from the DepthDistribution type, from
computeDepthDistribution's init, and from the rendered levels array.
- Add a D0 row to the rendered levels so wired-unverified cells are visible
and the distribution sums to the wired-cell count (D6,D5,D4,D3,D0).
- Reconcile section wrapper keys: use each section's stable key instead of the
array index so overlay-toggle reconciliation is correct.
- Document that health/depth/d6 rollups need no dedup: catalogData.cells is
one row per (integration, feature) grid cell (verified: 0 duplicate pairs),
so these per-cell signals are counted exactly once.
Extract the AdaptiveStatsBar aggregate computations out of page.tsx into a
unit-testable pure module (src/lib/page-stats.ts), mirroring the
computeColumnTally pattern, and fix a cluster of correctness bugs:
1. D6 cells were dropped from the depth distribution: DepthDistribution
lacked a `d6` key and the `\`d${depth}\` as keyof` cast produced
`dist["d6"]++ === NaN`. Add `d6` to the type, render a D6 row in
DepthDistributionSection, and replace the cast with an exhaustive
Record<0|3|4|5|6, keyof DepthDistribution> map the compiler checks.
2. d6Stats folded amber (stale/degraded D6) into gray. Count degraded
distinctly and surface it in D6Section.
3. healthStats counted gray (no-data) cells as green, contradicting the
"stats bar matches the matrix" invariant. Track no-data separately and
render it in HealthSection.
4. isSupported is correctly hardcoded true for wired-cell stats: a wired
catalog cell can never be in not_supported_features (generate-registry
resolves those to status "unsupported" before "wired"), so stats and
renderCell cannot diverge. Comment updated to state the invariant.
5. parity_tier was indexed via an unchecked cast (unknown tier →
`undefined++ === NaN`). Validate against the known tier set and skip +
log loud on unknown.
The matrix render path (renderCell/buildCellModel) is untouched.
Add a D5 any-fail fan-out case where the red sub-key is NOT first (greens
then a trailing red) and assert the rollup still resolves red, proving the
worst-state fold is order-independent. Also tighten three imprecise/misleading
comments: clarify the absent-tools D4 worst-state skip, annotate the d6:lgp
aggregate as a distractor not consulted by per-cell resolveD6, and drop the
inapplicable amber-path clause from the aggregate-only gray case.
OverlayToggleBar redefined ALL_OVERLAYS, PRESETS, and OverlayPreset as local
literals duplicating src/lib/overlay-types.ts (drift hazard if an overlay or
preset is added to one but not the other). Replace the local definitions with
re-exports from overlay-types so there is a single source of truth. Behavior
identical; existing component-level imports keep working via the re-export.
ComposedCell.arePropsEqual watched the wrong liveStatus keys: it omitted
agent/chat/tools (which its only consumer, deriveDepth, reads for D2/D4) and
watched an unused smoke key. Watch exactly deriveDepth's reads and fix the
stale "keep in sync with resolveCell" comment.
UnifiedCell rendered a blank cell for a {d6}-only overlay set because d6 is
consumed only by AdaptiveStatsBar and produced no per-cell content. Treat d6
as content-bearing: surface the depth chip + health row (which renders the
per-cell D6 badge). Default overlay set (links/health/depth) is unchanged.
- Rename module-private D5_STATE_RANK to WORST_STATE_RANK (used by both
resolveD5Row and resolveD6Row) and move the misplaced resolveD5Row doc
block onto resolveD5Row.
- Add a test asserting every CATALOG_TO_D5_KEY mapping value is free of the
':' / '/' key delimiters (keyFor's guard did not cover mapping values).
- mergeRowsToMap: fire the collision warning only on genuine state
divergence (rowsAreNoop content compare) instead of reference inequality,
eliminating noisy false warnings.
- isD6Green JSDoc: note the caller only invokes it after D5 is green
(contiguous ladder), so D6 can never be credited over a broken D5.
- computeMaxPossible: treat stub like unshipped (maxPossible=0) so a
not-yet-wired stub cell no longer false-positives isRegression.
Rewrite the two stale live-status keyFor tests that still asserted the old
integration-scoped D6 model (and the wrong e2e-full driver) to assert the
per-cell d6:<slug>/<featureId> shape; expand the two beautiful-chat D5 fan-out
tests to emit all 5 sub-keys so the worst-state fold (not the missing-row path)
is what's exercised; drop the dead unmapped-feature else branch in unified-cell
arePropsEqual (resolvers never read a direct key for unmapped features).
The Feature Matrix rendered every D6 cell in an integration's column from
the aggregate PocketBase row `d6:<slug>` (red whenever ANY cell fails),
so genuinely-green cells (e.g. langgraph-python/voice) rendered red. The
harness already emits per-cell `d6:<slug>/<featureType>` rows over the same
featureType keyspace as D5.
Resolve D6 per-cell through the same CATALOG_TO_D5_KEY bridge D5 uses,
mirroring resolveD5/resolveD5Row exactly: per-sub-row stale-green→degraded
fold before the worst-state fold, and strict handling of a missing mapped
sub-row (no-data unless a present sub-row is red). The aggregate `d6:<slug>`
row no longer drives per-cell rendering. Updated the two memo comparators
(composed-cell, unified-cell) to diff per-cell D6 sub-keys, and the stale
doc-comments that described D6 as an integration-level aggregate.
Hoist a single now (memoized on liveStatus) and thread it into the
render-path buildCellModel so the chip, the regressions/gaps filter, and
the badges all judge staleness against one instant. Previously the render
path defaulted to a fresh Date.now() per cell, so a cell the filter
included could render a different staleness state across a window boundary.
Drop the direct-key d5 fallback in resolveD5Row so the coverage badge
agrees with the chip (cell-model resolveD5) and depth (depth-utils
isD5Green), which both treat an unmapped feature as no-D5. Previously an
unmapped feature with a stray d5:<slug>/<featureId> row rendered a green
badge while the chip showed gray. Also guards the empty-map case.
A present red chat/tools row now pulls D4 down even if its sibling is green,
matching cell-model resolveD4. Single-side present-green cases still achieve
D4 (absent sibling is skipped).
A cell is a regression only when the next rung above achievedDepth has
emitted data (exists && status !== null). Drops the no-data-D5 false
positive (mapped but unemitted D5) while keeping red-below-ceiling and
D3-red regressions.
Fold each contributing row's stale-green to degraded before tone/rollup
derivation so a frozen-green driver no longer reads as healthy. Per-badge
windows: e2e/d5/d6 6h, health/d2/smoke 45m. Thread co-render now from
cell-matrix gaps filter and cells-view stats.
Move the three staleness windows and the byte-identical isStale/isRowStale
duplicate out of cell-model.ts and depth-utils.ts into one lib/staleness.ts.
Type-only import of StatusRow avoids a runtime cycle so live-status can also
consume it. No behavior change.