Skip to content

feat: Plan 9 PR A — dashboard shell + CP static serve - #26

Open
messagesgoel-blip wants to merge 3 commits into
mainfrom
feat/dashboard-pr-a
Open

feat: Plan 9 PR A — dashboard shell + CP static serve#26
messagesgoel-blip wants to merge 3 commits into
mainfrom
feat/dashboard-pr-a

Conversation

@messagesgoel-blip

@messagesgoel-blip messagesgoel-blip commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Scaffold top-level dashboard/ (Vite + React + TanStack Query/Router)
  • Auth: VITE_AUTH_MODE=apikey|oidc, session token + tenant selector → Authorization + X-Tenant-Id
  • Placeholder provider / agent-builder / admin shells
  • Control plane mounts dashboard/dist (or DASHBOARD_DIST_PATH) after API routes without shadowing /v1, /webhooks, /healthz
  • CI: new Dashboard job (typecheck + vitest + build)
  • Vitest + CP unit coverage for apiFetch headers and SPA serve

Test plan

  • cd dashboard && npm run typecheck && npm test && npm run build
  • cd control-plane && node --test --import tsx src/dashboard/spaServe.test.ts
  • CI Dashboard + Gate green
  • Optional: build dashboard, set DASHBOARD_DIST_PATH, hit / via CP

@coderabbitai review

Summary by CodeRabbit

  • New Features
    • Added a web dashboard with Provider, Agent Builder, and Admin areas.
    • Added API-key and OIDC sign-in options, tenant selection, session handling, and sign-out.
    • Added protected navigation and responsive dashboard layouts.
    • The control plane now serves the built dashboard, including static assets and single-page navigation.
  • Documentation
    • Added setup, development, deployment, configuration, and authentication guidance.
  • Tests
    • Added coverage for authentication, API requests, PKCE, dashboard serving, and routing behavior.
  • Chores
    • Added automated dashboard typechecking, testing, and production builds.

Add Vite/React dashboard with API-key and OIDC PKCE auth scaffolding,
X-Tenant-Id client wiring, provider/agent-builder/admin shells, and
control-plane SPA static hosting behind /v1 and /healthz.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef31014b-16b6-41b8-971b-15c17c085135

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc61d6 and d057df0.

📒 Files selected for processing (13)
  • control-plane/src/config.ts
  • control-plane/src/dashboard/spaServe.test.ts
  • control-plane/src/dashboard/spaServe.ts
  • dashboard/README.md
  • dashboard/package.json
  • dashboard/src/api/client.test.ts
  • dashboard/src/api/client.ts
  • dashboard/src/auth/oidc.ts
  • dashboard/src/auth/session.ts
  • dashboard/src/pages/AuthCallbackPage.tsx
  • dashboard/src/pages/LoginPage.tsx
  • dashboard/src/styles/index.css
  • dashboard/vite.config.ts

Walkthrough

Added a React/Vite dashboard with API-key and OIDC authentication, protected routes, tenant selection, API helpers, control-plane SPA serving, integration tests, documentation, and CI validation.

Changes

Dashboard foundation

Layer / File(s) Summary
Dashboard package and build setup
dashboard/package.json, dashboard/index.html, dashboard/vite.config.ts, dashboard/tsconfig*.json, dashboard/vite-env.d.ts
Added the dashboard package, Vite development proxy, production build output, strict TypeScript settings, environment typing, and test configuration.
Application bootstrap and presentation
dashboard/src/main.tsx, dashboard/src/styles/index.css, dashboard/src/test/setup.ts
Added React provider composition, router rendering, global styles, and Testing Library setup.
Repository and CI support
.gitignore, .github/workflows/ci.yml, dashboard/README.md
Added build artifact exclusions, dashboard typecheck/test/build steps, and dashboard operation documentation.

Authentication and API client

Layer / File(s) Summary
Authentication state and storage
dashboard/src/config.ts, dashboard/src/auth/session.ts, dashboard/src/auth/AuthProvider.tsx
Added authentication mode configuration, session storage helpers, tenant state, token state, persistence, and sign-out behavior.
OIDC and PKCE helpers
dashboard/src/auth/oidc.ts, dashboard/src/pages/LoginPage.tsx, dashboard/src/pages/AuthCallbackPage.tsx
Added PKCE generation, authorization URL construction, OIDC login initiation, callback validation, and deferred token exchange status.
API requests and validation
dashboard/src/api/client.ts, dashboard/src/api/client.test.ts
Added authenticated API requests, tenant headers, response parsing, ApiError, and tests for successful and failed responses plus PKCE output.

