# Package emission — design.md → the token artifacts

One source, two derivations: the project's `design.md` is canonical; the artifacts are compiled from it and **never hand-edited**. Both live beside `design.md` in the design folder — `packages/common/` at the base of a multi-package workspace, `src/common/` in a single-package repo, the path `--project .` resolves and `--write` emits into — and the app's global stylesheet imports both. Re-emit on every design.md change; the ship gate's drift checks byte-compare a fresh emission against each committed artifact.

| Artifact | Emit with | Carries | Compiled by |
| --- | --- | --- | --- |
| `design.tokens.css` | `node scripts/validate-design-md.mjs --project . --emit --write` | the color roles in both themes, the type ramp, elevation, the motion base, layout | the validator |
| `personality.css` — `primitives: nurix` only | `node scripts/validate-design-md.mjs --project . --emit-personality --write` | the six traits compiled into the token families every `@nurix/components` component reads: `--density-*`, `--motion-duration-*`, `--radius-scale`, `--elevation-*`, `--texture-*`, `--accent`/`--ring` | `compileToCss` from `@nurix/components/lib/personality`, reached through the validator from the project's own install |

## Coverage

`design.tokens.css` is complete by construction — the validator fails the design.md before emission otherwise: every one of the 36 color roles in **both** themes, the full type ramp, the elevation system, the motion base, layout dimensions. Composition consumes tokens and never invents values; switching packages re-skins the whole app.

## The personality block

`@nurix/components` derives feel — control geometry, spacing, timing, radius, depth, grain, accent — from six traits and reads nothing else for it: a component's transition is `var(--motion-duration-base)`, a control's height is `var(--density-control-height)`. The design.md states the traits once, in its design-values block, and the library derives every value:

```yaml
personality:
  density: compact      # compact | regular | airy — control height and padding, container padding, gaps
  pace: 0.3             # 0 still · 0.5 the library's neutral timing · 1 lively
  edge: sharp           # sharp | regular | soft — the radius scale
  depth: flat           # flat | layered | elevated — the elevation ladder
  texture: clean        # clean | grainy — grain opacity on effect surfaces
  tone: neutral         # neutral | expressive — whether the accent re-anchors on the primary
```

- **§4–§7 are read into the traits, never into pixels.** A §6 of "flat, hairline-separated" is `depth: flat`; a §7 of "utilitarian, nothing animates twice a day" is `pace` at 0.2–0.35; an operator §4 is `density: compact`. Declare only what differs from neutral (regular · 0.5 · regular · layered · grainy · neutral). An absent block ships the library's defaults and the validator warns (`dmw-05`) — a project that never declared its feel is how these families go unused.
- **`motion.duration` and `motion.easing` are the app's own vocabulary** (`--duration-panel`, `--ease-standard`, read by app-owned animation); the library's components never read them. `pace` is what retimes the library — and, through the library's default transition duration, every plain Tailwind `transition-*` utility in the app. State both from the same §7 reading.
- **The block is closed-vocabulary and fails closed** (`dmv-12`): an unknown trait, an out-of-range pace, or a block under `primitives: native` (nothing there reads it) fails validation.
- **The compiler is the project's own install.** `--emit-personality` resolves `@nurix/components` by walking up from the design.md, then one and two levels into the workspace (`packages/<app>`), because a pnpm root hoists nothing. Several installed versions is an ambiguity the app package resolves: `--project . --emit-personality --write --lib packages/<app>`. The artifact's header names the library version that compiled it, so a library upgrade re-emits by drift, never by hand.
- **The artifact is committed, never injected.** `personality.css` is the product's artifact — inspectable, diffable, drift-checked at the ship gate — imported after the library's `styles.css` so its `:root` wins on order. `<PersonalityStyle>` from the same subpath serves a runtime switch (a density toggle, a preview) and never replaces the artifact.

## Emission constraints

Each prevents a known silent-failure class:

- **Specificity armor.** Theme blocks are `:root:not(.dark)` and `:root.dark` — both specificity (0,2,0) — keyed on the `.dark` class the library compiles into its own stylesheet. A `data-theme` attribute keys none of the library's `dark:` variants, its theme toggle, or its toaster: an app on the attribute inverts its tokens and renders mixed library chrome.
- **Theme-invariant tokens sit in a bare `:root`.** Only the colour roles are themed. Font stacks, the type ramp, radii, shadows, motion and layout emit into a bare `:root` block ahead of the two theme blocks. Behind `:root:not(.dark)` they match nothing once `.dark` is on the root, and every `font-size: var(--text-…)` collapses to an inherited 16px in dark mode while the colours still look right.
- **The ramp emits Tailwind's composite sub-keys.** `--text-<step>` with `--line-height`, `--font-weight` and `--letter-spacing` sub-keys are exactly what the `text-<step>` utility applies. A `-size`/`-line`/`-weight` suffix form generates junk utilities and leaves `text-<step>` undefined. The `--font-feature-settings` and `--font-family` sub-keys are inert for the utility and are read through `var()`. The dense operator tier's steps are optional: absent steps emit nothing and warn once per package (`dmw-08`).
- **The tone trio.** `success`, `warning`, and `info` reach the library as `--tone-success|warning|info` — what a status dot (`<Chip prefix="dot" color="var(--tone-success)">`) and the copy button's check read, falling back to the library's own hue when a role is absent. Declare all three, in both themes — a missing `info` (`dmw-06`) puts the library's blue beside the package's green and amber.
- **Font binding by derivation.** Font stacks bind to installed packages under **registered family names** (`"Inter Variable"`, not `"Inter"`), each with its declared fallback stack — otherwise the flagship font silently never renders. Install the font packages the design.md declares (e.g. Fontsource) and verify the name matches what the package registers. The library's `font-sans` / `font-mono` / `font-display` utilities compile to `inherit` on purpose (a ramp, never a face): the app declares its own theme keys in a plain `@theme` pointing at the emitted slots — `--font-body: var(--font-sans)`, `--font-heading: var(--font-display)`, `--font-code: var(--font-mono)` — and sets the ambient face on `body` in `@layer base`.
- **Every authored token group reaches a named consumer.** An authored ramp with no emitter vanishes without a trace — wire each group (colors → theme classes/utilities, ramp → text styles, elevation → radius/shadow utilities, motion → duration/easing vars) and let the ship gate's reach check prove it. The personality families reach the library by construction; the app's `--duration-*` / `--ease-*` reach app-owned animation code, or they are unused.
- **Compile the emitted CSS once per change** — a stray utility string inside a CSS comment can defeat a fully green byte-level check.
- **One linear precedence, later wins:** package defaults → brief bindings → recorded deviations. Values resolve at emission; the runtime cascade carries only contexts (theme, density, `data-mode="inline"`).

## Stack neutrality

`design.tokens.css` is plain CSS custom properties — every stack consumes it. Under `primitives: nurix`, Tailwind maps the properties through the theme config; under `native`, the incumbent styling layer reads the same variables. No emission step is React-specific. `personality.css` is the one stack-bound artifact: compiled by and for `@nurix/components`, it does not exist under `native`.
