# tested.dev design

You are generating UI for tested.dev. Follow this file. If a screen fails these rules it is not done.

tested.dev is patch coverage and PR analytics for agents. Taste bar is Resend, Cal.com, Vercel: steal rules, not marks, not type, not libraries. One standard, two densities (marketing vs product). Tokens below are already shipped. Do not invent a look.

When rules compete, protect them in this order: shipped tokens and facts; the reader's job; one primary action; composition; then polish.

## 1. Principles

1. Dark-first. High contrast. Quiet chrome. Precision over decoration.
2. Accent is state, not style. Green is pass and the period. Red is fail. Amber is flake or pending. Gray is structure. Never paint for fun.
3. One primary action per screen: the thing the user should do. A role, not a color. A second control, if needed, is a quiet outline at the same radius.
4. Density: chips, rows, numbers. If a block would be a paragraph in a tool UI, it is already too long.
5. Developer voice. Sentence case everywhere: headings, buttons, labels, nav. Factual. Short.

## 2. Two surfaces

Same type, color, radius, copy, and chrome. Different density.

**Marketing (`tested.dev`).** Composed. Air around one idea. Home is a fold, not a dashboard. Pricing is a product page, not a poster.

**Product (`app.tested.dev`).** Tool surfaces. Scan in one second: gate, patch %, project %, pass or fail. Then the table, tree, or diff.

| Job | Surface |
| --- | --- |
| Signed-out home, pricing, marketing 404 | Marketing |
| Sign-in, repos, PR, coverage tree, billing, app 404, demo | Product |

Do not put a marketing hero on a repo page. Do not put a scorecard on the marketing fold.

## 3. Composition

The first viewport is the job, not a masthead plus setup. If the reader saw only this viewport they should remember the idea (marketing) or the gate (product), not the mood.

**Marketing home.** Nav + idea + CTA + visual, clustered near the center of the space under the nav (`--nav-height`), not a full `100vh` including nav. At 390 the same pieces stack.

**Pricing.** Nav-to-`h1` gutters about 40 / 48 / 80px at 390 / 768 / 1280.

**Chrome.** Nav stays on every page including 404 and empty. Footer does not appear on 404 or empty. No leftover sign-in-card chrome (bordered panel with the GitHub button ripped out). No newsletter, repo list, or personal links on a miss.

**Nav matches session.** Signed-out: Demo + Sign in. Signed-in: Repos / Billing / avatar. Signed-out nav brand (`brand-wordmark`) goes to tested.dev (or `/` on a preview).

**Empty and 404 anatomy.**

1. Mark, 48px (`nav-mark`: `t` + green period glyph).
2. Title. 404 title is exactly `Not found`.
3. One factual line. 404 line is exactly `This page does not exist.`
4. One primary `h-10 rounded-lg` button. Never a second button to make a sparse 404 feel designed.

Centered. `min-height` fills the viewport under nav. No footer. No illustration.

| Screen | Primary | Href |
| --- | --- | --- |
| Marketing 404 | `Home` | `/` |
| App 404, signed-out | `Demo` | `/demo` |
| App 404, signed-in | `Repos` | `/repos` |

Sign-in (signed-out) is the same family, even tighter: mark, `Sign in`, `Continue with GitHub`, `Public demo`. Nothing else. `Continue with GitHub` is the GitHub action label; do not use bare `Continue` elsewhere.

**Product pages.** Title + meta + the number or gate. Body copy stays in the diff. Helper copy is one line max per section, and only if the number is not self-explanatory. If the heading is `Coverage`, do not also explain what coverage is.

**Repo page.** Coverage number + chart + PR table. `Files` is a first-class outline button matching Sign in chrome, not a muted caption.

**PR page.** Title + meta + gate (`BELOW` / `PASS`) + patch / project (+ path floors) as scorecard rows (§8) + tabs.

**Coverage tree.** Gutters + tree + code pane, not a dump. Folders show a percent and a bar (§8). Files expand to source.

**Demo.** Routes stay under `/demo`. Deep links must not dump the user on a signed-out `/repos/...` sign-in wall.

## 4. Generated-design rejects

Each of these is a named fail. If you recognize the pattern, delete it. Do not replace it with a sterile anti-design template. Restraint here is hierarchy, type, and evidence, not empty margins.

