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.
- Renamed showcase/shell-internal/ → showcase/shell-dashboard/ (git mv for history).
- Updated package name to @copilotkit/showcase-shell-dashboard (+ lockfile).
- Updated external references: showcase/scripts/{probe-docs,generate-status}.ts comments and showcase/README.md.
- Added dispatch + filter + ALL_SERVICES entry for shell-dashboard in .github/workflows/showcase_deploy.yml.
- Provisioned Railway service showcase-shell-dashboard (id 4d5dfd74-be61-40b2-8564-b53b7dd4c15b):
- image source ghcr.io/copilotkit/showcase-shell-dashboard:latest
- env vars OPENAI_API_KEY, ANTHROPIC_API_KEY, PORT copied from showcase-shell
- custom domain dashboard.showcase.copilotkit.ai attached (CNAME → 6u9icvje.up.railway.app)
- image will be built and pushed via showcase_deploy.yml on merge