Routes and dashboard pages

Layer / File(s) Summary
Routing and authentication guards
dashboard/src/router.tsx
Added login and callback routes, authentication-aware index routing, protected route groups, and router type registration.
Dashboard shell and pages
dashboard/src/layouts/AppShell.tsx, dashboard/src/pages/ProviderHomePage.tsx, dashboard/src/pages/AgentBuilderHomePage.tsx, dashboard/src/pages/AdminHomePage.tsx
Added branding, navigation, tenant selection, sign-out, and placeholder Provider, Agent builder, and Admin pages.

Control-plane SPA serving

Layer / File(s) Summary
Dashboard mounting and configuration
control-plane/src/config.ts, control-plane/src/dashboard/spaServe.ts, control-plane/src/app.ts
Added dashboard distribution-path resolution, conditional SPA mounting, static asset caching, SPA fallback handling, and route bypasses for API, webhook, health, and non-GET/HEAD requests.
Serving integration and project handover
control-plane/src/dashboard/spaServe.test.ts, docs/superpowers/plans/HANDOVER.md
Added HTTP integration coverage for SPA routes, assets, health responses, and API 404 responses. Updated dashboard implementation status.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant LoginPage
  participant AuthProvider
  participant sessionStorage
  participant OIDCProvider
  Browser->>LoginPage: submit API key or start OIDC sign-in
  LoginPage->>AuthProvider: setToken(token)
  AuthProvider->>sessionStorage: store token and tenant ID
  LoginPage->>OIDCProvider: redirect with PKCE authorization URL
  OIDCProvider-->>LoginPage: return authorization callback
  LoginPage->>AuthProvider: navigate authenticated session to /provider
Loading
sequenceDiagram
  participant Browser
  participant ExpressApp
  participant mountDashboardSpa
  participant DashboardDist
  Browser->>ExpressApp: request dashboard path
  ExpressApp->>mountDashboardSpa: evaluate route and method
  mountDashboardSpa->>DashboardDist: read static asset or index.html
  DashboardDist-->>ExpressApp: return dashboard content
  ExpressApp-->>Browser: send asset or SPA fallback
Loading

Possibly related PRs

  • Numeracode/verilink#4: Provides the control-plane app.ts and config.ts foundations extended here for dashboard SPA serving.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: the dashboard shell and control-plane static serving.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-pr-a

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Scaffold Dashboard SPA and serve built assets from control-plane

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add a new Vite/React dashboard shell with auth scaffolding and tenant header wiring.
• Serve dashboard/dist from the control-plane without shadowing /v1, /webhooks, /healthz.
• Add CI and unit tests for SPA static hosting and API client headers.
Diagram

