Theme
UI guide
The dashboard is a single agent workspace at /: Repository → Agent Activity → Gap Analysis → Review Queue. It reads only from a scan, a run or the D1 history, and every number shown comes from one of those; there is no invented metric. The implementation lives in apps/web/src/components/agent/workspace.tsx; App.tsx owns run state and the components are presentational.
The reusable primitives live in packages/ui; this site consumes the same brand tokens through @edge-spec/ui/theme.css.
Page anatomy
text
┌─ header ─────────────────────────────────────────────────────────────┐
│ mark EdgeSpec provider sandbox backlog history settings │
├─ 01 Repository ──────────────────────────────────────────────────────┤
├─ 02 Agent Activity ──────────────────────────────────────────────────┤
├─ 03 Gap Analysis ────────────────────────────────────────────────────┤
└─ 04 Review Queue ────────────────────────────────────────────────────┘The four cards are numbered and reveal in order. The page scrolls; no scrollbar is visible. Accent-driven gradient glows and slowly drifting aurora blobs sit behind the content and are pointer-events-none.
Steps
- Repository — repository or local-path input, branch selector, ownership mode, and repository facts (languages, tooling, manifest). A local path only works while the dashboard runs through the Vite dev server.
- Agent Activity — scan, generate, validate and self-correct stages stream their own events. Each stage opens its log; Facts shows the structured scan facts and Event log the whole stream. Counts distinguish missing tests, coverage gaps, contract operations, unsupported and skipped files.
- Gap Analysis — the gap table, risk words, inferred OpenAPI operations (marked as inferred when no spec exists), and the actions that start a run. Bulk and per-file generation are here.
- Review Queue — one row per generated file with its diff, checks and coverage facts. Accept or reject each file; a rejected or unverified file never reaches a pull request. Backlog opens from the header; run history, the audit log and Settings open from the profile menu.
The Deployment section in Settings holds the configuration that lives in D1 and R2, not in this browser: workspace name and retention days, repository memory, the stored pull request policy used by webhook runs, and the opt-in drift scan. Each block reads its real route and saves explicitly; without the optional bindings it says so instead of showing an empty success. Run-history rows also download the stored R2 artifact bundle for that run; a 404 means the run stored no bundle.
Motion
Panels rise in with a short stagger and the aurora drifts. Working nodes blink; status text always accompanies the colour. Everything is disabled by prefers-reduced-motion: reduce. While a run is active the workspace shows a live badge and a pulsing dot on the brand mark.
Settings
The settings sheet sections are Provider, Sandbox, Keys, Pipeline, Policy, Deployment and Appearance. Open it from the profile menu; the header keeps the provider and sandbox shortcuts.
- Provider — provider, model, base URL for the private provider, and the matching key. Changing the provider clears the key field. Workers AI adds a Cloudflare account ID and token; the deployment binding is a local convenience only.
- Sandbox — provider, URL, key, concurrency and timeout. Without a runner validation stays static check only.
- Keys — GitHub token for private repositories and pull requests.
- Pipeline — coverage target, retries, files per run, strict TypeScript and parallel generation.
- Policy — opt-in PR gates.
- Deployment — workspace name and retention, repository memory, stored PR policy and the drift scan. These save to D1/R2, not
localStorage. - Appearance — dark/light and ten accent swatches.
Non-appearance fields are a draft: Cancel, Escape or the backdrop discards them. Saving writes settings to localStorage; keys go to the browser's secret store and are sent per request as headers. Appearance applies immediately. The Deployment section is the exception: it saves explicitly to D1/R2 and is not part of the local draft.
Theme and accent
The interface is built on the zinc ramp.
- Dark is the default. Light toggles
html.light, which remaps the zinc ramp, borders, glass background and status colours. Components do not have separate light variants. - Accent writes one CSS variable,
--accent-base; derived values are mixed withcolor-mix()and mapped onto Tailwind'sindigo-*utilities. Adding a swatch means adding an entry toACCENTSinapps/web/src/lib/theme.ts. - Theme and accent are applied by a small inline script before first paint, so there is no flash.
Layout and scrolling rules
- The document scrolls;
htmlhides its scrollbar globally. - Inner scroll containers (terminal, code block, dialog body, tab strip) use
.no-scrollbar. Keep the class on any new scrollable pane. - Content is capped at
max-w-7xlwith responsive padding. - Stage nodes are one column on mobile and a row on large screens.
- The gap table and preview stack until wide screens; the header actions wrap below the brand on small screens instead of shrinking the repository field.
Feedback and states
- Toasts appear bottom-right for scan results, PR creation, generation failures and run failures; they auto-dismiss after ~4.5 seconds.
- Run failures also stream into the log; a failed validation keeps the test in Review with a red card and
verified: false. - Empty states always say what to do next (set a repository, scan, run).
- Stop aborts the fetch; the UI discards further events even if the server-side pipeline finishes its current upstream call.
Accessibility
- Radix Dialog, Select, Switch, Slider and Tabs provide focus management, keyboard navigation and ARIA semantics.
- Inputs have labels or
aria-labels; the repository field setsaria-invalidwhile invalid. - Toggles use
role="switch"witharia-checked; segmented choices usearia-pressed. - Icon-only buttons have accessible names.
- Status is never colour-only: stages show status text, validation uses labels, risk uses words.
Adding UI
- Reuse the primitives in
packages/ui(Button,Badge,Input,Select,Dialog,Switch,Slider,Tabs,Card) andcn(); do not add raw controls or copy a primitive intoapps/web. - Use semantic tokens (
bg-zinc-*,text-*,border-*, accent utilities) instead of hard-coded colours so light mode and accents keep working. - Keep run state in
App.tsx; presentational components take props. Settings, theme and secrets live in external stores underapps/web/src/lib/. - Import shared shapes from
@edge-spec/core/types; do not duplicateGap,GenerationorValidation.