From 8afdf2cbb2a8b6d3324057ffee733e1e91a10d8a Mon Sep 17 00:00:00 2001 From: Kate Bonner Date: Tue, 8 Sep 2026 12:08:21 -0400 Subject: [PATCH 01/13] =?UTF-8?q?docs(app):=20ADR=200006=20=E2=80=94=20the?= =?UTF-8?q?=20brand=20sheet=20is=20the=20design=20system's=20source=20of?= =?UTF-8?q?=20truth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Twelve laws with no exceptions (accent = call to action, no shadows, 1px hairlines, one corner, the de facto type scale, motion, interaction, namespace by @theme declaration, self-enforcement, the 4px unit, 400 ms response, visible progress), a Principles tier of the Laws of UX under their correct names, the values staged for packages/app/src/design-system/style-tokens.css, and the landing plan (definition first; static gate required against a baseline; rendered and skins checks informational until each law's deltas clear). CONTEXT.md gains the Brand vocabulary (18 terms) and retires Exemptions, State edges, Yellow selection, and Elevation. ADR 0003 consequence 1 is superseded: the engines are inside the accent law and the live marker is neutral. --- packages/app/CONTEXT.md | 94 +++++++++++++ ...et-is-the-design-system-source-of-truth.md | 130 ++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 packages/app/docs/adr/0006-brand-sheet-is-the-design-system-source-of-truth.md diff --git a/packages/app/CONTEXT.md b/packages/app/CONTEXT.md index 53a5f5e8f..3d1e98341 100644 --- a/packages/app/CONTEXT.md +++ b/packages/app/CONTEXT.md @@ -76,6 +76,88 @@ Connections, solver/model Defaults, and Settings. These are set-and-forget surfa not places a researcher browses. _Avoid_: Settings menu (the zone is broader), footer +### Brand + +**Brand sheet**: +The one token file, canonical for every design-system value — hue, corner, unit, type, hairline, focus, motion — and holding nothing else. A value that disagrees with it is wrong; a value not in it is not part of the system; a rule that styles a surface is a **Skin**. See ADR 0006. +_Avoid_: theme (that is the **Theme palette**), stylesheet, design tokens file, polish + +**Skin**: +A rule that applies the **Brand sheet** to a named surface — the composer, the send button, the solver banner. Skins live in their own stylesheet, are gated exactly like any component, and never define a token or force themselves over the sheet. See ADR 0006, Law 9. +_Avoid_: polish, override, theme tweak, component style (as something exempt) + +**Design law**: +A rule of the brand that no token can express — how the accent may be paired, what a border is, what may move. Laws live in ADR 0006 with their reasons and are enforced by the **Design gate**. +_Avoid_: guideline, style rule, convention, best practice + +**Design gate**: +The checks that fail a build when code disagrees with the **Brand sheet** or a **Design law**: the static gate reads the source for literals, the rendered gate reads the running app in both schemes. A law without a gate is not yet a law. +_Avoid_: lint, style check, visual test + +**Brand accent**: +The one yellow the interface carries, and it carries it for one reason: it is the **Call to action**. Paired only as black-on-yellow or yellow-on-dark, never as a yellow foreground on a light surface, and never for selection, emphasis, or status. See ADR 0006, Law 1. +_Avoid_: highlight colour, primary colour, gold, "the yellow" (unqualified), second accent, selected colour + +**Call to action**: +The one control on a surface that prompts the researcher's immediate next action — send, connect, approve, begin — and the only thing that carries the **Brand accent**. A surface has at most one; a state is never one; yellow prompts and never points. See ADR 0006, Law 1. +_Avoid_: primary button (generic), accent button, yellow button, highlight, active state + +**Semantic state**: +The colours that mean something — success, warning, danger, and the session status dots — and the only chromatic hues besides the **Brand accent**. Never decorative, never an accent, never an edge; the meaning is always also carried by text or an icon. See ADR 0006, Laws 1 and 3. +_Avoid_: status colours (as a design category), alert colours, accent (for these), error border + +**Data-viz palette**: +The defined set of categorical colours the context tree and brain engines paint from — one hue per category, chosen under the accent law. The canvas is inside the system: its live marker is a pointer and is neutral. See ADR 0006, Law 1. +_Avoid_: chart theme, secondary palette, accent set, exemption + +**Principle**: +A behavioural rule of the interface — reduce choices, show progress, end flows well — that a designer checks in review rather than a gate checks in a build. Principles guide decisions; **Design laws** bind them. See ADR 0006, Principles. +_Avoid_: law (for these), heuristic (as the canonical word), guideline, rule of thumb + +**Theme palette**: +The surfaces, text, borders, and **Semantic state** colours the theme owns per scheme. The **Brand sheet** consumes it and never redefines it; the palette's accent is the **Brand accent** by reference. See ADR 0006, Law 8. +_Avoid_: theme (as the whole design system), skin, colour scheme (that is light / dark), palette (unqualified) + +**Hairline**: +The only edge the interface draws: one width, solid, in a neutral tone — or the call-to-action edge, ink on the armed fill and the scheme's own ink or cream on the idle outline. A state is never an edge; a floating surface is a hairline-bordered surface on its own ground layer; nothing casts a shadow. See ADR 0006, Laws 2 and 3. +_Avoid_: emphasis border, ring, inset shadow, bevel, elevation, divider (as a distinct thing), error border + +**Corner**: +The one rounding every control and surface shares; dots and round pills are circles. Nothing is rounded more or less than anything else, and hierarchy never comes from corner size. See ADR 0006, Law 4. +_Avoid_: radius scale, size-tiered corners, pill (for a non-round control), soft corner + +**Unit**: +The one spacing measure every padding, margin, and gap is a multiple of. A half-unit exists only as an inset beside a **Hairline**. See ADR 0006, Law 10. +_Avoid_: spacing scale, half-step, tight padding, off-grid (say "off the unit") + +**UI type**: +The size at which the interface describes itself — labels, controls, chrome, panels. Everything the interface says about itself is UI type; what the agent and researcher write is **Content type**. See ADR 0006, Law 5. +_Avoid_: body text, small text, chrome text (as a size), base size + +**Content type**: +The size at which what the agent and the researcher write — message prose and tool output — renders, one step above **UI type**. See ADR 0006, Law 5. +_Avoid_: body text (ambiguous between the two), base size, base text, prose size + +**Focus ring**: +The thin outline, set off from the control, that marks keyboard focus — ink on light, cream on dark, never the **Brand accent**, because yellow prompts and never points. Present on every interactive element and never removed. See ADR 0006, Laws 1 and 7. +_Avoid_: focus outline (generic), focus state (broader), blue ring, yellow ring, glow + +**Arrival**: +The first of three movements the interface makes: a block entering the timeline, or a card swapping into the dock, rises and un-blurs into place; nothing moves on hover, press, or state change. Under reduced motion an arrival is a fade. See ADR 0006, Law 6. +_Avoid_: animation (generic), hover lift, micro-interaction, bounce, transition (for the movement itself) + +**Glide**: +The second movement: a marker that follows attention — the tour spotlight — moving between positions, position only. Under reduced motion a glide is a jump-cut with a fade. See ADR 0006, Law 6. +_Avoid_: slide, tween, animated highlight, teleport (that is the reduced-motion fallback, not the behaviour) + +**Working**: +The third and last movement: an indeterminate indicator — a loader, a pulse, a shimmer — running continuously within its own bounds, never travelling or scaling. Under reduced motion it holds a still frame. See ADR 0006, Law 6. +_Avoid_: spinner (as the concept), loading animation, busy state (broader), determinate progress (shares the marker; it is not a movement) + +**Quiet control**: +A control that is visually muted until hovered or focused but always exists — in the tab order, named, and fully visible on focus. Hover changes its emphasis, never its presence. See ADR 0006, Law 7. +_Avoid_: hover-reveal (as the concept), hidden control, hover-only action, ghost button (that is a variant, not a behaviour) + ### Ambient surfaces **Live-solve indicator**: @@ -114,6 +196,18 @@ only surfaces its state), setup wall These were real surfaces before the chat-first redesign and are intentionally gone. A reader meeting them in older code or notes should map them forward: +- **Exemptions** (files and surfaces outside the design laws — logos, the poster, the + engines, the diff engine, the sheet itself) → gone under ADR 0006. Every surface is + inside the system; what was exempt is now defined, or is a delta. +- **State edges** (a red, amber, or green border carrying success / warning / danger) → gone + under ADR 0006 Law 3. A state is shown by fill, text, or icon; the edge stays a **Hairline**. +- **Yellow selection** (the soft-yellow fill and yellow edge on a selected or active row) → gone + under ADR 0006 Law 1. Selection is neutral — a ground layer and a strong hairline; the + **Brand accent** marks only the **Call to action**. +- **Elevation / float shadow** (the named float shadow, the theme's elevation tiers, the + utility shadows) → gone under ADR 0006 Law 2. A dialog, popover, palette, or toast is a + **Hairline**-bordered surface on its own ground layer; nothing in the interface casts a + shadow. - **Home / Dashboard** (the aggregate widget page) → dissolved. Its widgets became **Rail surfaces**, **Account zone** entries, in-Chat context, or first-run affordances. - **Home top chrome strip** (the Home page's brand mark + defaults capsule + projects diff --git a/packages/app/docs/adr/0006-brand-sheet-is-the-design-system-source-of-truth.md b/packages/app/docs/adr/0006-brand-sheet-is-the-design-system-source-of-truth.md new file mode 100644 index 000000000..db52d8fcb --- /dev/null +++ b/packages/app/docs/adr/0006-brand-sheet-is-the-design-system-source-of-truth.md @@ -0,0 +1,130 @@ +# The brand sheet is the design system's source of truth + +Status: proposed + +Tracking: (issue to follow on acceptance) + +Supersedes: ADR 0003, consequence 1 only — the context tree and brain engines are *not* exempt from the accent law, and no yellow marks the live position; they paint from the data-viz palette and the live marker is neutral (Law 1). ADR 0003's `#fff676` was already stale (the engines paint `#FFE614`); on acceptance 0003 receives a matching status line. + +Three artifacts described the Amicode design system and disagreed: the `amicode-design-system` skill (prose: `#fff676`, an 8/12px radius scale, a 14/16px type step), `packages/app/src/design-polish.css` — henceforth `packages/app/src/design-system/style-tokens.css` (tokens: `#FFE614`, a flat 4px radius, no 14px step, and a header calling itself the single source of truth), and `script/check-design-tokens.mjs` (which enforces the sheet and nothing else). We decided that the **brand sheet's token block is canonical for every value** — hue, corner, grid, type, hairline, focus, motion — that the **laws** a token cannot express live in this ADR with their reasons, that `CONTEXT.md` carries the vocabulary only, and that the skill is **regenerated** from these three and never hand-edited again. The gates enforce the sheet and the laws; a value disagreement is always resolved in favour of the sheet. + +**Considered:** the skill as canonical with the sheet implementing it (rejected: prose with no enforcement is exactly the arrangement that drifted — the skill fell a full re-baseline behind the code and nothing caught it); a standalone spec both derive from (rejected: a third artifact to keep in sync, duplicating values that already exist as tokens and are read live by the rendered gate). + +**Consequences:** the skill must be regenerated before it is trusted again; a law without a gate is not yet a law; the sheet's header stops pointing readers at the skill for the rules; two laws below were decided *over* the recommended option (no shadows, no state edges) and are recorded with those trade-offs precisely so a later reader does not "fix" them back; and there are **no exceptions** — every carve-out an earlier draft carried is now either a definition or a delta. + +## Laws + +Rules the brand carries that no token can express. Each states the rule, the reason a future reader will need, and the gate that makes it a law rather than a suggestion. **There are no exceptions.** Every surface, file, and pixel the app paints is inside the system: where an earlier draft exempted something, the law now either *defines* it — a third-party mark is content, a face declaration is a token definition, a palette seed is the palette's definition — or records it as a delta the code must close. The behavioural rules that a designer checks in review rather than a gate checks in a build are **Principles**, below the laws, and are named as such. + +### Law 1 — the accent law (colour categories and the call to action) + +There are four categories of colour and only four. **Brand accent** is the one yellow (`--accent`, `#FFE614`): it appears only as *black-on-yellow* (a fill carrying ink text or icon and an ink edge) or *yellow-on-dark* (a foreground over a dark surface) — never a yellow foreground on a light surface, never a second brand hue, never gold. **Neutrals** (the theme's greys) carry contrast on light; the accent carries warmth. **Semantic state** (success, warning, danger, info, and the session status dots) are the only other chromatic hues: they mean something, the meaning is always also carried by text or an icon, they are never decorative, never an accent, and — by Law 3 — never an edge. **Data-viz** (the context tree and brain engines) paints from a *defined* categorical palette — `--viz-` tokens in the sheet, one per node kind, chosen under this law: no brand yellow, gold, or darkened yellow — the accent is the call to action's alone, on a canvas as anywhere — and each hue clears 3:1 on the canvas ground in both schemes; the canvas obeys every other law as well: its live-position marker is a pointer, and pointers are neutral, ringed in the focus ring's tones (ink on light, cream on dark), never yellow. + +**The brand accent is the call to action.** It appears only on the control that prompts the researcher's immediate next action — send, connect, approve, begin — and on nothing else: never selection, emphasis, status, or decoration. A **surface** — the Chat, each open Panel, each dialog, and each ambient surface (the Setup nudge, the Live-solve indicator) — has its root marked `data-surface` and carries at most one call to action, marked `data-cta`; the topmost modal surface suspends the ones beneath it; the Rail and the titlebar chrome are not surfaces and carry no call to action, though the Live-solve indicator mounted in the titlebar is a surface; inside the Chat the composer dock's swap grammar already makes send and approve mutually exclusive. A control that is both selected and the call to action takes the call-to-action role, and its selectedness is carried by a non-colour signal (Law 7). A surface also offers few choices at once: any one decision — a menu, a picker, a dialog's row of actions — shows at most seven options ungrouped; more are chunked into named groups (Principles: Hick's Law, Miller's Law). On dark, the theme's "text accent" and "icon accent" roles resolve to neutral, as they already do on light — a yellow glyph on a non-CTA is decoration. The same holds one level down, at the theme's palette **seeds**: a semantic-state or interactive scale is never generated from the brand hue or from a darkened yellow (today `warning` is seeded with the CTA hover, light `info`/`interactive` with an olive `#4A4200`, and dark `interactive` with the accent itself — the seed that generates the selected and interactive borders); the accent and primary seeds stay literal because the scale generator consumes them — they are the palette's definition, bound by this rule, not an exception to it. + +**Yellow prompts; it never points.** Keyboard focus, the tour spotlight, a selected row, the cursor — these say *you are here*, not *do this next*, and they are neutral. The focus ring is ink on light and cream on dark, drawn as an **outline only** (a shadow ring is forbidden by Law 2), 2px with a 2px offset, with its own tokens in the sheet's per-scheme block. *Considered:* the brand accent on the dark focus ring (rejected: yellow would then mean two things on dark — the action, and wherever the keyboard is). *Reason:* `#FFE614` measures 1.27:1 on white — legible only against dark — so yellow cannot define an edge or read as text on light; and a hue that is sometimes brand and sometimes state, or sometimes an action and sometimes a pointer, stops meaning either. *Gate:* the rendered gate's yellow-pairing checks and gold-ramp check; the static gate's arbitrary-colour rule; the Tab walk asserts the focus-ring colour and that it is an outline; and the **call-to-action check** — every element painting `--accent` or `--accent-hover` lies inside a `[data-cta]`, every `[data-cta]` has a `[data-surface]` ancestor (a popover portaled to the body is not a surface), and each `[data-surface]` is the *closest* surface of at most one `[data-cta]` (a suspended surface's call to action is not counted while a modal is above it); the choice check — a `[role=menu]`, `[role=listbox]`, `[role=toolbar]`, or a dialog's action `[role=group]` shows at most seven direct options, or chunks them into *named* groups (`role=group` / `optgroup` with an accessible name); a collection — a Rail surface's list, a file tree, search results — is not a decision and is outside the ceiling; and the data-viz check — the rendered gate reads every `--viz-*` token in both schemes and asserts none lies within tolerance of `--accent`, `--accent-hover`, or the gold ramp and each clears 3:1 on the canvas ground, and the static gate asserts the engines carry no colour literal. + +### Law 2 — the shadow law + +Nothing in the interface casts a shadow. No drop shadow, no inset shadow, no zero-blur ring, no `shadow-*` or `ring-*` utility, on any surface in either scheme. A surface that floats above the app ground — a dialog, a popover, the command palette, a toast — is a **Hairline**-bordered surface on its own ground layer; separation is a hairline and ground contrast, never a bevel. *Reason:* the site differentiates with borders, not bevels; a shadow re-draws an edge the hairline already draws; and "no shadows" is enforceable where "tasteful elevation" is not. *Trade-off accepted (2026-09-07, over the recommended option):* on light, floating surfaces sit near-white on near-white and rely on the hairline alone for lift — one named elevation token (`--elev-float`) was considered and rejected in favour of a rule with no exceptions. `--elev-float`, every `--v2-elevation-*` consumer, and the upstream `@theme`'s nine `--shadow-*` names retire. *Gate:* static rule 8 with `SHADOW_POLICY=none` as the definition (the switch stays only for local triage) and the `shadow-*`/`ring-*` class names banned; the rendered gate asserts `box-shadow: none`, `text-shadow: none`, and no `drop-shadow()` in `filter` on every element, with no exceptions — the vendored diff engine's inset row fills are backgrounds drawn as shadows and become backgrounds. + +### Law 3 — the hairline law + +A border is a **Hairline**: exactly 1px (`--border-width`), solid, in one of five tones — the three neutral tones (`--v2-border-border-base` for a default edge, `-strong` for an active, selected, or focus-within edge, `-muted` for an internal divider) and the **call-to-action edge** — `--accent-edge-ink` (ink) on the armed fill, `--accent-idle-edge` (ink on light, cream on dark) on the idle outline, so the idle send reads on both schemes. Nothing else is a border: not 1.5px or 2px "emphasis", not a shadow (Law 2), not an outline (that is the **Focus ring**), and **a state is never an edge** — success, warning, and danger are carried by fill, text, and icon; the four state-border tokens retire — but only in Law 3's follow-up, together with the icon or word that replaces each edge and the edge-colour check, so that no site loses its only signal at landing. *Reason:* one weight and four tones give every surface the same edge grammar, so emphasis has to come from fill, ink, or the accent — which is where the brand wants it — and a coloured edge is the site's "status = ink chip + a word" rule broken. *Trade-off accepted (2026-09-07, over the recommended option of admitting a state tone where the edge itself is the signal):* an error field loses its red border and shows a hairline plus an icon and a word instead. *Gate:* static rule 7 (`border-width ∈ {0, 1px}`, `border-2/4/8` banned); the rendered gate asserts every border side ∈ {0, 1px} **and** every border colour ∈ {the three neutral tones, the two call-to-action edge tones}. + +### Law 4 — the corner law + +The interface has one corner: every control, chip, input, card, panel, and dialog is rounded by the same `--radius` (4px); dots and round pills are circles (`--radius-full`, 9999px — formerly 999px). The scale names (`--radius-xs` … `--radius-xl`) remain as **value-identical aliases** of `--radius` — the one permanent alias class Law 8 admits, because the rendered gate proves them identical — so call sites do not churn; an alias with a different value is a violation. *Reason:* the site sets a single `--radius-brand`; hierarchy comes from the hairline, the ground layer, and ink — never from corner size — so a scale would only re-open a decision on every card. *Gate:* static rule 2 (no raw radius; the upstream `@theme`'s rem radii deleted; `rounded-2xl` and larger nulled); the rendered gate asserts every corner ∈ {0, `--radius`, full} and every alias resolves to `--radius`. + +### Law 5 — the type law + +Two faces: DM Sans (variable, loaded 100–1000) for everything the interface and the agent say; JuliaMono for data, code, and labels. One size scale, in px: **10** (the floor — badges and meta; nothing renders smaller) · **11** · **12** · **13** (UI — everything the interface says about itself) · **14** (**Content** — what the agent and the researcher write: message prose and tool output, one step above the UI) · **15** (heading) · **18** (title) · **32** (display). Three weights, one word each: **normal** 440 · **medium** 530 · **semibold** 600 — DM Sans' optical regular, medium, and bold at these sizes; every other value retires, including Tailwind's own steps outside the scale (`font-bold`, `text-4xl` and beyond are nulled in the `@theme`, so they do not exist). *Reason:* this is the scale ~350 sites already use and the reason a variable face was chosen; the nominal 400/500 scale the skill described was never true of the product, and a 9px badge is below the floor at which the face is legible. Leading and tracking are not part of the scale — the upstream line-height and letter-spacing tokens stand unchanged and are out of this law's scope. JuliaMono ships one static weight and snaps to it; the weights govern DM Sans. *Gate:* the rendered gate asserts font-size ∈ the scale (a visually-hidden idiom at `font-size: 0` is not text and is outside the scale) and font-weight ∈ {440, 530, 600} on DM Sans text; the static gate bans **every** arbitrary `text-[…]` and `font-[…]` value — a size or weight is always named through its token (Law 8), never restated as a number — and keeps rule 3 (no hardcoded font stacks) for the faces. + +### Law 6 — the motion law + +Two durations and three kinds of movement. **Feedback** (hover, press, focus, state) is 0.16s on colour, border, and opacity only — nothing moves, lifts, or scales under the pointer. **Arrival** is the first kind of movement: a block entering the timeline — or the question card and composer swapping in the dock — rises and un-blurs into place over 0.3s in the website's grammar (`[.2, 0, .2, 1]`), never scales, and leaves faster (0.2s) than it arrived. **Glide** is the second: a marker that follows attention — the tour spotlight ring — may move between positions — bounds only, position and size, never a `transform` scale, never blur — 0.22s, marked `data-glide`. **Working** is the third and last: an indeterminate indicator — a loader, the solver pulse, the timeline's working shimmer — may run continuously as an opacity pulse, a rotation, or a gradient sweep within its own bounds; the element itself never travels or scales; it is marked `data-working` and has no fixed duration; the same marker also marks a determinate progress indicator (Law 12), which is not a movement. The dock's card swap is an arrival and is marked `data-arrive`. Where an exit exists it is shorter than its arrival; today the timeline has none. Under `prefers-reduced-motion` this is a rule, not an exception: the global duration reset the sheet carried retires; a transition or animation may change opacity and colour only — on every element — so every rise, scale, and blur goes to zero, every arrival keeps its fade, a glide becomes a jump-cut with a fade, and a working indicator holds a static frame — so under reduced motion an arrival is still announced by its fade. Every duration is a token: a transition or animation names `--motion-feedback`, `--motion-enter-duration`, `--motion-exit-duration`, or `--motion-glide`, never a literal; a working indicator's period is its own and is not a system duration. *Reason:* feedback that moves bounds jitters and misreports layout; arrival motion says "new" and a fade is the smallest signal that still says it; a marker that teleports loses the reader, one that glides keeps them; the sheet already did the arrival grammar for the timeline — the law makes it apply everywhere and be enforceable. *Gate:* the rendered gate asserts that under reduced motion no running transition or animation changes anything but opacity and colour, and that `transform` transitions and animations exist only on arrivals (`[data-timeline-enter]`, `[data-part-enter]`, `[data-arrive]`), glides (`[data-glide]`), and rotations on `[data-working]`; the static gate bans `transform` in a feedback transition list and any raw duration outside the sheet. + +### Law 7 — the interaction law + +Every interactive element defines five states — default, hover, active, focus-visible, disabled — in both schemes, with the same legibility in each. Disabled reads dead and *is* dead, and a live control never reads disabled: an enabled control never carries `disabled` or `aria-disabled`, and never renders at reduced opacity. The idle **Call to action** is therefore a live control in an idle state — never `:disabled`; pressing it focuses the composer; it is in the tab order; its outline is the idle call-to-action edge (Law 3) — so the site's neutral-to-yellow flip stays exactly as designed (*considered:* disabled-and-dead, rejected: it loses the flip). An icon-only control carries a name. A control never contains a control. A clickable element shows a pointer and offers a hit area of at least 24×24 CSS px, and the **Call to action** sits beside the input it completes — inside the same surface, at the composer's edge for send — never across the window from it (Principles: Fitts's Law). Colour is never the only signal: an element whose colour carries state also carries text or an `aria-label`. Text holds 4.5:1 and glyphs 3:1 against their ground in both schemes; a disabled control's contrast floor is none, because it is meant to read dead — the sweep asserts the floors on enabled controls. The **Focus ring** is never removed. And a control may be **quiet** — muted until hovered or focused, marked `data-quiet`, muted by *tone* (the palette's muted text and icon roles) and never by opacity, since reduced opacity is reserved for disabled and a quiet control must still clear the glyph contrast floor at rest — but it always *exists*: it is in the tab order, it has a name, and it becomes fully visible on focus and on focus-within of its group; hover changes emphasis, never presence. *Reason:* the design wants calm surfaces and a keyboard user is owed the same surface — presence and emphasis are different axes, and conflating them is how the tab-close button came to have no name. *Gate:* the rendered gate's label, nesting, cursor, hit-area, CTA-adjacency (a `[data-cta]`'s box lies inside, or within one unit — `--spacing` — of, the box of its surface's primary input: the first `textarea`, `input`, or `[contenteditable]` in the surface's DOM order, where the surface has one), reads-disabled, colour-only-signal, and Tab-walk checks; the quiet-control check (every `[data-quiet]` is focusable, named, and at full tone on focus-within); the state-parity contrast sweep across the five states; and a press on the idle call to action focusing the composer; the static gate bans `outline: none` without a `:focus-visible` replacement. + +### Law 8 — the namespace law + +One name per value, one owner per name. The **Brand sheet** owns the accent roles (`--accent*`, `--focus-ring*`), geometry (`--radius*`, `--spacing`), type (`--text-*` sizes, `--font-weight-*`, `--font-sans`, `--font-mono`), and motion (`--motion-*`), and it declares the geometry and type names **in Tailwind's own namespace through an `@theme` block**, so a `rounded-md`, `text-sm`, or `font-medium` utility and a CSS `var()` share one name and one value. For that to be an explicit override rather than an accident of import order, the upstream ui package's declarations of **every name the sheet declares** are **deleted wherever they are declared** — the `@theme` block in `ui/src/styles/tailwind/index.css` *and* the `:root` block in `ui/src/styles/theme.css`, both of which land in `@layer theme`, and `ui/src/v2/styles/theme.css`'s `--font-family-text` — so the sheet is the *sole* definer; `--font-family-text`, `--font-family-sans`, and `--font-family-mono` survive one cycle as aliases of `--font-sans` / `--font-mono`. Upstream names the sheet does *not* declare — `--font-size-small/base/large/x-large`, `--font-weight-regular`, `--line-height-*`, `--shadow-*` — persist until the law that retires them lands, then alias to the scale (Law 5) or to nothing (Law 2). The upstream `--*: initial` that nulls Tailwind's defaults is retained; the sheet re-nulls the off-scale steps as belt and braces. The **Theme palette** (the `--v2-*` surfaces, text, borders, and semantic-state colours per scheme, including the session status dots, which move from the sheet's `--status-*` into the palette as `v2-state-dot-*` with a one-cycle alias) is owned by the theme JSON, which the sheet consumes and never redefines; the palette's accent is `var(--accent)` by reference, never a second literal, and its focus colour is `var(--focus-ring)`. Scheme-forked Amicode roles that are not palette (`--prompt-bubble-*`, `--focus-ring`, `--accent-idle-edge`) live in the sheet's per-scheme block beside the accent roles; the scheme-fixed ones (`--fg-on-dark`, `--fg-on-dark-muted` — content on a fixed dark scrim) beside them. The `@font-face` declarations are token definitions and move into the sheet with the faces they define. The focus alias serves the palette's outline consumers; the few that used the focus colour as a border or a shadow ring migrate to the strong hairline or an outline, as Laws 2 and 3 require. The sheet's parallel vocabularies (`--font-size-*`, `--space-*`) retire, surviving only as aliases for one migration cycle; the one permanent alias class is value-identical aliases the rendered gate proves identical (Law 4's radius names). + +**A name the law must avoid:** `--text-base` is never a size in this app. It has three definers today — the upstream `@theme` as a size alias, the legacy theme stylesheet as a text *colour*, and the theme loader, which resolves a `text-base` colour and injects it through a `