# DESIGN-SYSTEM — DALSEEN / SAAED Frontend

> **Verified accurate:** 2026-05-02 — color tokens (all six brand hexes confirmed in `front/common/tokens.js`), light/dark structure, system tinting via `window.systemColor`, and Saaed-AI inversion all hold. **One drift:** the icon library is now **73 glyphs** (not ~50) — the count grew during batch screen work. INTEGRATION-NOTES has the current 73-glyph count and full catalogue.
> **Status:** active source-of-truth doc; canonical token reference for the design system.

**Audience:** anyone reskinning, theming, or building net-new screens.
**Source of truth:** `front/common/tokens.js`, `front/common/strings.js`, `front/common/roles.js`, the inline `<style>` block in `index.html`.
**Goal of this doc:** a complete reference of design tokens, typography, iconography, layout primitives, motion, locale handling, and component conventions — so a developer can build a new screen that visually belongs without re-reading the entire codebase.

---

## 0 · TL;DR

- **Brand:** DALSEEN — deep navy (`#0B2D46`) anchored, DALSEEN blue (`#279DF2`) accent.
- **Per-system accents:** Retail green `#12AF69`, Pay gold `#C49D54`, Dine orange `#EE7135`, HR purple `#5B3D8F`. The active system tints the sidebar rail, the active-row marker, and section headers.
- **Two themes:** `light` and `dark`. Pure light mode by default; dark is fully token-equivalent — every screen reads `window.DALSEEN_TOKENS[theme]`.
- **Two locales:** `en` (LTR, Inter Tight body / Fraunces display) and `ar` (RTL, IBM Plex Sans Arabic body / Amiri display). Direction flips set on `<html dir>` at the root.
- **No CSS framework, no Tailwind, no styled-components.** Inline `style={{ … }}` everywhere. Tokens are read directly from `T = window.DALSEEN_TOKENS[theme]` at the top of every component.
- **Spacing:** ad-hoc 4-px-grid (4 / 6 / 8 / 10 / 12 / 14 / 18 / 24 / 32 / 48). Radius scale: `2 · 3 · 6 · 8 · 10 · 12 · 16` px.
- **Density:** `comfortable` (default) and `compact` exposed via the App tweak; honoured per-screen where relevant (Inventory, Tables, KDS).
- **Iconography:** single `<Icon name=… size=…>` component, **73 named glyphs** (verified 2026-05-02 — `front/common/icons.compiled.js` `IconPaths` map). No third-party icon set. Full catalogue at the bottom of `INTEGRATION-NOTES.md`.
- **Motion:** four named keyframes (`dalseenPulse`, `dalseenShake`, `dalseenFade`, `dalseenFadeUp`, `dalseenFadeOnly`). No motion library.
- **Currency:** `window.fmtSAR(n, lang)` formats SAR — `SAR 1,234` in EN, `١٬٢٣٤ ر.س` in AR.

---

## 1 · Color tokens

Defined in `front/common/tokens.js` as `window.DALSEEN_TOKENS = { light: {...}, dark: {...} }`. **Both themes export the same key set** — every component can swap themes without conditional code.

### 1.1 Light theme (default)

| Token | Hex | Role |
|---|---|---|
| `bg` | `#F5F5F5` | App background (the layer behind cards) |
| `surface` | `#FFFFFF` | Card / panel / sheet surface |
| `surface2` | `#EBEBEB` | Subtle alt-surface (form rows, inset panels, sidebar segmented control track) |
| `surface3` | `#F5F6FB` | Sidebar background, large tinted regions |
| `ink` | `#0B2D46` | **Primary text** (also brand deep navy) |
| `ink2` | `#175D8F` | Secondary text — sidebar inactive rows, hover-able labels |
| `muted` | `#6C7A87` | Tertiary text — captions, meta, helper copy |
| `border` | `#E2E6EA` | Default 1-px hairline |
| `border2` | `#CCCCCC` | Heavier border (form field outlines, focus emphasis) |
| `accent` | `#279DF2` | **Brand accent** (DALSEEN blue) — primary buttons, links, focus ring |
| `accent2` | `#1F7EC2` | Accent hover / pressed |
| `accent3` | `#50A5DC` | Accent-on-accent / decorative |
| `accentInk` | `#FFFFFF` | Text on `accent` fills |
| `retail` | `#12AF69` | Retail system tint (green) |
| `pay` | `#C49D54` | Pay system tint (gold) |
| `dine` | `#EE7135` | Dine system tint (orange) |
| `hr` | `#5B3D8F` | HR system tint (purple) |
| `gold` | `#C49D54` | Owner-system tint, premium accents |
| `danger` | `#D23A2B` | Error / destructive |
| `warn` | `#E0A030` | Warning / pending |
| `ok` | `#12AF69` | Success (shares retail green by design) |
| `aiBg` | `#0B2D46` | Saaed-AI panel background (always dark, even in light theme) |
| `aiInk` | `#EBEBEB` | Saaed-AI text |
| `aiAccent` | `#C49D54` | Saaed-AI accent (gold-on-navy) |

