# Dashboard typography tokens

> Single source of truth for text sizing across the Minions dashboard.
> Centralized in W-mq1c8og40003a7cf (follow-up to PR #66 size primitives
> and PR #80 `.btn-add` consolidation).

## Why this exists

The dashboard SPA renders text from ~30 modules (`dashboard/js/render-*.js`,
`dashboard/pages/*.html`, `dashboard/styles.css`, plus the slim UX prototype
in `dashboard/slim/`). Without a shared token system, every callsite picks
its own pixel size. Cards drift, captions disagree, headers don't line up
across pages, and changing the design language costs ~700 edits.

Two pieces solve this together:

1. **Size primitives** (`--text-xs` ... `--text-display`) — raw px values.
   The 10 declared sizes cover every visual role the dashboard currently
   uses. Anchored at `--text-base = 12px`. PR #66 migrated ~664 callsites
   from raw `Npx` to these tokens.
2. **Role aliases** (`--text-heading`, `--text-body`, etc.) — semantic
   names that point at size primitives. Added in W-mq1c8og40003a7cf so
   new code talks in roles, not px. The size primitive stays the single
   source of truth; role names are aliases.

## Files this lives in

| File                            | What it owns                                                                                               |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `dashboard/styles.css`          | Canonical token declarations (`:root`), utility classes, and main-dashboard CSS rules.                     |
| `dashboard/slim/styles.css`     | Duplicate token declarations + utility classes for the feature-flagged slim UX (self-contained stylesheet). |
| `dashboard/docs/typography.md`  | This file — the contract.                                                                                  |
| `dashboard/docs/typography-audit.md` | The pre-migration audit + rounding decisions.                                                         |
| `test/unit/dashboard-font-size-tokens.test.js` | Tripwire that fails CI if raw `Npx` / `Nem` font-sizes leak back in.                          |

A tripwire asserts the slim token VALUES match the main values so the
two stay in lockstep.

## Size primitives

| Token              | Value | Common use                                                |
| ------------------ | ----- | --------------------------------------------------------- |
| `--text-xs`        | 10px  | Smallest legitimate text; sidebar badges, count pills     |
| `--text-sm`        | 11px  | Status badges, chip / tag labels                          |
| `--text-base`      | 12px  | Captions, secondary metadata, dense table cells           |
| `--text-md`        | 13px  | Default for slim chat / detail tables / button labels     |
| `--text-lg`        | 14px  | Body text, modal textarea, card titles                    |
| `--text-xl`        | 16px  | Main `<body>` font, secondary headers, chat input         |
| `--text-2xl`       | 18px  | Section headers, page headers, modal h3                   |
| `--text-stat`      | 22px  | Stat counter readouts, member emoji glyphs                |
| `--text-stat-lg`   | 28px  | Larger stat readouts, completions-card type emoji         |
| `--text-display`   | 32px  | Page hero / markdown H1, agent-detail emoji               |
| `--text-code`      | 0.9em | Inline `<code>` — relative so it scales with parent text  |

> `--text-code` is intentionally relative (`em`, not `px`). Inline code
> should read 10% smaller than whatever surrounds it. This is the one
> token that breaks the "all px" rule and is explicitly allowlisted in
> the tripwire.

## Role aliases (preferred for new code)

Use the role token when adding new UI. It survives a future size-scale
redesign without callsite edits.

| Role token             | Aliases             | Intended use                              |
| ---------------------- | ------------------- | ----------------------------------------- |
| `--text-role-display`  | `--text-display`    | Page hero, dashboard title                |
| `--text-heading`       | `--text-2xl` (18px) | Section + modal headers                   |
| `--text-subheading`    | `--text-xl` (16px)  | Secondary headers, card titles            |
| `--text-body`          | `--text-lg` (14px)  | Default body text, paragraphs             |
| `--text-meta`          | `--text-md` (13px)  | Captions, timestamps, secondary metadata  |
| `--text-caption`       | `--text-base` (12px)| Small labels, table cells                 |
| `--text-micro`         | `--text-sm` (11px)  | Chip / tag pill labels                    |
| `--text-code`          | (`0.9em`)           | Inline code                               |

## Utility classes

Each role token has a matching utility class for callsites that can't
write a CSS rule (inline `class="text-meta"`, template strings, etc.).
Only `font-size` is set — weight, color, line-height stay with the caller.

```html
<span class="text-meta">3 min ago</span>
<h3 class="text-heading">Active dispatches</h3>
<code class="text-code">npm run lint</code>
```

Available classes: `.text-display`, `.text-heading`, `.text-subheading`,
`.text-body`, `.text-meta`, `.text-caption`, `.text-micro`, `.text-code`.

## How to add a new text style

1. Pick an existing role token. The 7 roles + 1 code token cover every
   case the audit found.
2. If your value doesn't fit any role, round to the nearest primitive
   and document it inline. Inventing a new token requires changing
   `dashboard/styles.css`, `dashboard/slim/styles.css`, this doc, AND
   the tripwire.
3. NEVER hand-roll `font-size: 14px` in CSS / inline style / JS template
   string. The tripwire test `test/unit/dashboard-font-size-tokens.test.js`
   fails CI.

## Exceptions / allowlist

These declarations are intentional and exempt from the tripwire:

- `:root[data-font-size="…"]` rules in `dashboard/styles.css` — these
  declare the user-level zoom scale, not text size.
- SVG `font-size='90'` inside the `data:image/svg+xml` favicon link in
  `dashboard/layout.html` and `dashboard/slim/layout.html` — runtime
  data URL, CSS variables don't resolve inside it.
- The `--text-*` token declarations themselves in `:root` blocks. Each
  primitive is `calc(<base>px * var(--minions-text-scale))` — the
  `--minions-text-scale` knob (W-mq71mqtx00060fa9) nudges the dashboard's
  real text size from one place (main `1.05`, slim pinned to `1` so the
  slim UI keeps its current sizing). The base px stays the source of truth
  and must match between `dashboard/styles.css` and
  `dashboard/slim/styles.css`.

Everything else MUST go through a token.