graph TD
  B([Browser]) --> D([Dashboard SPA]) --> C([apiFetch + session]) --> A([Control-plane Express]) --> V([/v1 API routes])
  A --> S([SPA serve middleware]) --> X["dashboard/dist (index.html + assets)"]
  B --> S
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Serve dashboard under a dedicated prefix (e.g. /dashboard)
  • ➕ Eliminates risk of shadowing future non-API control-plane endpoints
  • ➕ Simplifies SPA bypass logic (only handle /dashboard/*)
  • ➕ Easier to add separate caching/security headers for the SPA
  • ➖ Requires router base path + asset base configuration in Vite
  • ➖ Requires updating redirects and links if product expects root-hosted SPA
2. Host dashboard as a separate static service (CDN/object storage)
  • ➕ Decouples frontend deploy cadence from control-plane
  • ➕ Better static caching and performance characteristics
  • ➕ Avoids Express static/fallback complexity entirely
  • ➖ Requires cross-origin configuration and stricter CORS handling
  • ➖ More infrastructure and deployment wiring up front

Recommendation: The PR’s approach (conditionally mounting dashboard/dist after API routes with explicit bypass for /v1, /webhooks, /healthz) is a reasonable first step for an integrated deploy and keeps local dev simple. If the control-plane is expected to grow additional non-API pages/endpoints, consider migrating to a /dashboard prefix to remove ongoing route-shadowing risk.

Files changed (31) +4318 / -5

Enhancement (16) +773 / -0
app.tsMount dashboard SPA after API routes +4/-0

Mount dashboard SPA after API routes

• Wires 'mountDashboardSpa(app)' into control-plane app creation. Ensures the SPA mount happens after API routes so it doesn’t interfere with JSON endpoints.

control-plane/src/app.ts

spaServe.tsImplement conditional dashboard dist static serving with SPA fallback +61/-0

Implement conditional dashboard dist static serving with SPA fallback

• Adds helpers to resolve the dashboard dist path and conditionally mount static serving only when 'index.html' exists. Implements GET/HEAD SPA fallback to 'index.html' while bypassing '/v1/', '/webhooks/', and '/healthz' paths, and logs mount/no-op decisions.

control-plane/src/dashboard/spaServe.ts

index.htmlAdd Vite HTML entrypoint +12/-0

Add Vite HTML entrypoint

• Introduces the dashboard 'index.html' with a root mount node and module script pointing to 'src/main.tsx'. Provides the base document for Vite builds and local dev.

dashboard/index.html

client.tsAdd apiFetch wrapper with ApiError handling +58/-0

Add apiFetch wrapper with ApiError handling

• Implements an API client that sets default headers, adds bearer token and tenant ID headers from session storage, and parses JSON/text responses. Throws a structured 'ApiError' on non-2xx responses using server-provided error messages when available.

dashboard/src/api/client.ts

AuthProvider.tsxAdd AuthProvider with token + tenant state +68/-0

Add AuthProvider with token + tenant state

• Introduces an auth context that persists the bearer token and tenant id in 'sessionStorage' and exposes setters plus 'signOut'. Exposes 'authMode' from config and a simple 'isAuthenticated' boolean.

dashboard/src/auth/AuthProvider.tsx

oidc.tsAdd minimal OIDC PKCE helpers and authorize URL builder +36/-0

Add minimal OIDC PKCE helpers and authorize URL builder

• Implements PKCE verifier/challenge generation and constructs an OIDC authorization URL for Authorization Code + PKCE. Token exchange is intentionally deferred; this PR establishes the client-side scaffolding.

dashboard/src/auth/oidc.ts

session.tsPersist token and tenant id in sessionStorage +20/-0

Persist token and tenant id in sessionStorage

• Adds simple getters/setters for the dashboard token and active tenant id. Centralizes storage keys and cleanup behavior when values are null.

dashboard/src/auth/session.ts

AppShell.tsxAdd top-level dashboard shell with nav and tenant selector +49/-0

Add top-level dashboard shell with nav and tenant selector

• Introduces a shared layout with primary nav and a session section containing tenant input and sign-out. Wires tenant selection to auth context to drive 'X-Tenant-Id' header injection.

dashboard/src/layouts/AppShell.tsx

main.tsxBootstrap React app with Query/Router/Auth providers +29/-0

Bootstrap React app with Query/Router/Auth providers

• Creates the React root and wraps the app with TanStack Query, AuthProvider, and TanStack Router. Sets conservative query defaults for early-stage development (no retry, no focus refetch).

dashboard/src/main.tsx

AdminHomePage.tsxAdd admin placeholder page +14/-0

Add admin placeholder page

• Adds an Admin landing page using the shared AppShell. Content is placeholder text referencing future Plan 9 PR work.

dashboard/src/pages/AdminHomePage.tsx

AgentBuilderHomePage.tsxAdd agent-builder placeholder page +12/-0

Add agent-builder placeholder page

• Adds an Agent Builder landing page using the shared AppShell. Content is placeholder text pointing to later PRs.

dashboard/src/pages/AgentBuilderHomePage.tsx

AuthCallbackPage.tsxAdd OIDC callback stub with state validation +48/-0

Add OIDC callback stub with state validation

• Implements an auth callback route that validates OIDC error parameters and stored 'state'. Leaves token exchange disabled while issuer/token endpoint configuration is finalized, but redirects if already authenticated.

dashboard/src/pages/AuthCallbackPage.tsx

LoginPage.tsxAdd login flow for API key and OIDC start +79/-0

Add login flow for API key and OIDC start

• Implements an API-key login form that stores a 'vrl_' token and redirects into the app. Adds OIDC start scaffolding that generates PKCE and navigates to the issuer authorize endpoint when configured.

dashboard/src/pages/LoginPage.tsx

ProviderHomePage.tsxAdd provider placeholder page +14/-0

Add provider placeholder page

• Adds a Provider landing page using the shared AppShell. Content is placeholder text describing intended future dashboard widgets.

dashboard/src/pages/ProviderHomePage.tsx

router.tsxAdd TanStack Router routes and RequireAuth gate +79/-0

Add TanStack Router routes and RequireAuth gate

• Defines routes for login, OIDC callback, and authenticated sections (provider/agent-builder/admin). Adds a 'RequireAuth' wrapper to redirect unauthenticated users to '/login'.

dashboard/src/router.tsx

index.cssAdd baseline dashboard styling for shell and login +190/-0

Add baseline dashboard styling for shell and login

• Introduces core CSS for the app shell layout, navigation, panels, and login view. Establishes theme variables and basic interaction styles for early UI scaffolding.

dashboard/src/styles/index.css

Tests (3) +133 / -0
spaServe.test.tsAdd unit tests for SPA static hosting and bypass paths +75/-0

Add unit tests for SPA static hosting and bypass paths

• Creates a temporary dist directory with 'index.html' and a sample asset, then boots the Express app and validates SPA fallback behavior. Verifies '/healthz' remains JSON and unknown '/v1/*' routes remain API-style 404 JSON.

control-plane/src/dashboard/spaServe.test.ts

client.test.tsTest apiFetch auth/tenant headers and OIDC PKCE output +57/-0

Test apiFetch auth/tenant headers and OIDC PKCE output

• Adds unit tests validating 'apiFetch' attaches 'Authorization: Bearer' and 'X-Tenant-Id' when present in session storage. Also validates PKCE helper output shape and base64url character constraints.

dashboard/src/api/client.test.ts

setup.tsAdd Vitest test setup +1/-0

Add Vitest test setup

• Configures test initialization by importing Testing Library helpers. Used by Vitest via 'setupFiles' in Vite config.

dashboard/src/test/setup.ts

Documentation (2) +43 / -5
README.mdDocument dashboard dev/prod usage and auth modes +38/-0

Document dashboard dev/prod usage and auth modes

• Adds dashboard-specific developer instructions (two-terminal workflow) and production serving guidance via control-plane. Documents env vars and the two auth modes (API key and OIDC PKCE scaffolding).

dashboard/README.md

HANDOVER.mdUpdate Plan 9 handover status for PR A in flight +5/-5

Update Plan 9 handover status for PR A in flight

• Updates the handover note to reflect that Plan 9 PR A (dashboard shell) is currently in progress. Adjusts the ‘next session’ guidance to sequence PR A then PR B.

docs/superpowers/plans/HANDOVER.md

Other (10) +3369 / -0
ci.ymlAdd Dashboard CI job (typecheck, vitest, build) +36/-0

Add Dashboard CI job (typecheck, vitest, build)

• Introduces a new 'dashboard' job that runs in the 'dashboard/' working directory. The job installs dependencies, runs typecheck and unit tests, then builds with 'VITE_AUTH_MODE=apikey' for CI determinism.

.github/workflows/ci.yml

.gitignoreIgnore Node modules and build outputs +7/-0

Ignore Node modules and build outputs

• Adds ignores for 'node_modules/', 'dashboard/dist/', 'control-plane/dist/', and TypeScript build info files. Prevents committing generated artifacts from the new dashboard and existing control-plane builds.

.gitignore

config.tsAdd DASHBOARD_DIST_PATH configuration +4/-0

Add DASHBOARD_DIST_PATH configuration

• Extends config with a 'dashboard.distPath' optional value. Enables overriding the dashboard build output directory via 'DASHBOARD_DIST_PATH'.

control-plane/src/config.ts

package-lock.jsonLock dashboard dependencies +3205/-0

Lock dashboard dependencies

• Adds an npm lockfile for the new dashboard package, pinning versions for React, Vite, TanStack Query/Router, Vitest, and related tooling. Ensures reproducible installs in CI and local environments.

dashboard/package-lock.json

package.jsonCreate dashboard package with build/test scripts +33/-0

Create dashboard package with build/test scripts

• Defines the new '@verilink/dashboard' package with Vite dev/build, TypeScript typecheck, and Vitest test scripts. Declares runtime and dev dependencies for React/TanStack and the toolchain.

dashboard/package.json

config.tsAdd dashboard runtime config from Vite env +8/-0

Add dashboard runtime config from Vite env

• Defines 'apiBaseUrl', auth mode selection ('apikey' default), and OIDC issuer/client env parameters. Normalizes the API base URL to avoid trailing slashes.

dashboard/src/config.ts

vite-env.d.tsType Vite env variables for dashboard +13/-0

Type Vite env variables for dashboard

• Adds TypeScript typings for dashboard-specific Vite env vars including auth mode and OIDC configuration. Improves type safety and editor feedback when reading 'import.meta.env'.

dashboard/src/vite-env.d.ts

tsconfig.app.jsonAdd strict TS config for the dashboard app +25/-0

Add strict TS config for the dashboard app

• Defines dashboard compiler options for modern ES/DOM targets, strict lint-like checks, and vitest globals. Adds a simple path alias ('@/*') for imports.

dashboard/tsconfig.app.json

tsconfig.jsonAdd TS project references root config +4/-0

Add TS project references root config

• Adds a lightweight root 'tsconfig.json' referencing 'tsconfig.app.json'. Aligns with multi-config TypeScript project patterns for Vite apps.

dashboard/tsconfig.json

vite.config.tsAdd Vite config with CP proxy and Vitest settings +34/-0

Add Vite config with CP proxy and Vitest settings

• Configures React plugin, '@' alias, and a dev proxy for '/v1' and '/healthz' to the control-plane. Enables sourcemaps and sets up Vitest to run in jsdom with a shared setup file.

dashboard/vite.config.ts

@qodo-code-review

qodo-code-review Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 18 rules

Grey Divider


Remediation recommended

1. SPA shadows /v1 root ✓ Resolved 🐞 Bug ≡ Correctness
Description
mountDashboardSpa() serves index.html for GET/HEAD requests to "/v1" and "/webhooks" because
isSpaBypassPath() only bypasses paths starting with "/v1/" and "/webhooks/". This changes those
endpoints from the API JSON 404 to a 200 HTML response whenever a dashboard build is present.
Code

control-plane/src/dashboard/spaServe.ts[R16-21]

+function isSpaBypassPath(reqPath: string): boolean {
+  return (
+    reqPath === '/healthz' ||
+    reqPath.startsWith('/v1/') ||
+    reqPath.startsWith('/webhooks/')
+  );
Relevance

●●● Strong

Clear correctness bug contradicting PR intent (“never shadows /v1”); likely fixed quickly.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The SPA fallback runs for all GET/HEAD requests except those matched by isSpaBypassPath(), and it is
mounted before the API JSON 404 handler, so missing bypass coverage causes HTML responses for those
API namespace roots.

control-plane/src/dashboard/spaServe.ts[16-22]
control-plane/src/dashboard/spaServe.ts[46-58]
control-plane/src/app.ts[56-62]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`isSpaBypassPath()` does not bypass the exact namespace roots (`/v1`, `/webhooks`), so the SPA fallback returns `index.html` for those paths.

### Issue Context
`mountDashboardSpa(app)` is mounted before the JSON 404 handler, so any GET/HEAD request not bypassed will be handled by the SPA fallback when `index.html` exists.

### Fix Focus Areas
- control-plane/src/dashboard/spaServe.ts[16-22]
- control-plane/src/dashboard/spaServe.ts[46-58]
- control-plane/src/app.ts[56-62]

### Suggested fix
Update bypass logic to include exact roots, e.g.:
- `reqPath === '/v1' || reqPath.startsWith('/v1/')`
- `reqPath === '/webhooks' || reqPath.startsWith('/webhooks/')`
(Optionally also treat `/healthz/` as bypass if you want strictness.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Invalid API keys accepted ✓ Resolved 🐞 Bug ≡ Correctness
Description
LoginPage accepts any token starting with "vrl_" and marks the user authenticated (Boolean(token)),
but the control-plane rejects API keys unless they are exactly 68 characters. Users can enter
malformed keys, get routed into authed pages, and then hit 401s on protected API calls.
Code

dashboard/src/pages/LoginPage.tsx[R16-22]

+    const key = apiKey.trim();
+    if (!key.startsWith('vrl_')) {
+      setError('API key must start with vrl_');
+      return;
+    }
+    auth.setToken(key);
+    navigate({ to: '/provider' });
Relevance

●●● Strong

Deterministic frontend/backend validation mismatch causing bad UX; straightforward to enforce
length/client-side validation.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The frontend sets auth state solely based on token presence and only validates the prefix, while the
backend explicitly rejects API keys that are not exactly 68 chars, creating a deterministic
mismatch.

dashboard/src/pages/LoginPage.tsx[13-23]
dashboard/src/auth/AuthProvider.tsx[48-57]
control-plane/src/middleware/auth.ts[61-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The dashboard accepts malformed API keys client-side (only checks the `vrl_` prefix), but the backend enforces a strict length requirement.

### Issue Context
- Frontend treats `isAuthenticated` as `Boolean(token)`.
- Backend rejects API keys when `key.length !== 68`.

### Fix Focus Areas
- dashboard/src/pages/LoginPage.tsx[13-23]
- dashboard/src/auth/AuthProvider.tsx[48-57]
- control-plane/src/middleware/auth.ts[61-66]

### Suggested fix
Mirror the backend validation at minimum:
- Require `key.startsWith('vrl_') && key.length === 68` (and optionally a hex check for the suffix).
Also consider handling 401/403 globally by clearing the stored token and navigating back to `/login` so stale/invalid tokens don’t leave the app in an “authenticated” state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Dist disable comment wrong ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
resolveDashboardDistPath() claims an empty string means "do not mount", but when env/config is empty
it always falls back to "../dashboard/dist". This comment/behavior mismatch can mislead operators
trying to disable SPA mounting via an empty value.
Code

control-plane/src/dashboard/spaServe.ts[R8-13]

+/** Resolve dashboard dist directory; empty string means "do not mount". */
+export function resolveDashboardDistPath(): string {
+  // Prefer live env so tests can set DASHBOARD_DIST_PATH after config freeze.
+  const fromEnv = (process.env.DASHBOARD_DIST_PATH || config.dashboard.distPath || '').trim();
+  if (fromEnv) return path.resolve(fromEnv);
+  return path.resolve(process.cwd(), '../dashboard/dist');
Relevance

●●● Strong

They’ve accepted fixing misleading comments/behavior mismatches; low-risk clarity fix.

PR-#6

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code trims env/config and falls back to a default path when empty, so empty does not disable
mounting as the comment states.

control-plane/src/dashboard/spaServe.ts[8-14]
control-plane/src/config.ts[71-74]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The function-level comment says an empty dist path disables SPA mounting, but the implementation treats empty as "use default ../dashboard/dist".

### Issue Context
Config already documents that empty means fallback-to-default; the SPA mount only truly disables when `index.html` is missing.

### Fix Focus Areas
- control-plane/src/dashboard/spaServe.ts[8-14]
- control-plane/src/config.ts[71-74]

### Suggested fix
Either:
1) Update the comment to match actual behavior (empty => default fallback), or
2) Implement an explicit disable mechanism (e.g., `DASHBOARD_DIST_PATH=disabled` or a separate boolean flag) and have `resolveDashboardDistPath()`/`mountDashboardSpa()` honor it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Content-Type forced to JSON ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
apiFetch() sets "Content-Type: application/json" whenever init.body is truthy and no Content-Type is
provided, regardless of the actual body type. This makes the helper easy to misuse for non-JSON
bodies (it will advertise JSON even when the body isn’t JSON).
Code