### 1.2 Dark theme

Same keys, navy-shifted palette. Surface stack is `bg #0A1F30 → surface #0F2A3F → surface2 #13334D → surface3 #0D3044`. Ink inverts to `#EEF3F7` / `#B6C7D4`. Accent shifts brighter (`#50A5DC`) for adequate contrast on dark surfaces. System tints all desaturate slightly: retail `#2FCB82`, pay `#D6B070`, dine `#F2864D`, hr `#8B6FBC`. Saaed-AI panel stays navy-on-navy (`aiBg #08161E`, `aiInk #EEF3F7`, `aiAccent #D6B070`).

### 1.3 Usage rules

1. **Never hard-code a hex in a component.** Always `const T = window.DALSEEN_TOKENS[theme];` and reference `T.surface`, `T.ink`, etc.
2. **System tinting** is centralised: `window.systemColor(T, sys)` returns the right token (`T.retail` / `T.pay` / `T.dine` / `T.hr` / `T.gold` for owner / `T.accent` as fallback). Use this for the active-nav indicator strip, section headers, badges.
3. **Saaed AI** is the one component that's intentionally inverted in light mode — use the `aiBg` / `aiInk` / `aiAccent` tokens, not `ink`/`accent`. This is a **deliberate brand decision** (gold-on-navy AI surface, regardless of theme), **not** a token bug — preserve it through any reskin.
4. **Contrast pairs** that ship guaranteed-AA: `ink on surface`, `ink2 on surface`, `muted on surface` (small text only at 14px+), `accentInk on accent`. Anything else, double-check before shipping.
5. **Status colours**: `ok / warn / danger` are the only colours allowed for state semantics. Don't reach for `accent` or system tints to mean "success".

---

## 2 · Typography

Imported from Google Fonts in `index.html`:
`Fraunces (display, ital + opsz)`, `Inter Tight (400/450/500/600)`, `IBM Plex Sans Arabic (400/500/600)`, `Amiri (400/700)`.

### 2.1 Family stack

| Locale | Body | Display (h1/h2/h3) |
|---|---|---|
| `en` (LTR) | `"Inter Tight", -apple-system, sans-serif` with `font-feature-settings: "ss01","cv11"` | `"Fraunces", serif` |
| `ar` (RTL) | `"IBM Plex Sans Arabic", "Inter Tight", sans-serif` | `"Amiri", "IBM Plex Sans Arabic", serif` (forced via `!important`) |

Switching is automatic — the `<html dir>` and `<html lang>` swap (set by `App` whenever tweaks change) selects via the CSS in `index.html`.

### 2.2 Type scale (observed)

The codebase doesn't ship a formal type scale; sizes are inline. The **conventions in use** that new code should follow:

| Rank | px | Weight | Where it's used |
|---|---|---|---|
| Display | 22–28 | 600 (Fraunces) | Screen titles, modal headers |
| Title | 17–18 | 600 | Card headers, section titles |
| Body | 13–14 | 400–500 | Default text |
| Small | 11–12 | 400 | Meta, captions, table cells |
| Micro | 9–10 | 600, `text-transform: uppercase`, `letter-spacing: 0.5–1.3` | Eyebrows, badges, label tags |
| Numeric (KPIs) | 26–48 | 600, `font-variant-numeric: tabular-nums` | Dashboard tiles, total lines |

### 2.3 Letter-spacing & feature settings

- Body Latin: `font-feature-settings: "ss01","cv11"` (Inter Tight stylistic alternates) — set globally in `index.html`.
- Brand wordmark "DALSEEN" in sidebar: Fraunces 600, `letter-spacing: -0.3` (Latin only).
- Eyebrow / label-tag micro text: `letter-spacing: 1.3, text-transform: uppercase`.
- Numerics in tables / KPIs: `tabular-nums` (manually applied; not global).

