Project: webhost.systems (v1) Focus: Dashboard polish — add Documentation + Account pages; start improving Agents navigation toward a sortable/searchable table Owner: Engineering
Started Slice B dashboard navigation polish by adding dedicated pages for:
Account (identity + tier/usage snapshot; billing placeholder)
Documentation (spec-first quick links + local quickstart notes)
Began refactoring the dashboard shell so Account and Documentation are no longer “coming soon” placeholders.
Improved Agents navigation by replacing the left “agent buttons” list with a sortable/searchable/filterable agents table.
Implemented Agents routing as two separate pages using query-string routes (so #... remains available for in-page section anchors):
Agents list: ?page=agents
Agent detail: ?page=agent&agentId=<agentId>
Added a new Account page intended to become the home for:
identity info (from Clerk),
tier + usage snapshot (from control plane),
billing/subscription UI (placeholder for now).
Notes:
The page pulls usage using the existing GET /v1/usage/current endpoint via ControlPlaneClient.
The page intentionally does not introduce any new server endpoints yet.
Added a new Documentation page intended to be a “spec-first index”:
links to docs/spec and ADRs (via GitHub),
quick local env reminders,
quick list of endpoints exercised by the dashboard.
Notes:
This stays non-normative and points back to the spec set as the source of truth.
Updated the authed sidebar nav so:
“Documentation” is a first-class route label (instead of “Docs”).
“Account” route renders the new Account page.
“Documentation” route renders the new Documentation page.
Replaced the left “agent buttons” list with a table-driven Agents sidebar that supports:
Search: client-side substring match across name/description/id/status/runtime + active deployment id.
Filters:
status (draft|ready|deploying|active|error|disabled|deleted)
runtime (cloudflare|agentcore|unknown)
deployment state (has active deployment / none)
Sorting (client-side):
name, status, preferredRuntimeProvider, hasActiveDeployment, updatedAtMs, createdAtMs, _id
sensible defaults (e.g. updatedAtMs desc, hasActiveDeployment desc)
Selection: clicking a row selects the agent (preserving the existing “left list + right detail panels” layout).
This is intentionally dependency-free (no table library).
Next UX polish: when the selected agent is filtered out, show a small banner (or adjust the selection policy) so the state doesn’t feel “lost”.
Routing follow-up (requested): move to two separate pages:
Agents list route (table + create)
Agent detail route (deploy/invoke/deployments/telemetry for a selected agent)
✅ Dashboard improvements are non-normative and remain aligned with:
10_API_CONTRACTS.md (usage endpoint for tier/usage display)
00_MASTER_SPEC.md (dashboard requirements / views)
✅ No changes to the control-plane boundary or security model.
⚠️ Billing UI is still placeholder; webhook-driven tier updates remain a future phase.
apps/web/src/App.tsx — route wiring + nav label updates
apps/web/src/components/AccountPage.tsx — new (now rendered)
apps/web/src/components/DocsPage.tsx — new (now rendered)
apps/web/src/components/AgentsPage.tsx — replaced agent sidebar list with sortable/searchable/filterable table
apps/web/src/index.css — added table/control styles to support the Agents table UX
Manual UI sanity:
navigation switches between Dashboard / Agents / Account / Documentation
Agents sidebar table: search/filter/sort works and selection updates the right-side detail view
Account page loads usage (when control plane env is configured)
Typecheck:
npm run typecheck -w apps/web
No new server endpoints added.
Agents table UX is a first pass:
selection can become “hidden” if filters/search exclude the selected agent (needs a banner or consistent selection policy)
Agents list/detail split is now implemented using query-string routing:
list: ?page=agents renders only the table/create UI
detail: ?page=agent&agentId=<agentId> renders only deploy/invoke/deployments/telemetry for that agent
Hash (#...) is reserved for in-page anchors (section jumps) within each page.
Avoid duplicating token-fetch logic across pages long-term:
consider extracting a shared useControlPlaneClient() hook now that multiple pages use the same pattern.
[ ] Agents routing polish (query-string routes):
[ ] verify deep links:
?page=agents
?page=agent&agentId=<agentId>
[ ] verify list → detail navigation and detail → list back work with browser history
[ ] confirm in-page anchors still work on Account/Docs/Agents pages (hash is not used for routing)
[ ] Agents table polish:
[ ] show a banner when filters/search exclude the selected agent (if/when a combined view exists again), or ensure the detail route drives selection cleanly
[ ] move remaining inline table styles toward the CSS classes for consistency
[ ] Add a small “agent quick actions” row for the selected agent (copy id, refresh, open deployments section).
[ ] Extract a shared useControlPlaneClient() hook to eliminate repetition across Dashboard/Agents/Account.
[ ] Start wiring real billing once checkout + webhook flows are implemented (per spec).
Implemented a shared useControlPlaneClient() hook in the web app to centralize:
base URL normalization (VITE_CONTROL_PLANE_URL)
Clerk JWT template selection (VITE_CLERK_JWT_TEMPLATE, default "convex")
consistent, actionable token-fetch failure messaging
Refactored pages to use the shared hook (removes repeated client construction):
Dashboard
AgentsPage
AccountPage
Notes:
No behavior changes intended; this is primarily a duplication/consistency win.
This aligns with the earlier “avoid duplicating token-fetch logic across pages” follow-up.
Added a small “agent quick actions” row in the Agent detail header:
Copy agent id
Refresh details (agents + deployments + usage/telemetry)
Jump link to the Deployments section
Added an environment-configurable CORS allowlist to the Convex HTTP server:
CONTROL_PLANE_CORS_ALLOW_ORIGINS (comma-separated origins)
When unset/empty, preserves the previous dev-friendly behavior (reflect Origin).
Tightened SSE error envelopes to avoid leaking internal/provider error messages:
Keep user-facing messages generic unless the error is one of the explicitly allowlisted codes (e.g. UNAUTHORIZED, LIMIT_EXCEEDED, etc.)
Notes:
No API shape changes; these are HTTP-layer response hardening changes.
Implemented the v1 delegated invocation endpoint (server-to-server, internal):
POST /v1/delegated/invoke/:agentId
Auth model:
HMAC over raw request bytes using WHS_DELEGATION_SECRET
required headers:
X-WHS-Delegation-Source
X-WHS-Delegation-Timestamp (skew window enforced; recommended ±5 minutes)
X-WHS-Delegation-Signature: v1=<hex-hmac-sha256(raw_body_bytes)>
optional allowlist via WHS_DELEGATION_ALLOWED_SOURCES
Delegated user resolution:
Added an internal upsert path to resolve delegation.externalUserId (auth subject) into a WHS users row, creating it if missing.
Idempotency:
Added delegatedInvocationIdempotency table and internal mutations to ensure retries do not double-run/double-charge.
Conflict detection: same (userId, agentId, idempotencyKey) with different payload hash returns CONFLICT.
Limits + routing behavior remains consistent with the normal invoke gateway:
routes only via agents.activeDeploymentId
enforces request-limit reservation pre-invoke (ADR-0007)
Added a WHS-specific node type to Agentromatic’s shared node registry:
whs_agent (config schema includes whsAgentId + invocation input placeholders)
Added Agentromatic server-side helper(s) to sign and call WHS delegated invocation:
uses WHS_CONTROL_PLANE_URL + WHS_DELEGATION_SECRET
sends to POST /v1/delegated/invoke/:agentId with the required headers
Extended the Phase 1 executeWorkflow stub toward a minimal runner shape:
sequentially iterates nodes from the workflow snapshot (still not a DAG engine)
attempts delegated WHS invocation for whs_agent (and compatible ai_agent configs) when configured
logs results into execution logs for UI debugging
Notes:
This is deliberately “thin-slice wiring”: it sets up the integration seams without committing to a full workflow engine yet.
The delegated invocation endpoint is explicitly server-to-server and MUST NOT be used from browsers.
Typecheck:
npm run typecheck -w apps/web
npm run typecheck -w apps/control-plane
npm run typecheck (Agentromatic workspaces)