| Name | Reject because |
| --- | --- |
| `cards-in-cards` | Borders used to fake hierarchy. Spacing and type already group. |
| `all-caps-eyebrows` | Decorative marketing kickers (`THE FUTURE OF`, tracked overlines). Product tab labels already shipped as `DIFF` / `COVERAGE` stay. |
| `generic-hero-card-grid` | Centered hero plus three feature cards is the default landing, not this product. |
| `em-dashes` | Use periods, commas, parentheses. §9 document titles are the only em dash in the product. |
| `decorative-gradients` | Glows, blobs, glass, noise, rainbow borders, gradient text. Color is state. |
| `leftover-next-defaults` | `tested.dev — dashboard`, stock Next.js white-grid 404, leftover `<title>` that the root layout overrides. |
| `essay-like-tool-surfaces` | Repo and PR pages that read like a blog. Chips, rows, numbers. |
| `nested-scorecard-cards` | Scorecard rows are `border-b` lines, not a card inside a card. |
| `second-green` | Do not invent another green. Do not use accent as a fill behind copy. `--color-success` and `--color-pass` are already `#4ade80`. |
| `mixed-radii` | One family: 2px. Do not mix an 8px hamburger with a 2px Home. |
| `stolen-marks` | No Vercel triangle, no Resend cube, no Cal Sans, no Favorit, no Domaine. |
| `geist-as-face` | Geist is composition reference only (empty-state anatomy, spacing, quiet materials). Do not set type to Geist or Geist Title Case. Do not install `@vercel/geistcn`, geist-ui, coss, or Resend `src/ui`. |
| `get-started-ok-oops` | Ban: get started, OK, oops, sorry, unfortunately, unable to, something went wrong, click here, lorem, mock data, TODO in UI. |
| `illustrated-404s` | No jokes, mascots, or drawings on a miss. Mark + title + line + one button. |
| `100vh-phone-holes` | A stacked 390 fold must not leave a viewport of empty black. |
| `page-overflow` | `document.documentElement.scrollWidth === document.documentElement.clientWidth`. Tables and tab strips may scroll inside. The page may not. |
| `period-as-soup` | The green square is the wordmark period, the mark, and the pass badge. Not a bullet, not a hero ornament, not icon filler. |
| `giant-pricing-numbers` | No leftover numbered decoration on pricing. |
| `unsigned-chrome-on-404` | Signed-in users keep Repos / Billing / avatar on a miss. |
| `settings-404` | `/settings` redirects to `/settings/billing`. Not a 404, not a second settings home. |
| `shields-clone-badge` | README coverage badge is our 22px dark pill, not a Shields.io replica. |
| `mixed-pill-treatments` | Same row family, one chip. Do not mix solid-fill PASS chips with tinted path-floor chips. |

## 5. Type

| Role | Face | Weight | Notes |
| --- | --- | --- | --- |
| Headings, wordmark | Inter Display | 700 | Tracking about `-0.03em` on display sizes |
| Body, UI, buttons, nav | Manrope (`--font-sans`) | 400 / 500 | The only UI sans. Do not swap for Inter, Inter Display, or Geist. |
| Code, hashes, file paths, tab labels, scorecard rows and numbers | IBM Plex Mono (`--font-mono`) | 400 | Never page titles |

No second display face.

- Marketing page title: `clamp(36px, 8vw, 52px)`
- App page title: `clamp(28px, 4vw, 36px)`
- Body / UI: 14 to 16px. No tiny gray copy to fake density.
- Tabular numerals for percents and counts.

The primary button under a heading must hold the page: `h-10` (40px), not a 12px mono chip under a 52px title.

## 6. Color

Use the shipped variables. Hex is the computed value. Do not add a parallel palette.

| Token | Hex | Use |
| --- | --- | --- |
| `--color-bg` / `--background` | `#111113` | Page |
| `--color-fg` / `--foreground` | `#ededed` | Text |
| `--color-fg-dim` | `#d4d4d8` | Secondary text on dense rows |
| `--color-muted` / `--muted-foreground` | `#a1a1aa` | Muted UI, captions |
| `--color-accent` / `--primary` | `#fafafa` | Invert primary fill |
| `--primary-foreground` / `--color-elev` | `#0a0a0b` | Text on primary |
| `--color-border` | `#222225` | Hairline, secondary outline |
| `--color-border-strong` / `--color-sep` | `#2a2a2e` | Stronger rule |
| `--color-elev-2` / `--color-card-surface` | `#161618` | Raised surface, only when spacing is not enough |
| `--color-code-bg` | `#0a0a0b` | Code pane |
| `--color-success` / `--color-pass` | `#4ade80` | Pass, period glyph |
| `--color-danger` / `--destructive` | `#f87171` | Fail |
| `--color-warning` / `--color-warn` | `#fbbf24` | Flake, pending |
| `--color-info` | `#a1a1aa` | Structure, not a second accent |
| `--nav-height` | `56px` | Site nav |

