Commit Graph

305 Commits

Author SHA1 Message Date
Jordan Ritter 042e79e259 fix(showcase): dashboard pending-surface never masks a real red — severity-first comm-error decode, stale-observedAt guard, pending chip 2026-06-07 22:40:13 -07:00
github-actions[bot] c023a03fac style: auto-fix formatting 2026-06-08 05:23:16 +00:00
Jordan Ritter fe3af73086 fix(mastra): gen-a2ui throw + role normalizer + cmdk bump; probe-YAML comment fixes; dashboard validateCell tag enforcement 2026-06-07 22:19:47 -07:00
Jordan Ritter f0edcd5d94 fix(showcase): convey timeout_ms to fleet worker and stop rendering unprobed ms-agent-harness-dotnet 2026-06-07 10:00:47 -07:00
Jordan Ritter 5387e4de38 fix(showcase): add D6 entry to coverage dashboard legend 2026-06-06 02:30:36 -07:00
Jordan Ritter ffc60ff1ca feat(showcase): surface fleet pool comm-errors (unreachable overlay) on the dashboard 2026-06-05 10:38:41 -07:00
Jordan Ritter 6161047933 fix(showcase-dashboard): degrade LiveBadge gracefully when row lacks signal
Treat `signal` as optional on legacy LiveBadge rows (neutral 'unknown' marker
when absent), so the trimmed initial projection cannot misrender or throw.
2026-06-05 01:46:12 -07:00
Jordan Ritter 264f88f2b1 feat(showcase-dashboard): lazy-load signal in CellDrilldown
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.
2026-06-05 01:46:11 -07:00
Jordan Ritter c5a6db9991 fix(showcase-dashboard): self-fetch signal in DiscoveryAuthBanner
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.
2026-06-05 01:46:11 -07:00
Jordan Ritter e76129ca55 feat(showcase-dashboard): add stale tally, fix amber/D5 dimension, surface degraded
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.
2026-06-05 01:46:11 -07:00
Jordan Ritter 82fcd6a2a6 perf(showcase-dashboard): trim initial status projection and harden the live SSE path
Drop the heavy `signal` field from the initial PocketBase fetch via a
STATUS_LIST_FIELDS projection + skipTotal + length-based bounded-concurrency
pagination, restoring fast first paint. Harden the realtime path: flushPending
teardown guards, keyless-delete id->key buffer collapse (last-write-wins),
SSE-delta signal-presence upsert, and a `degraded` flapping flag that clears on
heartbeat-success and resets on terminal error. ConnectionStatus deduped.
2026-06-05 01:45:53 -07:00
Jordan Ritter 0173ae1f28 test(showcase-dashboard): fix autocancel regression test isolation
Restore stubbed globals (EventSource, localStorage, window config) in
afterEach via vi.stubGlobal/unstubAllGlobals + snapshot, and unmount the
hook in a finally so the 30s heartbeat interval and its pb singleton don't
leak past the test onto the about-to-close in-process server.

Also normalize the fetchInitial page merge to
[first.items, ...rest.map((r) => r.items)].flat() for clarity (behavior
identical to the prior heterogeneous flatMap).
2026-06-04 23:21:37 -07:00
Jordan Ritter ccbec009d7 fix(showcase-dashboard): disable PB SDK autocancel on concurrent initial fetch
The PocketBase JS SDK derives a request key from method+path and
auto-cancels any in-flight request that shares it. useLiveStatus's
fetchInitial fans pages 2..N out concurrently at the same path
(/api/collections/status/records), so every page after the first
cancelled its predecessor — the cancelled promises rejected, Promise.all
rejected, and the hook dropped to OFFLINE. Pass requestKey:null on the
page-1 read, every fan-out page, and the heartbeat ping so all
concurrent same-path reads complete.

