From 4a58808d55bf63f7328df43bb7eb0d4e8c4363d5 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Tue, 1 Sep 2026 15:11:55 +0200 Subject: [PATCH] docs(design): write the diagram system into DESIGN.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DESIGN.md did not contain the word "diagram", and that silence is the whole reason this drifted: the diagram surface became the single largest violation of this document's own STRICT rules — no hex outside `tokens.css`, every dark remap in `theme.css`, components consume semantic tokens — while the document said nothing about it either way. A lint stops a color reaching a fence; only this explains what to write instead. The new section carries the role table, the three layout kinds, how a role becomes a surface/border/label, and the four surfaces that share the vocabulary. It also records the two judgment calls a reader would otherwise have to reverse-engineer: why `queued` and `mergify` share one accent, and why diagram borders lift toward white on dark when "the product palette does not flip" three sections above. The STRICT rules and the self-correction list gain the diagram case, and the SVG-hex exemption now says explicitly that diagrams are not covered by it. `AGENTS.md` gets the short version and the new command, per this file's own instruction to keep the two aligned. Also normalizes the diagram code to the site's American spelling. The three commits below this one were written in British English, which no other file in the repo uses — and the lint prints its guidance to contributors of a site that says "color". It is a style pass rather than a fix, folded in here because this is the commit that codifies house style; it changes no behavior, and the tests and the lint pass unchanged. Change-Id: Id961fd51cd73f70a9cab6ebc897c457e6dbfc581 --- AGENTS.md | 9 ++- DESIGN.md | 95 ++++++++++++++++++++++++++- plugins/remark-graphviz.test.ts | 6 +- plugins/remark-graphviz.ts | 8 +-- scripts/check-diagram-tokens.mjs | 24 +++---- scripts/check-diagram-tokens.test.mjs | 16 ++--- src/components/GitGraph.astro | 2 +- src/styles/index.css | 4 +- src/styles/theme.css | 10 +-- src/util/diagramSvg.ts | 8 +-- 10 files changed, 141 insertions(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 028c6d837e..3fa440d0d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ ## Design System See **[DESIGN.md](./DESIGN.md)** for the docs site's design system: tokens, typography utilities, -dark-mode rules, and STRICT code rules. +dark-mode rules, diagram roles, and STRICT code rules. When you write or modify any styling, read `DESIGN.md` first. The rules there override defaults — no hex literals, no Chakra references, no `--color-mergify-blue` references, and components should @@ -13,6 +13,12 @@ use semantic tokens by default so dark mode works automatically. Product-specifi (callouts, brand-colored buttons, product pills) may use `var(--color-*)` primitives directly as a narrow documented exception — see `DESIGN.md` for the policy. +**Diagrams never name a color.** A Graphviz fence, `` and `` all name a +*role* — `queued`, `pending`, `merged`, `failed`, `config`, `mergify`, `datastore`, `external`, +`batch`, `muted`, `chrome` — and the page resolves it at paint time, which is what makes them +theme-aware. `pnpm check:diagram-tokens` fails CI on a color in a diagram. See the "Diagrams" +section of `DESIGN.md` before adding or editing one. + ## Quick Facts - **Framework**: Astro 5.x with MDX @@ -43,6 +49,7 @@ pnpm test # Run Vitest unit tests # Safety pnpm check:internal-leaks # Scan docs for leaked internal information +pnpm check:diagram-tokens # Scan diagrams for hardcoded colors ``` ## Key Directories diff --git a/DESIGN.md b/DESIGN.md index 91bfd296b3..bf7bdb4426 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -17,6 +17,8 @@ source files listed below; the code is the source of truth. | Typography utility classes | [`src/styles/typography.css`](./src/styles/typography.css) | | Global content rules + section-accent system | [`src/styles/index.css`](./src/styles/index.css) | | Product accent colors (canonical source) | [`mergify.com/DESIGN.md`](../mergify.com/DESIGN.md) — "Product Accent Colors" section | +| Diagram roles + paint rules | [`src/styles/index.css`](./src/styles/index.css) — the `.dg` block | +| Diagram SVG post-processing | [`src/util/diagramSvg.ts`](./src/util/diagramSvg.ts) | | Reusable Astro components | [`src/components/`](./src/components/) | | Page layout templates | [`src/layouts/`](./src/layouts/) | | Icons (Phosphor bold + custom SVGs) | Inline `?raw` imports; no icon component abstraction yet | @@ -210,6 +212,90 @@ surrounding surface contrast changes instead. 4. Document the new token in this file under the appropriate section. 5. Consume the semantic token — never the primitive — in components. +## Diagrams + +Diagrams were the one surface that escaped this document, and it showed: 63 distinct colors across +18 pages, only 10 of which existed anywhere in `tokens.css`, in four unrelated dialects, with a +dark mode that was a hardcoded list of six strings a build-time plugin matched against. This +section exists so it does not happen again, and `pnpm check:diagram-tokens` enforces the one rule +that matters. + +**A diagram never names a color.** It names a *role*, and the page resolves the color at paint +time. Graphviz copies a `class` attribute verbatim into the SVG, so a role survives the render and +CSS can do the rest — which is why dark mode needs no second render and no second palette. + +### Roles + +| Role | What it means | +| --- | --- | +| `queued` | in the queue, waiting its turn | +| `pending` | running now — validating, testing | +| `merged` | merged, passed, done | +| `failed` | failed, dequeued, cascaded out | +| `config` | configuration and inputs | +| `mergify` | a component we run | +| `datastore` | Postgres, Redis, storage | +| `external` | GitHub, CI, third parties — not us | +| `batch` | a grouping container | +| `muted` | skipped, dashed, de-emphasized | +| `chrome` | edges, arrowheads, captions | + +`plain` is not a role: it marks an element as a caption rather than a box, and combines with one +(`class="merged plain"`). The plugin infers it for a shape Graphviz drew with no border, so +`shape=plaintext` needs nothing. + +`queued` and `mergify` deliberately share the Merge Queue teal. Same color, two names, because +"waiting in the queue" and "a service we run" are the same idea on two different kinds of diagram, +and a role name that lies is worse than a duplicated accent. Adding a role costs three lines: an +accent in `theme.css` (both blocks), a `.dg .` rule in `index.css`, and an entry in +`DIAGRAM_ROLES` in `src/util/diagramSvg.ts`. + +### Diagram kinds + +A fence opts into a layout with its class — `` ```dot class="queue" `` — which sets direction and +spacing, never color. A fence that names none lays itself out. + +| Kind | For | +| --- | --- | +| `queue` | left-to-right pipelines: queues, batches, CI fan-out | +| `flow` | top-to-bottom state machines and decision trees | +| `arch` | boxes and wires, orthogonal routing, wide labels | + +### How a role becomes a color + +Each role carries one accent. `index.css` mixes it against the page background to produce a +surface, a border and a label, so a diagram tints toward whatever surface it sits on. Dark mode +changes only how much (`--dg-tint`, `--dg-ink`, `--dg-edge-lift`) and the neutrals — never the +product accents, per "Product palette does not flip" above. Borders do lift toward white on dark: +that is a value *derived* from an accent, not a flipped accent, and a `--color-green-700` outline +on the dark page surface is otherwise too dim to read. + +A container is mixed at a fraction of a node's tint (`--dg-tint-cluster`) so a node of the same +role reads as sitting on top of it rather than dissolving into it. + +### The four surfaces + +All four emit the same `node` / `edge` / `cluster` groups and the same role classes, and all four +are painted by the `.dg` block in `index.css`: + +- `` ```dot `` fences, rendered by `plugins/remark-graphviz.ts` +- `` graph mode, also Graphviz +- `` linear mode, hand-placed SVG +- ``, hand-placed SVG + +`src/util/diagramSvg.ts` holds what the two Graphviz surfaces share. Before this, each had its own +`COLORS` map and its own dark-mode string-replace, which is how they drifted apart. + +### Writing one + +- Name a role, never a color. `PR1 [class="queued"];`, not `PR1 [fillcolor="#347D39"];` +- Keep cluster labels short. Graphviz sizes a container to fit the label it measured in Helvetica, + and the page paints it in Inter at weight 600 — roughly 4% wider. Node labels have margin to + absorb the drift; cluster labels do not. +- Do not set `fontsize`, `nodesep` or `ranksep` unless the diagram genuinely needs it. Every fence + gets a shared set of defaults, and a fence that overrides them stops matching its neighbours. +- `none` and `transparent` are shape, not color, and the lint knows the difference. + ## Typography Two font families: @@ -334,7 +420,8 @@ These rules apply when writing or generating component, style, or page code. `var(--color-*)` primitives are only allowed inside `tokens.css` and `theme.css`. - **Inline SVG `fill`/`stroke`**: the only place raw hex is permitted outside `tokens.css`. Use `currentColor` wherever possible; fall back to a `var(--color-*)` primitive only when the SVG - must carry its own color independent of the theme. + must carry its own color independent of the theme. **Diagrams are not covered by this + exemption** — they name a role and CSS resolves it; see "Diagrams" above. - **Typography**: use the utility classes (`heading-section`, `text-subtitle`, etc.) on custom pages. Do not compose ad-hoc `font-size` / `font-weight` / `letter-spacing` combos from scratch. - **Dark mode**: never add a `:root.theme-dark` block inside a component-scoped `