Summary
The operator dashboard (apps/dashboard) renders a full sidebar from src/lib/nav.ts, but 14 of the declared nav routes have no corresponding App Router page and return a hard 404 ("This page could not be found"). The entire Build group and all of the Infrastructure group are dead, plus Operate → Logs.
Reproduce: open the dashboard and click any item under Build or Infrastructure, e.g. http://<dashboard>/build/agents.
Dead links (14)
There is no /build route directory at all under apps/dashboard/src/app/(admin)/, and no /operate/logs.
Build group (9)
/build/agents
/build/integrations
/build/webhooks
/build/auth
/build/billing
/build/mcp
/build/skills
/build/modules
/build/dashboard-plugins
Infrastructure group (4 — also under the /build/ path prefix)
/build/adapters
/build/database
/build/storage
/build/secrets
Operate group (1)
Everything else (Home, the rest of Operate, Customers, Settings) resolves correctly.
Root cause
nav.ts is the single source of truth for the sidebar + ⌘K palette and mirrors docs/dashboard-ia.md, but the matching page.tsx files were never created for these routes. There is also no automated guard asserting that every nav href resolves to a real route, so the drift went unnoticed.
Backing data already exists
The dashboard API client (apps/dashboard/src/lib/api.ts) already exposes methods for nearly every missing page (api.agents(), api.mcp.servers(), api.skills.list(), api.modulesState(), api.plugins(), api.tools.adapters(), api.db.tables(), api.storage.list(), api.secrets.list(), api.billing.customers(), api.webhooks.endpoints(), api.harnesses.list(), api.tools.listNative()).
Two need a thin client addition where the backend endpoint already exists:
- Logs —
GET /api/v1/logs (services/runtime/internal/server/logs.go) returns {lines: LogLine[]}; LogLineSchema is already defined, only the api.logs() method is missing.
- Auth —
GET /api/v1/oauth/providers (services/runtime/internal/server/oauth.go) returns {providers: {name, configured, default_scopes[]}[]}; needs a schema + api.oauth.providers() method.
So all 14 can ship as real, data-backed pages following the existing operate/* page pattern (server component + graceful empty-state when the runtime endpoint isn't live).
Plan
- Add the two missing API client methods (
logs, oauth.providers).
- Build all 14 pages as data-backed server components matching the established
operate/* pattern (PageHeader + data render + dashed empty-state on ApiError).
- Add an automated route-existence test (dependency-free Node test wired into
pnpm -r test) that walks every href in nav.ts and asserts a matching App Router page exists — so this class of dead link can never regress silently.
- Verify with
pnpm --filter dashboard build (type-checks every page) + the new test + lint/prettier.
Validation contract
- Every
href declared in nav.ts (and via plugins) resolves to a rendered page — no 404s from the sidebar or ⌘K.
- Each new page renders its data when the runtime endpoint returns data, and a clean empty-state (not a crash) when the endpoint is unavailable/returns an error.
- Customers/* pages remain MT-gated (unchanged behavior).
- The route-existence test fails if any nav link lacks a page, and passes once all 14 are added.
Summary
The operator dashboard (
apps/dashboard) renders a full sidebar fromsrc/lib/nav.ts, but 14 of the declared nav routes have no corresponding App Router page and return a hard 404 ("This page could not be found"). The entire Build group and all of the Infrastructure group are dead, plus Operate → Logs.Reproduce: open the dashboard and click any item under Build or Infrastructure, e.g.
http://<dashboard>/build/agents.Dead links (14)
There is no
/buildroute directory at all underapps/dashboard/src/app/(admin)/, and no/operate/logs.Build group (9)
/build/agents/build/integrations/build/webhooks/build/auth/build/billing/build/mcp/build/skills/build/modules/build/dashboard-pluginsInfrastructure group (4 — also under the
/build/path prefix)/build/adapters/build/database/build/storage/build/secretsOperate group (1)
/operate/logsEverything else (Home, the rest of Operate, Customers, Settings) resolves correctly.
Root cause
nav.tsis the single source of truth for the sidebar + ⌘K palette and mirrorsdocs/dashboard-ia.md, but the matchingpage.tsxfiles were never created for these routes. There is also no automated guard asserting that every navhrefresolves to a real route, so the drift went unnoticed.Backing data already exists
The dashboard API client (
apps/dashboard/src/lib/api.ts) already exposes methods for nearly every missing page (api.agents(),api.mcp.servers(),api.skills.list(),api.modulesState(),api.plugins(),api.tools.adapters(),api.db.tables(),api.storage.list(),api.secrets.list(),api.billing.customers(),api.webhooks.endpoints(),api.harnesses.list(),api.tools.listNative()).Two need a thin client addition where the backend endpoint already exists:
GET /api/v1/logs(services/runtime/internal/server/logs.go) returns{lines: LogLine[]};LogLineSchemais already defined, only theapi.logs()method is missing.GET /api/v1/oauth/providers(services/runtime/internal/server/oauth.go) returns{providers: {name, configured, default_scopes[]}[]}; needs a schema +api.oauth.providers()method.So all 14 can ship as real, data-backed pages following the existing
operate/*page pattern (server component + graceful empty-state when the runtime endpoint isn't live).Plan
logs,oauth.providers).operate/*pattern (PageHeader + data render + dashed empty-state onApiError).pnpm -r test) that walks everyhrefinnav.tsand asserts a matching App Router page exists — so this class of dead link can never regress silently.pnpm --filter dashboard build(type-checks every page) + the new test + lint/prettier.Validation contract
hrefdeclared innav.ts(and via plugins) resolves to a rendered page — no 404s from the sidebar or ⌘K.