Primary button: invert (white fill `#fafafa` on dark page, dark label). Green / red / amber always pair with a non-color cue (`PASS` / `FAIL` / `BELOW`), never color alone. Do not theme body text.

## 7. Radius, space, breakpoints

**Radius.** One family. `--radius`, `--radius-lg`, `--radius-card`, `--radius-control` all compute to `2px`. Tailwind `rounded-lg` is 2px here. Hamburger (`nav-menu-btn`), Home, Start free, Sign in, mark, coverage bars, README badges: same family.

**Space.** 4px grid (`--spacing: 0.25rem`). Heading to its first line: tight. Section to section: clearly larger. Every gap has one owner. Do not repair a weak group with a one-off margin.

**Breakpoints.** Always 390 / 768 / 1280. Marketing hamburger through 1023px; desktop nav row at 1024+.

## 8. Components

**Wordmark.** `brand-wordmark`: `tested` + a real period glyph (`brand-wordmark-dot`) rendered as a green square `#4ade80` + `dev`. Inter Display Bold. The square sits on the baseline like a real Inter period (same size and advance, not 2×, not a CSS box, not a pseudo-element).

**Mark.** `t` + the same green square. Empty states and 404s. Wordmark in nav.

**Button.** Real `<button>` or `<a>` with button styles, not an underlined text link pretending to be the CTA.

- Primary: invert, `h-10`, `rounded-lg`.
- Secondary: quiet outline, same height and radius.
- Label: sentence case, names the destination or action (`Home`, `Demo`, `Sign in`, `Start free`, `Public demo`).
- Icon left = verb, right = destination, if you use icons at all.
- Disabled if the user can unlock it. Hidden if they cannot.

**Empty / 404.** Anatomy in §3. HTTP 404 on unknown routes.

```html
<header class="site-nav">…session-correct chrome…</header>
<main class="flex min-h-[calc(100dvh-var(--nav-height))] flex-col items-center justify-center">
  <span class="nav-mark" aria-hidden="true"><!-- t + period glyph, 48px --></span>
  <h1>Not found</h1>
  <p>This page does not exist.</p>
  <a class="inline-flex h-10 items-center rounded-lg px-4" href="/">Home</a>
  <!-- App 404: Demo → /demo (signed-out) or Repos → /repos (signed-in) -->
</main>
```

**Scorecard row.** `font-mono text-[13px]`, `border-b border-border`, `py-2.5`. Label + percent + threshold + `PASS` / `FAIL` pill. Patch, project, and path floors all use this row and the same pill class. The coverage tab uses the same rows, not a restyled summary.

```html
<div class="flex w-full min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1 border-b border-border py-2.5 font-mono text-[13px] last:border-b-0">
  <span>patch</span>
  <span>85.3%</span>
  <span class="text-muted-foreground">≥ 80%</span>
  <span class="gate-badge">PASS</span>
</div>
```

**Pill.** State only: `PASS`, `FAIL`, `BELOW`, flake. `gate-badge` / `gate-badge-fail`: tinted chip, colored text (`#4ade80` pass, `#f87171` fail) on ~12% alpha, `rounded-lg`. Never a solid fill, never ordinary metadata.

**Coverage bar.** Height 6px, `rounded-lg` (2px), track is structure gray, pass fill `#4ade80`.

**README badge.** 22px dark pill, 2px radius. Green square period on pass; `n/a` is the same family with a gray period.

**Nav.** `site-nav`, height `--nav-height`. `nav-logo` + `nav-links` + `nav-actions`. Quiet. Header lockup stays one line at 390. Hamburger opens a full-width panel.

**Table.** Semantic `<table>`, full width of the content well. Numeric columns right-aligned, including headers. Mono for hashes, paths, percents.

## 9. Copy

Good: `PR coverage and analytics for agents.`

Errors: what happened, then what to do. User-state `Couldn’t` / `Can’t`. Infra `Failed to`. Never humor an error.

Codecov is a replacement story, never keep-both: the gate plus the project view is Team, then Codecov comes out. Do not use this file to rewrite frozen blog posts.

Document titles, via Next metadata (`generateMetadata` or route `metadata`): `{Page} — tested.dev`.

- Home: `tested.dev — PR coverage and analytics for agents`
- `Not found — tested.dev`
- `Repos — tested.dev`
- `Pricing — tested.dev`

## 10. Verify

Look at 390, 768, and 1280 for wraps, gaps, and overflow. Squint: the job (the idea or the gate) should be obvious. Sweep §4; any recognized pattern is a fail. Rules are the floor, not the target. If every rule passes and the screen still looks generated, it is not done: fix hierarchy, type, and density. Do not add chrome.
