DESIGN.md — the forge's design system
The principles, in short. (The numbered list in §1 is the long form.)
- One confident sentence says what the page is for; nothing explains itself twice.
- The real product is the visual: show the actual rows, not a picture of them.
- Generous rhythm: space between things, not lines.
- One background, one text colour (
--bg,--fg), muted for the rest. - At most one accent,
--accent, for interaction alone. Enforced:accentinscripts/design-lint.ts. - Few borders: a hairline where a thing ends, never a box around a thing.
- Rows over boxes: a list is rows, a fact is a
dlrow, not a card. - Plain words, as in
docs/WRITING.md.
The five rules of attention. Every pass over a page follows them; the owner's time is the cost.
Each has a real page as its pair. Enforced by rule 5: test/attention.test.ts.
- One answer. A page answers its one question at first sight. State is a word and a glyph, not a sentence.
- Do: securityPage gives each fact a toned badge (Recent, Overdue), not a paragraph.
- Don't: previewSection (src/web/pages/apps.ts) says "The last deploy of this preview failed. Push again to redeploy it." where Failed with the tone says it.
- No notes. Explanatory prose that does not change a decision goes. Help lives in one place, on demand.
- Do: the home composer is the sentence of purpose and the control itself (studioHome).
- Don't: a <p class="muted small"> under a list that explains what the list is; the heading already did.
- Real depth only. Details, history, logs and evidence open in place, in their own compartment. The essential is never one tap away, and nothing is a tap with nothing behind it.
- Do: the disclosure in src/web/ui.ts (used in src/web/pages/new-forge.ts) holds the optional extras; the choice stays on the page.
- Don't: a disclosure that opens onto one line, or one that hides the verdict.
- Compartments. Each concern sits in one region with one heading, in the same order on every page. Regions never repeat each other.
- Do: /settings, where each concern is one settings-block with one heading.
- Don't: the same fact in the header, the tab count and a status line, as the Pulls tab and its list once did (#10312).
- Measured. Every page has a first-view budget at a narrow (390 px) and a wide (1280 px) width: visible words, text blocks and distinct colours, in
docs/design/attention-budget.json. A page over its budget failstest/attention.test.ts. A pass may lower a budget; raising one needs a note in the pull. Seedocs/features/attention.md.
Worked examples.
src/web/studio/home.ts,studioHome: the<h1 class="sr-only">Home</h1>thentellBoxis
one sentence of purpose (1) and the real composer, not a picture of one (2); <section class="stack">
gives the rhythm (3); the lines <p class="small muted"><a href="/feed">The feed</a></p> are
muted text, not buttons or boxes (4, 7).
src/web/pages/security.ts,securityPage:<dl class="status-rows">with onestatusRow
per fact (Traffic, Backup, Admin accounts, …) is rows over boxes (7); a row that leads
anywhere ends in one <a> (Traffic, Backups), the only interactive colour (5); each row says
its state in a few plain words, with a toned badge for the verdict (Recent, Overdue) (8).
One vocabulary, two surfaces. The web renders it as HTML and CSS; a terminal UI renders the same names as cells and boxes. If a component is not in these files, it does not exist — and if you find yourself hand-rolling markup a page already has a name for, that is the bug.
Read this once. It is short on purpose.
Each section is its own file under docs/design/sections/, so a change to one
never conflicts with a change to another. This page is the spine: it orders
them, and the design check (bun scripts/design-lint.ts) reads them in this
order — a section file not listed here is not part of the contract. A new
component class is named in docs/design/areas/<area>.md, not here.
---
- 5a. Saying where you are reading from - 5b. The workflow units - 5c. Apps, runs and the crew - 5d. Page-local classes
- 6a. The code browser
- 6b. Code, coloured
- 6c. The editor
- 6·0. The pointer
- 6·0a. The dashboard's panes
- 6·0a·1. Laid out for the terminal it is in
- 6·0b. Icons, and the appearance picker
- 6·0c. Pictures in the terminal
- 6a. Counts and marks on a row
- 6b. The workspace — one screen for a whole repository
- 6c. Editing, and the commit dialog
- 6c·1. Contributing: fork at the origin, propose a change
- 6d. $FORGE_EDITOR — the operator's own editor, over a pty
- 6e. Links that work
- 6e·1. Reviews, threads, and the merge gate
- 6f. Realtime
- 6g. Apps, runs and the crew, where the terminal still shows them