Add a real-SDK regression test (the mocked suite never exercised the SDK
so it missed this): an in-process http server replays paged status
records with per-page latency, the production getPb() client drives the
real hook, and it asserts "live" with all rows. RED without requestKey:null.
2026-06-04 23:03:40 -07:00
Jordan Ritter cd45c873a9 fix(showcase-dashboard): show loading state instead of authoritative zeros during initial fetch
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.
2026-06-04 23:03:40 -07:00
Jordan Ritter 5818d4ce1c perf(showcase-dashboard): order-safe concurrent paged initial status fetch
Replace the single serial-paginating getFullList with a concurrent
page-1 fetch + Promise.all fan-out over the remaining pages, merged by
array index so results are deterministic regardless of resolution
order. Forward a stable sort:"id" on every page so concurrent requests
share one ordering. Wrap the initial setRows in startTransition to
avoid a synchronous full-matrix re-render. The snapshot is best-effort;
the live SSE subscription reconciles anything created in the brief
fetch->subscribe window.
2026-06-04 23:03:40 -07:00
Jordan Ritter dc76bb4220 fix(showcase): render absent-starter columns as 🚫 unsupported, not grey/red ✗
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 🚫.
2026-06-04 22:26:36 -07:00
Jordan Ritter be8a5c04ea fix(showcase/dashboard): render Starter row-group in the live FeatureGrid (was in dead CellMatrix)
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).
2026-06-04 01:17:59 -07:00
github-actions[bot] 6a7199e3da style: auto-fix formatting 2026-06-04 00:24:11 -07:00
Jordan Ritter 01079062a3 test(showcase/dashboard): cross-package starter column-slug equality
Only counts (12) were asserted on each side, so a future slug rename on
one side would silently flip a column to grey "not-supported" while both
counts stayed 12. Add a dashboard-side test that fs-reads the harness
STARTER_TO_COLUMN value set (the producer-side remap lives in a separate
pnpm workspace, so it cannot be imported) and asserts SET-EQUALITY with the
dashboard STARTER_COLUMNS. Mirrors the harness starter-mapping-drift fs-read
pattern; reds if either side renames a column slug without the other.
2026-06-04 00:24:11 -07:00
Jordan Ritter 92c2f3d06c fix(showcase/dashboard): correct starter staleness window to hourly cadence
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.
2026-06-04 00:24:11 -07:00
Jordan Ritter 71de409884 feat(showcase/dashboard): add Starter row-group with 5-state smoke-health vocabulary
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).
2026-06-04 00:24:11 -07:00
github-actions[bot] 33d7eef4e4 style: auto-fix formatting 2026-06-03 00:58:59 +00:00
Jordan Ritter 9125ec5b81 fix(showcase): gate Coverage D6 badge + stats by depth ladder; gated indicator only on genuine lower-rung failure 2026-06-02 17:57:48 -07:00
Jordan Ritter 7d097feabc fix(showcase): resolve dashboard links to real shell host via server-threaded shellUrl 2026-06-02 17:57:47 -07:00
github-actions[bot] a8f1544286 style: auto-fix formatting 2026-06-02 20:51:16 +00:00
Jordan Ritter 7b0e663b22 fix(showcase): render reachable depth buckets (D0..D6) in stats bar, drop dead D1/D2
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.
2026-06-02 13:30:35 -07:00
Jordan Ritter 13d7fa5ee5 fix(showcase): correct aggregate-stats divergence from per-cell model
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.
2026-06-02 13:19:43 -07:00
Jordan Ritter 377b56cb19 test(showcase): harden D6 rollup tests — order-independent worst-state, comment precision
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.
2026-06-02 13:06:01 -07:00
Jordan Ritter 07abb37ea4 fix(showcase): thread shared now + periodic staleness re-render in dashboard page 2026-06-02 13:05:59 -07:00
Jordan Ritter 952209977e test(showcase): harden useOverlays mocks and cover baseline + selectProbe
Stub window.location explicitly via Object.defineProperty (instead of the
global) so the hook's window.location.hash read-path is genuinely exercised
and robust even if window !== globalThis. Add coverage for setTab persistence,
the #baseline parse resolving to the baseline tab, and selectProbe leaving the
tab and hash consistent (ops probe drilldown).
2026-06-02 13:05:58 -07:00
Jordan Ritter b6f865adce fix(showcase): persist overlays in setTab for storage symmetry
setTab wrote the URL hash but, unlike toggle/updateOverlays, never called
saveToStorage, so a tab switch dropped overlay persistence asymmetrically.
Persist the current overlay set in setTab so the user's overlay selection
survives tab switches and reloads. Covered by a red-green test in the
useOverlays suite.
2026-06-02 13:05:58 -07:00
Jordan Ritter e6da6ba3ed refactor(showcase): re-export overlay/preset definitions from overlay-types
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.
2026-06-02 13:05:58 -07:00
Jordan Ritter e408489313 fix(showcase): correct cell renderer memo keys and d6 overlay blank matrix
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.
2026-06-02 13:05:56 -07:00
Jordan Ritter 7f5c3ac16e fix(showcase): correct live-status/depth-utils CR findings
- 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.
2026-06-02 13:05:55 -07:00
Jordan Ritter 461621af0e fix(showcase): return effective (downgraded) row from cell-model resolvers
resolveD4/D5/D6 returned the RAW status row in `.row` while `.status` was
derived from the stale-downgraded effective state, so a stale-green fold
reported `.row.state === "green"` but `.status === "amber"`. Store the
effective (downgraded) row instead so `.row.state` agrees with `.status`,
mirroring the invariant in live-status.ts `buildBadge`. Also replace
resolveD4's `worstState!`/`winner!` non-null assertions with a guard like
resolveD5/D6, and pin the resolveD3 producer-invariant (D1/D2 gate is
enforced upstream; buildCellModel never reads health:/agent: rows) with a
characterization test and comment.
2026-06-02 13:05:54 -07:00
Jordan Ritter c32a877a91 test(showcase): add dashboard cell color/rollup matrix 2026-06-02 12:37:55 -07:00
Jordan Ritter a510aa08d6 feat(showcase): enable Depth (and D6) overlays by default 2026-06-02 12:37:55 -07:00
Jordan Ritter 377b7e8163 test(showcase): complete D6 per-cell test migration
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).
2026-06-02 11:35:51 -07:00
Jordan Ritter c64aebc422 fix(showcase): resolve D6 per-cell instead of integration aggregate
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.
2026-06-02 09:47:34 -07:00
Jordan Ritter f565f73add chore(showcase/shell-dashboard): apply oxfmt formatting to runtime-config 2026-06-01 08:18:10 -07:00
Jordan Ritter 1b0aa557ba fix(showcase/shell-dashboard): keep client ops fetch same-origin via /api/ops
The staging D6 dashboard rendered no data because the client did a direct
cross-origin fetch to the harness URL — CORS-blocked and the wrong path
(`/probes` instead of `/api/probes`). Root cause: getRuntimeConfig() sourced
the client RuntimeConfig.opsBaseUrl from the server proxy target OPS_BASE_URL
(the harness URL), which the root layout serialized into
window.__SHOWCASE_CONFIG__, so resolveBaseUrl() used it as the fetch base
instead of falling through to the same-origin /api/ops proxy.

