The "no demo" X marks are text-center while depth chips and overlay
content were text-left, causing visual misalignment across all overlay
views. Center both the td and the ComposedCell flex column.
Split inline type imports into separate import-type statements per
oxlint consistent-type-specifier-style rule. Prefix unused connection
parameter. Apply oxfmt formatting.
Stats bar shows health/parity/docs summary counts that appear/hide
based on active overlays. Legend renders color-coded keys for each
active overlay layer.
OverlayColumnHeader shows health strip, parity badge, and tally based
on active overlays. RefDepthColumn is a frozen column pinned after the
feature name that appears when the parity overlay is active.
Stacks 4 overlay content layers (Links, Depth, Health, Docs) into a
single matrix cell. Each layer renders only when its overlay is active,
driven by the shared overlays Set from useOverlays.
Pill-style toggle bar for 5 overlay layers with 3 presets (Default,
Developer, Ops). Each pill shows active state; presets apply curated
overlay combinations with one click.
- Update collision-avoidance comment in showcase_deploy.yml to remove
starter-specific examples (service/starter collision no longer possible)
- Fix showcase_keep-alive.yml description: pings showcase services, not starters
- Remove "starter" from catalog-types.ts manifestation union type
- Remove dead starter cell skip logic from cell-matrix.tsx cellIndex builder
- Update depth-utils.ts comment (defensive null guard, not starter-specific)
- Remove starter-specific test cases from depth-utils and cell-matrix tests
The status collection now has ~1300+ records across all probe
dimensions. The paginated getList fetches in rowid order, so
the 500 cap loaded smoke/health/agent/d5/chat/tools records
but truncated all e2e per-cell records (created later by the
6-hourly e2e-demos probe). Without e2e records in the
LiveStatusMap the depth ladder stopped at D2 for every cell.
The mousedown-based click-outside handler fired before the cell button's
onClick, clearing selectedCell and preventing the toggle-close path from
executing. Now the handler checks if the click target is inside a cell
button and defers to the button's own onClick for toggle logic.
Add clickable cell drilldown showing all 5 badge dimensions (health,
e2e, smoke, d5, d6) with tone, label, tooltip, and failure metadata
(fail_count, first_failure_at, signal payload) for red/amber badges.
Enhance Gaps filter to surface cells with RED probes as functional
gaps alongside structural gaps (unshipped cells).
Add Failures stat to StatsBar counting cells with red rollup.
Pass connection status through to CellMatrix for drilldown rendering.
Compare live achieved depth against CatalogCell.max_depth to detect
regressions. Wire the Regressions filter chip to actually filter rows,
compute real regression count in stats bar, and remove the hardcoded
empty state placeholder.
## Summary
Surface the U/W/C/T tier mapping inline next to the L1-L4 Status column
header on the Packages tab, so operators can read the strip without
cross-referencing docs.
| Letter | Label | Probe dimension |
|--------|-------|-----------------|
| U | Up | `health:<slug>` |
| W | Wired | `agent:<slug>` |
| C | Chats | `chat:<slug>` |
| T | Tools | `tools:<slug>` |
## Test plan
- [x] New test asserts the legend renders with exact text `(U=Up,
W=Wired, C=Chats, T=Tools)` and `data-testid="packages-uwct-legend"`
- [x] All 6 packages-section tests pass
- [x] `npx tsc --noEmit` clean
Surface the U/W/C/T tier mapping inline next to the L1-L4 Status column
header so operators don't need to cross-reference docs to read the strip.
Maps to the underlying probe dimensions per level-strip.tsx:
U = Up (health:<slug>)
W = Wired (agent:<slug>)
C = Chats (chat:<slug>)
T = Tools (tools:<slug>)
CR R1 follow-ups on top of the /api/ops proxy fix.
Bucket (a) — must-fix:
- Dockerfile: declare ARG/ENV OPS_BASE_URL in the builder stage. next.config.ts
evaluates rewrites() at build time and throws if OPS_BASE_URL is unset, which
aborted `next build` in CI. Mirrors the existing NEXT_PUBLIC_SHELL_URL /
NEXT_PUBLIC_POCKETBASE_URL pattern.
- showcase_deploy.yml: pipe OPS_BASE_URL through to docker build for the
shell-dashboard matrix entry, defaulting to the production
showcase-ops-production.up.railway.app URL.
- next.config.ts: strip trailing slashes from OPS_BASE_URL before constructing
the rewrite destination, matching the same normalization in
src/lib/ops-api.ts:resolveBaseUrl so server-side rewrite and client-side
fetch agree on the URL shape.
- src/lib/ops-api.ts: treat empty / whitespace NEXT_PUBLIC_OPS_BASE_URL as
"no override". `??` only short-circuits on null/undefined, so an env var set
to "" silently produced baseUrl="" and URLs of the form "/probes" with no
/api/ops prefix.
- use-probes.integration.test.tsx: snapshot+restore process.env.NEXT_PUBLIC_OPS_BASE_URL
in beforeEach/afterEach so tests never leak env state. Strengthen the proxy
contract assertions: lock toHaveBeenCalledTimes(1), assert method=GET,
cache=no-store, accept JSON header, and signal is an AbortSignal. Tighten
the 404 regression to assert the canonical ensureOk message shape so a
refactor that changes the format trips the test.
Bucket (b) — applied since the diff stayed focused:
- triggerProbe: add cache:"no-store" for parity with the GET fetches.
- fetchProbeDetail / triggerProbe: throw early when id is empty so callers
get a clean error instead of a request to /probes//... .
- ensureOk: bump body-truncation cap from 200 to 500 chars and append a
`[truncated, N bytes total]` marker so operators can see they're missing
tail bytes when the server returns a long HTML/stack-trace body.
- ops-api.ts: drop dev-loop review-cycle tag prefixes (R2-C.3, R3-C, R3-D.1)
from comments. Keep the actual rationale.
- ops-api.ts header docstring: clarify that NEXT_PUBLIC_OPS_BASE_URL is read
live at runtime in this codebase (SSR + tests), not just statically inlined
into the client bundle.
Verified:
- Tests: vitest run — 26 files, 293 passed, 1 skipped (no test count change).
- Typecheck: tsc --noEmit clean.
- Lint + format: oxlint + oxfmt clean on changed files.
- Local Docker build: `docker build --build-arg OPS_BASE_URL=https://...` succeeds.
Without --build-arg the build fails with "OPS_BASE_URL must be set" as
expected, confirming the fix is load-bearing.
Status tab was rendering "All probes idle. No upcoming runs scheduled."
even though showcase-ops `/api/probes` returns 9 probes.
Root cause:
* `lib/ops-api.ts` falls back to `/api/ops` when no explicit baseUrl and no
`NEXT_PUBLIC_OPS_BASE_URL` is set. `NEXT_PUBLIC_OPS_BASE_URL` is not
set on Railway shell-dashboard.
* `next.config.ts` was empty — there was no rewrite forwarding
`/api/ops/*` to the showcase-ops service. So the browser hit
`dashboard.showcase.copilotkit.ai/api/ops/probes` and got a 404 from
the dashboard origin itself.
* Direct cross-origin fetch from the browser was not an option either:
showcase-ops has no CORS allowlist.
Fix:
* Add a Next.js rewrite that forwards `/api/ops/:path*` to
`${OPS_BASE_URL}/api/:path*` server-side. The browser only ever sees
same-origin calls, and `OPS_BASE_URL` stays out of the client bundle.
* `OPS_BASE_URL` is required at build/start — fail loudly if unset
rather than silently rendering an empty status table.
* Update `lib/ops-api.ts` doc comment to reflect that `/api/ops` is now
the deterministic same-origin proxy path (contract), not a "guess".
* Add `src/hooks/use-probes.integration.test.tsx` — exercises the hook
through the real `lib/ops-api` client to a mocked `fetch`. Asserts the
default URL is exactly `/api/ops/probes`, parses real probe data, and
surfaces a useful error when the proxy 404s. The pre-existing
`lib/ops-api.test.ts` and `use-probes.test.ts` each mock at a different
layer, so neither caught this contract gap.
Operational follow-up: `OPS_BASE_URL` set on Railway shell-dashboard service
points to `https://showcase-ops-production.up.railway.app`. Auto-update
will pick up the rewrite on the next image rebuild.
Small, collapsible disclosure panel above StatsBar on the Cells tab with
plain-English definitions of the D0–D6 chip layers. Default collapsed;
open/closed state persists across navigations via
localStorage["chip-explainer-open"]. Footer link points to the Notion
plain-English page for deeper detail.
The Cells tab is consulted dozens of times a day by operators who already
know what the chips mean; the explainer is for first-time viewers and
rare refreshers, so the trigger is a small text button and the default
is collapsed to keep the layout clean.
R4-B.1: useProbes effect now calls setData(null) on dep change to mirror
useProbeDetail's CR-B1.4 behavior. Without this, the dashboard renders
stale list data from the previous baseUrl until the new fetch resolves.
R4-B.6: useTriggerProbe checks controller.signal.aborted after the await
resolves. If a back-to-back trigger aborted the controller after the
fetch already returned, honor the R3-C.1 supersession contract and
resolve to null instead of returning the stale TriggerResponse.
R3-B.1: status-table lastRunTone() returned green for partial-pass runs
(failed=0, passed<total), reintroducing the misread that R2-D.4 fixed in
status-runs-list. Schedule table is the most-viewed surface, so this is
the highest-impact regression. Mirror runs-list semantics: amber tone +
"P/T (S skipped)" result text. Also explicit "P/T pass (F fail)" for
failed runs so the column reads consistently with the tone.
R3-B.2: StatusDetailPanel durations filter used \`typeof d === "number"\`
which admits NaN/Infinity (typeof NaN === "number"). The sparkline then
computed Math.min(...durations) = NaN and emitted "NaN,NaN" coords in
the polyline. Switch to Number.isFinite() so non-finite durations are
dropped.
Tests added (red-green TDD):
- status-table: amber tone + "skipped" text for partial pass; explicit
"pass (N fail)" text for failed; "N/N pass" for fully-passed
- status-detail-panel: NaN/Infinity durations don't appear in sparkline
R3-C.1: useTriggerProbe.trigger now returns Promise<TriggerResponse | null>.
The supersession path resolves to `null` (typed) instead of the previous
`undefined as unknown as TriggerResponse` cast, which lied about the
return shape and would crash any caller that read fields off the result.
R3-C.2: useProbeDetail clears `error` on id change so the detail panel no
longer renders a stale error message under a freshly-selected probe header.
R3-C.3: useProbes clears `error` on dep change (e.g. baseUrl swap) so a
prior dep tuple's failure does not persist under the new one.
R3-C.4: useTriggerProbe clears `error` at the start of each trigger call
so a successful retry leaves no sticky error from the prior failure.
Bonus: parseJson now attaches the original parse error as `cause` on the
wrapper so debuggers can walk back to the SyntaxError name/stack without
re-parsing the wrapped message.
Tests: red-green tests added for each behavior; full file suite 49/49.
R3-D.2: the toggle-close branch on the Trigger button forgot to reset
pickerOpen and selected, while outside-click and Escape both did. So
toggling the menu closed and reopening it showed ghost checks from a
prior session. Extract a closeMenu() helper used by all three close
paths so the invariant is structural, not duplicated.
Bonus: add aliveRef guard so async rejections from onTrigger that
resolve after unmount don't trigger setState-on-unmounted warnings.
R3-D.1: fetchProbes and fetchProbeDetail are polled every ~10s for live
status data. Without an explicit cache directive the browser and Next.js
App Router fetch wrapper may serve stale responses, defeating the poll.
Add cache: 'no-store' to both GET call sites and cover with tests.
The degraded-state tooltip read "stale — last pass @ <observed_at>", but
observed_at is the most recent producer tick (when the row was last
*seen* in any state) — for a degraded row that is when degradation was
last observed, NOT when it last went green. Operators reading "last
pass @ ..." would mis-triage by treating that timestamp as the last
green tick.
Change copy to "stale — last seen @ <observed_at>" and add a covering
test asserting the new wording (and the absence of "last pass").