### 2.4 Numerals & RTL handling

- Numbers, currency, codes, IDs, and `<kbd>` are forced LTR even inside Arabic context: `[dir="rtl"] [data-numeric], [dir="rtl"] .dalseen-numeric, [dir="rtl"] code, [dir="rtl"] kbd { direction: ltr; unicode-bidi: isolate; }` (rule lives in `index.html`).
- Phone, email, and any node tagged `data-ltr` are also forced LTR with isolated bidi.
- `window.fmtNum(n, lang)` and `window.fmtSAR(n, lang)` use locale-aware `toLocaleString('ar-SA' | 'en-US')` and append `ر.س` (AR) or prepend `SAR ` (EN).

---

## 3 · Layout primitives

### 3.1 Spacing scale

Ad-hoc but consistent 4-px-grid:
**4 · 6 · 8 · 10 · 12 · 14 · 18 · 24 · 32 · 48** px.
Padding inside cards is typically `12 · 14 · 18`. Page margins are `18–24`. Section gaps are `24 · 32`.

### 3.2 Radius scale

| Use | Radius |
|---|---|
| Tag pills, chips | 2–3 |
| Form controls, small buttons, inset rows | 6–8 |
| Cards, panels, segmented controls, sheets | 10–12 |
| Hero tiles, large modals, full-screen sheets | 16 |

### 3.3 Borders

- Default hairline: `1px solid ${T.border}` (`#E2E6EA` light).
- Heavier outlines: `1px solid ${T.border2}` (`#CCCCCC` light) — used for form-input outlines on focus, table-cell separators in dense tables.
- Sidebar separator: `borderInlineEnd: 1px solid ${T.border}` (logical property — flips automatically in RTL).

### 3.4 Shadows

Sparingly used. Two patterns in the codebase:

- Active sidebar/system tab: `boxShadow: '0 1px 4px rgba(11,45,70,0.08)'`
- Lifted cards / popovers / modals: `boxShadow: '0 8px 24px rgba(11,45,70,0.12)'` (approximate — varies per component)

No formal elevation scale; if you need one, `0/0/0/0.04 → 0/2/8/0.08 → 0/8/24/0.12 → 0/16/48/0.18` reads naturally with this palette.

### 3.5 Density

The `density` tweak (`comfortable` | `compact`) is set on the App and read by screens that have data tables (Inventory Hub, HR Directory, Accounting Journal, Platform Tenants). Compact reduces row padding from `10/12` → `6/8` and tightens gaps from `12` → `8`. Most screens currently honour the App-level setting opportunistically; it's not enforced systemically.

---

## 4 · Iconography

`window.Icon` (`front/common/icons.compiled.js`) is a single React component:

```jsx
<Icon name="bag" size={16} style={{ color: T.accent }} />
```

The full glyph set (referenced from `DALSEEN_NAV` and screens):

`bag · card · fork · grid · branch · spark · check · clipboard · tag · coin · heart · chart · shield · warn · ledger · receipt · sync · box · truck · users · phone · device · bank · chair · flame · menu · beaker · tablet · calendar · bike · clock · beach · folder · cog · link · star · book · flag · book`

**Conventions:**

- Colour the icon by passing `style={{ color: '...' }}`; `currentColor` propagates inside the SVG.
- For directional / arrow icons in RTL, add `className="dalseen-icon-mirror"` — the global rule `html[dir="rtl"] .dalseen-icon-mirror { transform: scaleX(-1); }` flips them automatically. Brand & semantic icons (logos, the bag, the receipt) **must not** be mirrored.
- Icons in nav are 15 px, in cards 16–18 px, in KPI tiles 20–24 px.
- There is no outline/filled variant system — every icon ships in one weight.

---

## 5 · Component conventions

### 5.1 Buttons

No `<Button>` primitive — every button is hand-styled inline. The patterns in use:

