Console UX Rules (UXR catalog — BINDING)

Status: Binding (ratified 2026-07-11) Scope: all console UI under src/Web/StellaOps.Web/** Source sprint: docs/implplan/SPRINT_20260711_018_FE_ux_rules_material_and_remediation.md Guard spec: src/Web/StellaOps.Web/src/app/core/design/ux-rules-guard.spec.ts

Distilled from the 2026-07-11 36-finding UX audit (families A–F) and ratified by the approved before/after design review. Every rule below is binding for any agent or human touching console UI. Four rules (R1, R11, R12, R14) are machine-enforced by the guard spec; the rest are enforced by review and by the UXR-5/6 population sweeps. Format per rule: Rule (binding statement) · Why (human-perception grounding) · Material (reusable token/primitive that implements it) · Guard (machine enforcement, if any).

All contrast ratios below are computed WCAG relative-luminance ratios (alpha-blended over the actual theme background), not eyeballed.


UXR-R1 — Never white text on a brand or status token fill

UXR-R2 — Focus must be visible in both themes

UXR-R3 — Dark-theme warning is the orange ramp + an icon; gold is reserved

UXR-R4 — Type floor: base 14px; readable floor 12px; below 12px is ornament only

UXR-R5 — Interactive targets ≥ 32px

UXR-R6 — Clickable non-button elements get the keyboard triad via stellaClickableRow

UXR-R7 — Enumerable match keys get stella-suggest (or a picker) — never bare free text

UXR-R8 — One filled primary per view; table rows never carry filled primaries

UXR-R9 — No affordance without a handler

UXR-R10 — Hover-intent consistency (150ms enter / 300ms leave)

UXR-R11 — Angular Material is FROZEN; the house idiom is canonical


UXR-R12 — Route changes move focus to the main landmark without stealing query-filter focus

UXR-R13 — Async feedback announces once, at the urgency it actually has

UXR-R14 — The routed shell owns the single main landmark

UXR-R15 — Page-level tabs use the canonical tabs and preserve addressable state

UXR-R16 — Routed authoring drafts cannot disappear on navigation

UXR-R17 — Theme-sensitive readable and status styling uses semantic tokens

UXR-R18 — Search and filter inputs have a persistent accessible name

UXR-R19 — Popup menus expose one complete relationship and keyboard contract

UXR-R20 — Controls live at the scope they actually affect

UXR-R21 — A loading layer yields as soon as its destination is usable

UXR-R22 — One primary accent; every other color is a semantic role

UXR-R23 — Every page opens with the canonical purpose-header

UXR-R24 — One Refresh rendering

The guard spec — how enforcement works

src/Web/StellaOps.Web/src/app/core/design/ux-rules-guard.spec.ts scans all of src/app (.ts/.scss/.html) and runs six checks (~8s; run it with npx vitest run --config vitest.codex.config.ts src/app/core/design/ux-rules-guard.spec.ts):

  1. New white-on-token violation (R1) — fails with the offending file + the rule pointer.
  2. New Material usage (R11) — same.
  3. White-on-token baseline honesty — a baseline entry that no longer violates (fixed or deleted) FAILS the spec until the entry is removed, so the allowlist can only shrink.
  4. Material baseline honesty — same for the Material freeze list.
  5. Route-focus coverage (R12) — every routed shell template keeps stellaRouteFocus on its main landmark.
  6. Single-main coverage (R14) — feature templates cannot introduce a nested/duplicate main landmark inside the routed shell.

Protocol when you fix or delete a baselined file: remove its line from WHITE_ON_TOKEN_BASELINE / MATERIAL_BASELINE in the same change. Never add an entry.