# Sprint 11 — Spectrum UX port : 4 primitives + compact density global
**Base** : `origin/main` (PR #12 mergée — sprint 8 + 9 + 10 en).
**Branch** : `sprint/11-spectrum-ux`.
**Scope** : frontend-only sprint, 5 livrables coordonnés. Pas de backend, pas de schéma, pas de nouvelle API. Pas de modif tokens couleurs/spacing/font dans tailwind.config.
---
## Contexte
Analyse Spectrum (`/home/user/Documents/01_Projects/spectrum`) faite via workflow. Résultat : Spectrum n'a **pas de design system** (Tailwind raw + violet ramp + recipes inlinées 30× partout). Mimic est structurellement meilleur. On ne porte pas la philosophie Spectrum — on extrait **5 patterns isolés** qui survivent au brutalisme Mimic.
## Constraints absolues
1. **Primary `#024ad8` (Electric Blue) reste**. Pas de violet, pas de spectrum ramp.
2. **Brutalisme reste** : `rounded-none` sur containers (sauf status pills + tab count pills + avatars), zero `transition-*`, zero `shadow-*`, hairline 1px borders.
3. **Pas de modif `tailwind.config.*`** sauf si une nouvelle classe utility est strictement nécessaire (justifier dans le PR).
4. **DESIGN.md amendments additives uniquement** — pas de modif des tokens existants.
5. **Mono uniquement pour data** (IDs MITRE, dates ISO, commands, etc.) — rule sprint 7.
## Décisions binding (lockées par user)
- **Q1 → B** : Tabs primitive **+ consumer EngagementDetailPage** (3 tabs : Schedule / Description / Simulations au lieu du stack vertical actuel).
- **Q2 → B** : **Compact density GLOBAL** — toutes les tables passent à 32px row, pas opt-in. User accepte le tradeoff WCAG (touch target) sur Engagements list.
- **Q3 → A** : AlertBanner + refactor `SimulationFormPage` (Done banner + SOC-blocked banner).
---
## Livrables
### Livrable 1 — `Tabs` primitive + `useHashTab` hook + consumer
**Files** :
- `frontend/src/hooks/useHashTab.ts` (NEW) — ~30 LoC. Reads `window.location.hash`, falls back to default ID, listens to `hashchange`, updates URL without reload. TypeScript pur, zéro style.
- `frontend/src/components/Tabs.tsx` (NEW) — composant ``. Underline variant.
- `frontend/src/styles/index.css` — nouvelles recipes :
- `.tab-underline` — `text-graphite caption-bold cursor-pointer border-b-2 border-transparent hover:text-ink` (instantané, pas de transition)
- `.tab-underline-active` — `text-primary border-primary` (override)
- `.tab-count-pill` — `rounded-pill bg-cloud text-graphite text-[11px] px-xs py-0 font-mono`
- `.tab-count-pill-active` — `bg-primary-soft text-primary` (override)
- `frontend/src/pages/EngagementDetailPage.tsx` — refactor en 3 tabs :
- Tab 1 "Schedule" (id `schedule`, default) — dates + statut + bouton "Edit engagement"
- Tab 2 "Description" (id `description`) — texte description
- Tab 3 "Simulations" (id `simulations`) — la liste actuelle de simulations
- Count pills sur tab 3 = nombre de simulations
- Tabs branchées sur `useHashTab('schedule')`
- `DESIGN.md` — nouvelle subsection `### Navigation › Sub-page tabs` (additive)
**Brutalism check** :
- Tab : `border-b-2` only, pas de bg, pas de rounded, pas de transition
- Pill : `rounded-pill` autorisé (exception status pill DESIGN.md L125)
- Hover : `hover:text-ink` instantané
### Livrable 2 — `AlertBanner` component + 4 recipes
**Files** :
- `frontend/src/components/AlertBanner.tsx` (NEW) — ``. Brutalist : border-l-4 strip semantic, fill `bg-paper`, border `border-hairline` partout sauf le strip gauche, `rounded-none`. Icon Lucide à gauche (`AlertCircle` error, `AlertTriangle` warn, `CheckCircle` success, `Info` info) à `size={16}`.
- `frontend/src/styles/index.css` — 4 nouvelles recipes :
- `.alert-error` — `bg-paper border border-hairline border-l-4 border-l-bloom-deep px-md py-sm flex items-start gap-sm`
- `.alert-warn` — same + `border-l-warn`
- `.alert-success` — same + `border-l-success`
- `.alert-info` — same + `border-l-primary`
- `frontend/src/pages/SimulationFormPage.tsx` — refactor 2 banners hand-rollés (~L300 Done banner + ~L310 SOC-blocked banner) → `` / ``.
- `DESIGN.md` — extend `### Toast Notifications` avec subsection `### Inline Banners` (additive, mêmes 4 variants).
**Note** : `bloom-deep`, `warn`, `success`, `primary` sont les tokens existants — pas de nouveau token couleur.
### Livrable 3 — `BackLink` component (dédup)
**Files** :
- `frontend/src/components/BackLink.tsx` (NEW) — `Back to engagements`. Renders `← {children}` (ArrowLeft Lucide size=14, gap-xxs). Style : `text-graphite hover:text-primary caption-md` instantané.
- Refactor consumers (3 instances hand-rollées) :
- `frontend/src/pages/EngagementDetailPage.tsx` (~L35)
- `frontend/src/pages/SimulationFormPage.tsx` (back-link en edit mode)
- `frontend/src/pages/TemplateFormPage.tsx` (back-link en edit mode)
### Livrable 4 — `.table-compact` recipe + apply GLOBAL
**Files** :
- `frontend/src/styles/index.css` — nouvelle recipe (mais appliquée comme default, pas opt-in cette sprint per user decision Q2-B) :
- Soit : modifier la recipe `.card-product table` existante pour passer à 32px row, `caption-md` text, `py-xxs px-xs` cells, divider `border-hairline`.
- Soit : ajouter `.table-compact` puis l'appliquer à TOUTES les list pages (Engagements, Templates, Users, etc.).
- **Choix builder** : pick the cleaner path. Préférer modifier le default si toutes les tables doivent suivre, sinon `.table-compact` + apply partout.
- Apply sur :
- `frontend/src/pages/EngagementsListPage.tsx`
- `frontend/src/pages/TemplatesListPage.tsx`
- `frontend/src/pages/UsersAdminPage.tsx`
- `frontend/src/components/SimulationList.tsx` (utilisé dans EngagementDetailPage)
- Toute autre table dans le code (grep `