dashboard/src/api/client.ts[R20-24]

+  const headers = new Headers(init.headers);
+  if (!headers.has('Accept')) headers.set('Accept', 'application/json');
+  if (init.body && !headers.has('Content-Type')) {
+    headers.set('Content-Type', 'application/json');
+  }
Relevance

●● Moderate

Helper likely intended for JSON-only calls; change is subjective without repo precedent for
dashboard client.

PR-#5

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper’s header logic is explicitly based on body presence, not on whether the body is JSON, so
it can misrepresent request payloads.

dashboard/src/api/client.ts[20-24]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`apiFetch()` unconditionally defaults `Content-Type` to JSON whenever `init.body` is present.

### Issue Context
This is a shared helper; future callers may pass non-JSON bodies.

### Fix Focus Areas
- dashboard/src/api/client.ts[20-24]

### Suggested fix
Only set `Content-Type: application/json` when you know you’re sending JSON (e.g., when `init.body` is a string produced by `JSON.stringify`, or add a `json` option that sets both body+header). Otherwise, leave `Content-Type` unset unless the caller explicitly provides it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Hardcoded API_KEY_HMAC_SECRET value 📘 Rule violation ⛨ Security
Description
control-plane/src/dashboard/spaServe.test.ts hardcodes a value for API_KEY_HMAC_SECRET, which
constitutes committing credential-like material into source. This can trigger secret scanning
(gitleaks) and violates the repository requirement to keep credentials out of code.
Code

