Replay the Option B runtime-config spike as a vitest integration test
that guards the no-rebuild env switching property going forward:
- `next build` once with no per-env URL env vars (only a sentinel
OPS_BASE_URL so next.config.ts's rewrites() can validate; Next
evaluates rewrites at build, not only at start, so a placeholder
here is unavoidable — the assertions don't depend on it).
- `next start` twice, each on a fresh port and a DIFFERENT
POCKETBASE_URL / SHELL_URL / OPS_BASE_URL set.
- Fetch `/` on each boot and extract the inlined
`window.__SHOWCASE_CONFIG__={...}` JSON from the served HTML.
- Assert env-A URLs on the first boot and env-B URLs on the second
boot of the SAME built artifact. If anyone re-introduces a
build-time URL bake, the second boot's HTML still shows env-A
values and this test fails.
Test lives at `showcase/shell-dashboard/tests/runtime-env-switch.spike.test.ts`
and is picked up via a new vitest include for `tests/**/*.spike.test.ts`
(the default include is `src/**/*.test.{ts,tsx}` and the integration-
weight spike doesn't fit there). The `.spike.test.ts` suffix keeps
the include narrow so the visual snapshot suite under
`tests/visual/` stays out.
Total wall time locally: ~12s (build ~2s warm with prebuild data
generation already run; two boots ~10s combined). Heavy enough to
gate behind a `tests:integration` script in CI rather than running
on every push.
The e2e-demos probe writes per-demo rows keyed as e2e:<slug>/<featureId>
but resolveCell looked up e2e_smoke:<slug>/<featureId>. The mismatch
caused every per-demo depth chip to show gray (no data), leaving cells
stuck at D2. Fix: read the e2e dimension the probe actually writes.
Replace the static status.json feed with a live PocketBase subscription
(useLiveStatus / useLastTransition hooks + live-status lib + pb client),
add vitest + playwright visual test setup, wire feature-grid + cell
pieces + badges to the live data model. Adds Dockerfile + .dockerignore
for Railway deployment of the dashboard.