- **Primary**: `background: T.accent`, `color: T.accentInk`, `padding: 10px 18px`, `borderRadius: 8`, `border: none`, `fontWeight: 600`, `cursor: pointer`. On press, swap to `T.accent2`.
- **Secondary / ghost**: `background: 'transparent'`, `color: T.ink`, `border: 1px solid ${T.border}`, otherwise the same dimensions.
- **Destructive**: `background: T.danger`, `color: '#fff'`.
- **Tab / segment**: `background: on ? T.surface : 'transparent'`, `color: on ? T.ink : T.muted`, with the active row marked by a 3-px coloured strip on the inline-start side using `position: absolute; insetInlineStart: 0; …; background: <systemColor>`.
- **Icon-only**: `width/height: 32`, `borderRadius: 8`, `background: T.surface2` on hover.

If a developer is starting fresh, _please_ extract these into a `<Btn variant="primary|secondary|ghost|danger" />` — but follow the visual contract above.

### 5.2 Cards & panels

Standard card: `background: T.surface`, `border: 1px solid ${T.border}`, `borderRadius: 12`, `padding: 18`. Headers inside cards use 17 px / 600 / `T.ink`, optional eyebrow above in 10 px / 600 / `T.muted` / uppercase / `letter-spacing: 1.3`.

### 5.3 Inputs

- `background: T.surface`, `border: 1px solid ${T.border2}`, `borderRadius: 8`, `padding: 10px 12px`, `fontSize: 14`, `fontFamily: 'inherit'` (so Arabic inputs pick up Plex Arabic).
- Placeholder: `color: inherit; opacity: 0.6`.
- In RTL, text inputs keep `text-align: start` (set globally in `index.html`); phone / email / `[data-ltr]` force LTR + bidi-isolate.
- No floating-label pattern; labels sit above the input in 12 px / 500 / `T.muted`.

### 5.4 Tables

- Wrapper card: same as §5.2.
- Header row: `background: T.surface2`, `color: T.muted`, 11 px, uppercase, `letter-spacing: 0.6`, `padding: 10px 12px`.
- Body row: `padding: 12px`, hairline `border-top: 1px solid ${T.border}`.
- Numeric columns get `data-align="right"` — the global rule in `index.html` flips `right` ↔ `left` automatically in RTL so money still aligns to the inline-end edge.
- Hover: `background: T.surface3`.

### 5.5 Badges & pills

Two flavours:

- **Filled status**: `padding: 2px 6px`, `borderRadius: 3`, `fontSize: 9`, `fontWeight: 600`, `text-transform: uppercase`, `letter-spacing: 0.5`. Background = the system colour (`T.retail`/`T.pay`/`T.dine`) or status colour (`T.ok`/`T.warn`/`T.danger`); text = `#fff`. This is the badge format used throughout `DALSEEN_NAV` (e.g. the "Live", "New", "Beta", "WPS", "ESS", "Nitaqat" pills).
- **Tonal chip**: `background: T.surface2`, `color: T.ink2`, `borderRadius: 6`, `padding: 4px 8px`, `fontSize: 12`. Used for filters, tags, soft selectors.

### 5.6 Modals & sheets

`window.Modal` (`front/common/modals.compiled.js`) provides the standard modal. Conventions:

- Backdrop: `rgba(11,45,70,0.45)`, `backdrop-filter: blur(2px)`.
- Container: `background: T.surface`, `borderRadius: 16`, `boxShadow: 0 16px 48px rgba(11,45,70,0.18)`, max-width 560 (small) / 840 (medium) / 1080 (large).
- Header: 18 px / 600 / `T.ink`, close button top-end (logical property, RTL-safe).
- Footer: actions right-aligned (LTR) / left-aligned (RTL); primary on the trailing edge.
- `dalseenFadeUp` enter animation.

### 5.7 Sidebar

The full sidebar is `front/common/shell.compiled.js`. Notable:

- Fixed 260 px wide, `background: T.surface3`.
- Brand block top: 34 × 34 navy tile with serif "D" and a 6 × 6 accent dot in the bottom-end corner.
- Below the brand, when not super-admin: a 3-up segmented control of system pills (Retail / Pay / Dine), each tinted with its system colour when active.
- Nav rows: 13 px, icon 15 px, active-row strip 3 px wide on the inline-start edge in the system colour, label weight bumps from 400 → 500 when active.
- Badges (`Live`, `New`, `Beta`, `WPS`, `ESS`, `Nitaqat`, numeric counts) render inline on the right (or inline-end in RTL).

### 5.8 Topbar

Generated by `window.Topbar`. Title shows the active screen's bilingual label; right side has search, the "Saaed AI" toggle, language toggle (en/ar), theme toggle (light/dark), and the persona/account chip.

### 5.9 Dashboard tiles

`window.Dashboard` (and `DashboardSplash` first-load animation). KPI tile pattern:

- Card surface, 18 px padding.
- Eyebrow (10 px, muted, uppercase) — e.g. "REVENUE TODAY".
- Big number: 26–32 px / 600 / tabular-nums / `T.ink`.
- Delta below: 12 px / `T.ok` (positive) or `T.danger` (negative), prefixed `+` / `−`.

---

## 6 · Motion & animation

Defined inline in `index.html`:

| Keyframe | Use |
|---|---|
| `dalseenPulse` | Live indicators, recording-state dots (`scale 0.9 → 1.5`, `opacity 0.7 → 0`) |
| `dalseenShake` | Validation error nudge on form fields (±6 px x-translate, 4 oscillations) |
| `dalseenFade` | Default mount fade (`opacity 0 → 1`, `translateY 6 → 0`) |
| `dalseenFadeUp` | Modal / sheet entry (`opacity 0 → 1`, `translateY 8 → 0`) |
| `dalseenFadeOnly` | Screen swap — opacity-only (does **not** create a containing block, so `position: fixed` modals nested inside still anchor to the viewport) |

The screen-swap container has class `dalseen-screen` and uses `dalseenFadeOnly` for exactly that reason — read the comment in `index.html` before changing it.

No third-party animation library is loaded. If a screen needs richer motion, hand-roll it with CSS or `requestAnimationFrame`.

---

## 7 · Locale & RTL

### 7.1 Strings table

`window.STRINGS = { en: {...}, ar: {...} }` in `front/common/strings.js` — currently ~30 keys covering shell chrome, POS basics, role labels, persona, theme/language toggle. Access pattern:

```js
const t = window.useT(lang);  // returns k => STRINGS[lang][k] ?? k
t('total');  // → 'Total' or 'الإجمالي'
```

Screen-specific copy is **inline** as `lang === 'ar' ? 'النص' : 'Text'` ternaries — there is no per-screen string table. When migrating, expect every screen to need string extraction.

### 7.2 Bilingual data

Demo data in `DALSEEN_DATA` (and tenant fixtures) ships every label as a paired `{ en: '...', ar: '...' }` object or `name_en` / `name_ar` columns. The convention in screens:

```js
const label = item[lang] ?? item.en;        // for { en, ar }
const label = item[`name_${lang}`] ?? item.name_en;  // for split columns
```

### 7.3 RTL plumbing

Set in `App` whenever the lang tweak changes:

```js
document.documentElement.setAttribute('dir', tweaks.lang === 'ar' ? 'rtl' : 'ltr');
document.documentElement.setAttribute('lang', tweaks.lang);
```

This cascades through CSS rules in `index.html` that:

- Swap font families (Plex Arabic for body, Amiri for display)
- Flip `text-align: right ↔ left` for cells tagged `data-align`
- Mirror icons tagged `.dalseen-icon-mirror`
- Force LTR + bidi-isolate on numerics, codes, phones, emails, and `[data-ltr]` nodes

**Component authoring rule:** prefer **logical properties** over directional ones — `margin-inline-start` not `margin-left`, `padding-inline-end` not `padding-right`, `inset-inline-start` not `left`. The shell sidebar / sidebar rail strip / nav row text-align all use logical properties. Most components in this codebase do; new ones must.

---

## 8 · Accessibility floor

The codebase makes a baseline effort but is not WCAG-audited. What's already true:

- Foreground/background contrast for `ink on surface`, `ink2 on surface`, `accentInk on accent` is AA at 14 px+; `muted on surface` only at 14 px+ (don't use it for 12 px chrome).
- Every nav row, button, input, and modal close action is keyboard-focusable (default `<button>`, `<input>` semantics).
- RTL mirrors the entire layout, not just text direction.
- No skip-link, no `aria-current` on the active nav row, no `aria-label` on icon-only buttons. **All three are migration tickets.**

---

## 9 · Roles & permissions (UX impact)

Defined in `front/common/roles.js`. There are **9 roles** (`superadmin · owner · manager · cashier · accountant · auditor · waiter · chef · warehouse`). Each role has:

- A colour swatch (`col`) used in role chips and the persona pill.
- A scope (`platform` | `tenant` | `branch`) that drives data-scoping in the (eventual) backend.
- A permission set in `DALSEEN_PERMS[role]` — either `{ all: true, hidePlatform: true }` (owner) or `{ ids: [...], readonly?: true }` (everyone else). `auditor` is `readonly`.
- A role policy in `DALSEEN_ROLE_POLICIES[role]` controlling biometric-on-sign-in, biometric-on-shift-start, and the high-value refund threshold above which biometric is forced.

UX rules driven by this:

- The sidebar filters its rows through `window.canSee(role, navId)` — no nav item appears if the role can't see it.
- Owner role's "hidePlatform" stripe hides every `platform.*` item even though `all: true`.
- Auditor and any role with `readonly` get a quiet "Read-only" badge in the topbar and disabled mutation buttons in screens that respect it.
- The biometric prompt (`window.BiometricPrompt`) gates sign-in for roles with `biometricRequired: true` and gates start-of-shift for roles with `shiftBiometric: true`.

---

## 10 · Number, money, date formatting

| Helper | Defined in | Behaviour |
|---|---|---|
| `window.fmtSAR(n, lang)` | `common/strings.js` | Rounds, locale-format thousands separator (`ar-SA` or `en-US`), then either prepends `SAR ` (EN) or appends ` ر.س` (AR) |
| `window.fmtNum(n, lang)` | `common/strings.js` | Locale-format thousands separator only |
| `API.Money.sar(halalas)` | `api/api-foundation.js` | Returns `{ amount_minor: int, currency_code: 'SAR' }` — the canonical money envelope used by every `/sales`, `/purchasing`, `/accounting` payload. Halalas = SAR × 100. |
| Dates | (no helper) | Ad-hoc `toLocaleDateString('ar-SA' | 'en-US')` per screen. **Migration ticket:** centralise into `fmtDate(d, lang)` |

**Currency rule:** never multiply or compare with floats. Do all money math in `amount_minor` (integer halalas) and only divide by 100 at the render edge.

---

## 11 · Reskin / re-theme checklist

If you need to white-label this for another brand:

1. **Edit `front/common/tokens.js`.** Swap the colour values in `light` and `dark`. Keep the key names — every screen reads them.
2. **Edit the brand wordmark** in `front/common/shell.compiled.js` (and the `.jsx` source). The tile says "D"; the label says "DALSEEN" / "دال سين"; the eyebrow says "Business OS" / "نظام تشغيل الأعمال".
3. **Edit the favicon** (currently none — `index.html` doesn't ship one).
4. **Replace the system list** in `DALSEEN_SYSTEMS` and the labels in `DALSEEN_KIND_LABEL` if the product taxonomy differs.
5. **Replace fonts** — three font-family strings in `index.html`'s `<style>` block, and the Google Fonts URL.
6. **Demo data** in `DALSEEN_DATA`, `DALSEEN_RETAIL`, `DALSEEN_PLATFORM`, `DALSEEN_OWNER` should all be either replaced or stripped before production.

---

## 12 · Open design debt / migration tickets

The integration team should expect to do these as part of moving from prototype to production:

- **Extract a `<Btn>` / `<Card>` / `<Input>` / `<Badge>` component library.** Currently every screen re-implements these inline — visual drift over time is a guarantee otherwise.
- **Centralise the type scale** as named tokens (`TYPE.display / .title / .body / .small / .micro / .numeric`) instead of inline pixel values.
- **Centralise spacing tokens** (`SPACE.xs/sm/md/lg/xl/2xl`) so density toggling can be implemented once instead of per-screen.
- **Add a `<Stack>` / `<Inline>` layout primitive** with `gap` — replaces the dozens of ad-hoc `display:flex; gap: N` snippets and makes drag-reorder DOM edits cleaner.
- **Add a `fmtDate(d, lang, opts)` helper** in `common/strings.js` and migrate inline `toLocaleDateString` calls.
- **Per-screen string extraction.** The inline `lang === 'ar' ? 'النص' : 'Text'` ternaries are a translator's nightmare; move into a key-value table per screen.
- **A11y floor:** `aria-current="page"` on the active sidebar row, `aria-label` on icon-only buttons, a skip-link to `<main>`, focus-visible outlines on every interactive surface.
- **Density token wired to the App-level tweak**, applied via a CSS custom property on `<html>` so screens don't each have to special-case it.
- **Replace `_meta` blob audit history** (in `api-namespace.js` for products) with a real audit-log resource the moment the backend is wired.