Decouple the two: the client direct override is now an explicit, opt-in,
client-intended env var (NEXT_PUBLIC_OPS_DIRECT_BASE_URL) that defaults to ""
in every environment, so the client lands on /api/ops. The Route Handler's
server-only OPS_BASE_URL read and its sentinel behavior are unchanged.

- showcase/shell-dashboard/src/lib/runtime-config.ts: opsBaseUrl now read from
  NEXT_PUBLIC_OPS_DIRECT_BASE_URL (default ""), not OPS_BASE_URL; no sentinel.
- showcase/shell-dashboard/src/lib/ops-api.ts: update resolveBaseUrl comments
  to describe the client override vs server proxy target distinction.
- showcase/shell-dashboard/src/app/layout.tsx: note opsBaseUrl is the client
  override, never the harness URL.
- tests: encode the contract (client falls through to /api/ops when no direct
  override; server proxy target does not leak into the client config), incl. an
  end-to-end regression in the env-switch spike test.
2026-06-01 08:14:03 -07:00
github-actions[bot] 688f2d31cf style: auto-fix formatting 2026-06-01 14:47:38 +00:00
Jordan Ritter b19c27f045 fix(showcase/shell-dashboard): resolve OPS_BASE_URL at runtime via /api/ops route handler
The /api/ops/* proxy was a next.config.ts rewrite, which Next.js evaluates at
`next build` and freezes into the prebuilt Docker image. The shared CI build
bakes a placeholder OPS_BASE_URL (http://ops.invalid) to satisfy a
throw-if-unset guard, so every deploy of the single :latest image proxied to a
dead host regardless of its runtime env — /api/ops/* returned 500
(ENOTFOUND ops.invalid), the Feature Matrix health overlay got no probe data,
and every cell downgraded to amber (zero green) despite green backend data.
The same freeze also baked the production harness URL into the shared image,
so even a correct rebuild would point staging at the prod harness.

Replace the build-time rewrite with a Route Handler at
src/app/api/ops/[...path]/route.ts that reads process.env.OPS_BASE_URL at
REQUEST time (force-dynamic, never statically cached) and proxies
/api/ops/<path> -> ${OPS_BASE_URL}/api/<path>, forwarding method, query
string, headers, and body. A missing OPS_BASE_URL now returns a clear 503
instead of a build throw. The build no longer depends on OPS_BASE_URL.

This fixes both traps with one image: each environment resolves its own
runtime OPS_BASE_URL from the same artifact, no rebuild. Requires the
dashboard image to rebuild + redeploy. Harness path is now /api/probes.
2026-06-01 07:46:27 -07:00
Jordan Ritter f7203f3368 fix(showcase): thread shared now through cell-matrix render path
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.
2026-05-30 21:34:48 -07:00
Jordan Ritter 36f316b24d fix(showcase): resolveD5Row returns null for unmapped features
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.
2026-05-30 21:34:39 -07:00
Jordan Ritter 82f79b211f fix(showcase): unify resolveD5Row strict-missing + honest stale badge row
resolveD5Row now mirrors cell-model.ts resolveD5's STRICT missing-sub-row
handling: a mapped multi-key D5 family is credited green only when EVERY
sub-row is present. A missing sub-row collapses a present green/degraded
fold to no-data (gray), while a present red still dominates. Previously a
family with one present-green and missing siblings rendered a false-green
badge while buildCellModel's D5 rendered gray.

buildBadge now returns the EFFECTIVE (downgraded) row so badge.row.state
agrees with badge.tone — a stale-green badge had tone amber but
row.state green, a latent false-green for any consumer reading .row.state.
The spread preserves fail_count/first_failure_at/observed_at/signal so
drilldown metadata is unaffected; only .state becomes honest.
2026-05-30 21:01:44 -07:00
Jordan Ritter 79a22fb18d test(showcase): cover computeColumnTallyDetail health-dimension branch
Add cases for green-D3+D4+red-D5 and green-D3+red-D4, both landing in the
red bucket with dimension 'health'. No production change.
2026-05-30 20:52:07 -07:00
Jordan Ritter c506d8badf fix(showcase): deriveDepth D4 uses worst-state-wins instead of OR
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).
2026-05-30 20:50:45 -07:00
Jordan Ritter afd3bdba36 fix(showcase): require emitted data above achievedDepth for isRegression
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.
2026-05-30 20:49:11 -07:00
Jordan Ritter 8c61713d66 fix(showcase): downgrade stale-green rows in resolveCell + resolveD5Row
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.
2026-05-30 20:46:32 -07:00