control-plane/src/dashboard/spaServe.test.ts[4]

+process.env.API_KEY_HMAC_SECRET ||= 'test-hmac-secret-for-unit';
Relevance

● Weak

Team previously rejected removing hardcoded API_KEY_HMAC_SECRET/test secrets to satisfy gitleaks in
tests.

PR-#21
PR-#16

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2373972 requires that source code contain no secrets/credentials detected by
gitleaks. The change introduces a hardcoded HMAC secret value assigned to API_KEY_HMAC_SECRET in
control-plane/src/dashboard/spaServe.test.ts, which is credential-like material committed directly
into the repository.

Rule 2373972: Source code must contain no secrets or credentials as detected by gitleaks
control-plane/src/dashboard/spaServe.test.ts[4-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A credential-like secret value is hardcoded in a test (`process.env.API_KEY_HMAC_SECRET ||= 'test-hmac-secret-for-unit';`). This violates the policy requiring code to contain no secrets/credentials detectable by gitleaks.

## Issue Context
The test only needs a non-empty value for `API_KEY_HMAC_SECRET`; it does not need a committed constant.

## Fix Focus Areas
- control-plane/src/dashboard/spaServe.test.ts[4-4]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread control-plane/src/dashboard/spaServe.ts Outdated
Comment thread dashboard/src/pages/LoginPage.tsx
Comment thread control-plane/src/dashboard/spaServe.ts Outdated
Comment thread dashboard/src/api/client.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@control-plane/src/dashboard/spaServe.ts`:
- Around line 18-20: Update isSpaBypassPath in
control-plane/src/dashboard/spaServe.ts (lines 18-20) to match exact /v1 and
/webhooks paths in addition to their slash-prefixed namespaces. Add regression
tests in control-plane/src/dashboard/spaServe.test.ts (lines 69-74) confirming
GET requests to both exact paths bypass the SPA fallback and return JSON API 404
responses.
- Around line 9-13: Update resolveDashboardDistPath to remove the
process.cwd()-based fallback; require the configured
DASHBOARD_DIST_PATH/config.dashboard.distPath and use a repository-stable
dashboard distribution location instead. In the dashboard request routing, treat
exact /v1 and /webhooks paths as bypasses so they do not reach the SPA
index.html fallback, while preserving SPA handling for other paths.

In `@dashboard/package.json`:
- Line 13: The test:e2e script currently falls back to playwright without
declaring that executable. Add `@playwright/test` to dashboard/package.json
devDependencies and update the lockfile so clean installs provide the fallback
runner, or remove the fallback and enforce the documented pw-test prerequisite.

In `@dashboard/README.md`:
- Around line 8-12: Update the development commands in the README to begin from
the canonical repository path /srv/storage/repo/VeriLink/ before entering
control-plane or dashboard. Apply the same absolute-path change to both terminal
instructions so they no longer depend on the caller’s current directory.

In `@dashboard/src/api/client.ts`:
- Around line 20-24: Update the request header construction around the headers
initialization so a body alone never assigns application/json; set Content-Type
only when an explicit JSON option requests it or the caller already supplies a
header, preserving FormData multipart boundaries and URLSearchParams form
encoding. Add regression coverage for both FormData and URLSearchParams
requests.

In `@dashboard/src/auth/oidc.ts`:
- Around line 26-35: Update the authorization URL builder around the OIDC
endpoint construction to use a validated discovery metadata
authorization_endpoint, or a separately validated authorizationEndpoint
configuration, instead of appending /oauth/authorize to opts.issuerUrl. Ensure
the endpoint is resolved before setting the existing OAuth query parameters and
preserve the current PKCE and state values.

In `@dashboard/src/auth/session.ts`:
- Around line 8-10: Replace the token persistence in setStoredToken with a
server-managed HttpOnly, Secure, SameSite session-cookie flow through a
same-origin session or BFF endpoint; remove bearer/API credential storage from
sessionStorage and retain sessionStorage only for non-sensitive UI state.

In `@dashboard/src/pages/AuthCallbackPage.tsx`:
- Around line 16-40: Complete the AuthCallbackPage callback flow after state
validation by reading the authorization code and stored PKCE verifier,
exchanging them through the configured token or control-plane session endpoint,
and passing the resulting bearer token to auth.setToken before navigating to
/provider. Handle missing parameters and exchange failures with terminal
user-facing messages, and remove the stored OIDC state and verifier after any
terminal callback result. Add a test covering a successful OIDC callback.

In `@dashboard/src/styles/index.css`:
- Line 10: Update the stylesheet declarations around the root font-family and
the Fraunces font-family declarations to satisfy the configured Stylelint rules:
move the declaration at line 10 to the permitted position and remove unnecessary
quotes from the Fraunces family names at the referenced declarations, preserving
the existing font stacks and styling behavior.
- Around line 145-171: Update the `.login__form` min-width to use the available
container width rather than a viewport-based `90vw` minimum, ensuring the form
does not exceed `.login`’s padded width on narrow viewports.

In `@dashboard/vite.config.ts`:
- Around line 1-23: Update the Vite config’s defineConfig callback to call
loadEnv before resolving VITE_DEV_PROXY_TARGET, ensuring `.env.local` values are
available during evaluation. Store the loaded proxy target once and reuse it for
both the `/v1` and `/healthz` proxy entries, preserving the existing localhost
fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e08912c0-dd6f-4852-8888-27f98d2d84c0

📥 Commits

Reviewing files that changed from the base of the PR and between 03d6ccb and 1bc61d6.

⛔ Files ignored due to path filters (1)
  • dashboard/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (30)
  • .github/workflows/ci.yml
  • .gitignore
  • control-plane/src/app.ts
  • control-plane/src/config.ts
  • control-plane/src/dashboard/spaServe.test.ts
  • control-plane/src/dashboard/spaServe.ts
  • dashboard/README.md
  • dashboard/index.html
  • dashboard/package.json
  • dashboard/src/api/client.test.ts
  • dashboard/src/api/client.ts
  • dashboard/src/auth/AuthProvider.tsx
  • dashboard/src/auth/oidc.ts
  • dashboard/src/auth/session.ts
  • dashboard/src/config.ts
  • dashboard/src/layouts/AppShell.tsx
  • dashboard/src/main.tsx
  • dashboard/src/pages/AdminHomePage.tsx
  • dashboard/src/pages/AgentBuilderHomePage.tsx
  • dashboard/src/pages/AuthCallbackPage.tsx
  • dashboard/src/pages/LoginPage.tsx
  • dashboard/src/pages/ProviderHomePage.tsx
  • dashboard/src/router.tsx
  • dashboard/src/styles/index.css
  • dashboard/src/test/setup.ts
  • dashboard/src/vite-env.d.ts
  • dashboard/tsconfig.app.json
  • dashboard/tsconfig.json
  • dashboard/vite.config.ts
  • docs/superpowers/plans/HANDOVER.md

Comment thread control-plane/src/dashboard/spaServe.ts Outdated
Comment thread control-plane/src/dashboard/spaServe.ts
Comment thread dashboard/package.json Outdated
Comment thread dashboard/README.md Outdated
Comment thread dashboard/src/api/client.ts
Comment thread dashboard/src/auth/session.ts Outdated
Comment thread dashboard/src/pages/AuthCallbackPage.tsx Outdated
Comment thread dashboard/src/styles/index.css
Comment thread dashboard/src/styles/index.css
Comment thread dashboard/vite.config.ts Outdated
Bypass SPA for /v1 and /webhooks roots, validate 68-char API keys,
allow disabling SPA via DASHBOARD_DIST_PATH sentinels, and set
Content-Type only for string request bodies.

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Addressed open Qodo findings:

  1. SPA no longer shadows /v1 or /webhooks roots
  2. API key login validates 68-char vrl_ + hex form
  3. DASHBOARD_DIST_PATH=off (etc.) disables SPA mount; unset still falls back to ../dashboard/dist
  4. apiFetch only defaults JSON Content-Type for string bodies

Re: hardcoded API_KEY_HMAC_SECRET in the unit test — intentional test fixture (same pattern as other CP tests); not a production secret.

Stabilize SPA dist resolution, OIDC discovery + token exchange,
in-memory bearer storage, Vite loadEnv proxy, README paths, and
narrow-viewport CSS.

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Addressed CodeRabbit CHANGES_REQUESTED items in d057df0:

  • SPA dist fallback via import.meta.url (cwd-independent)
  • README uses /srv/storage/repo/VeriLink/…
  • OIDC discovery + authorization code token exchange
  • Bearer tokens in memory only (not Web Storage)
  • Vite loadEnv for VITE_DEV_PROXY_TARGET
  • CSS overflow / Stylelint-ish cleanups; dropped premature test:e2e script

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

@messagesgoel-blip I will review the updated changes in PR #26.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant