10 KiB
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
- Primary
#024ad8(Electric Blue) reste. Pas de violet, pas de spectrum ramp. - Brutalisme reste :
rounded-nonesur containers (sauf status pills + tab count pills + avatars), zerotransition-*, zeroshadow-*, hairline 1px borders. - Pas de modif
tailwind.config.*sauf si une nouvelle classe utility est strictement nécessaire (justifier dans le PR). - DESIGN.md amendments additives uniquement — pas de modif des tokens existants.
- 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. Readswindow.location.hash, falls back to default ID, listens tohashchange, updates URL without reload. TypeScript pur, zéro style.frontend/src/components/Tabs.tsx(NEW) — composant<Tabs items={[{id, label, count?}]} activeId onChange />. 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')
- Tab 1 "Schedule" (id
DESIGN.md— nouvelle subsection### Navigation › Sub-page tabs(additive)
Brutalism check :
- Tab :
border-b-2only, pas de bg, pas de rounded, pas de transition - Pill :
rounded-pillautorisé (exception status pill DESIGN.md L125) - Hover :
hover:text-inkinstantané
Livrable 2 — AlertBanner component + 4 recipes
Files :
frontend/src/components/AlertBanner.tsx(NEW) —<AlertBanner variant="error|warn|success|info" title? children />. Brutalist : border-l-4 strip semantic, fillbg-paper, borderborder-hairlinepartout sauf le strip gauche,rounded-none. Icon Lucide à gauche (AlertCircleerror,AlertTrianglewarn,CheckCirclesuccess,Infoinfo) à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) →<AlertBanner variant="success">/<AlertBanner variant="warn">.DESIGN.md— extend### Toast Notificationsavec 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) —<BackLink to="/engagements">Back to engagements</BackLink>. Renders← {children}(ArrowLeft Lucide size=14, gap-xxs). Style :text-graphite hover:text-primary caption-mdinstantané.- 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 tableexistante pour passer à 32px row,caption-mdtext,py-xxs px-xscells, dividerborder-hairline. - Soit : ajouter
.table-compactpuis 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.
- Soit : modifier la recipe
- Apply sur :
frontend/src/pages/EngagementsListPage.tsxfrontend/src/pages/TemplatesListPage.tsxfrontend/src/pages/UsersAdminPage.tsxfrontend/src/components/SimulationList.tsx(utilisé dans EngagementDetailPage)- Toute autre table dans le code (grep
<tabledansfrontend/src/)
DESIGN.md—### Data Tablessection : modifier la ligne "Row min-height 44px" → "Row min-height 32px (compact density, all surfaces)". Documenter le tradeoff WCAG (touch target dérogé en faveur de la densité opérateur — c'est un BAS tool, pas un site grand public).
ATTENTION : WCAG SC 2.5.5 (Target Size: Minimum) recommande 44×44. Tu déroges. Garder les boutons d'action des rows ≥ 24px d'icône avec padding pour préserver le hit area minimal. Tester en clavier + lecteur d'écran nicht regressé.
Livrable 5 — DESIGN.md amendments + CHANGELOG
Files :
DESIGN.md:### Navigation: nouvelle subsection "Sub-page tabs" (livrable 1)### Toast Notifications: nouvelle subsection "Inline Banners" (livrable 2)### Data Tables: ligne row min-height passe à 32px + paragraphe rationale (livrable 4)
CHANGELOG.md: entrée Sprint 11 listant chaque primitive + ses tokens DESIGN.md.
Sequencing
- frontend-builder : livrables 1 + 2 + 3 + 4 + 5 en cascade. Une seule passe. Commits atomiques :
(collapse OK si plus efficace ; ~5-7 commits attendus)
feat(frontend): add Tabs primitive + useHashTab hook feat(frontend): refactor EngagementDetailPage to 3-tab layout feat(frontend): add AlertBanner component + 4 semantic variants refactor(frontend): migrate SimulationFormPage banners to AlertBanner feat(frontend): add BackLink helper + dedup 3 hand-rolled instances feat(frontend): compact table density (32px row) global docs(design): add tabs / inline banners / compact-table-density to DESIGN.md - design-reviewer : visual + token compliance check. Focus :
- Underline tabs : pas de transition, pas de bg fill, border-b instantané
- Count pill :
rounded-pillest dans l'exception DESIGN.md, OK - AlertBanner :
border-l-4semantic OK, pas de shadow - BackLink : caption + icon align baseline
- 32px tables : densité lisible ? Hit area boutons d'action OK ?
- code-reviewer : sprint diff. Focus :
- Aucune transition / shadow / rounded leak
- useHashTab :
hashchangelistener cleanup dans useEffect return - Tabs : prop API stable, fallback default ID si hash invalide
- AlertBanner : ARIA
role="alert"ourole="status"selon variant - BackLink :
toeststringou objet React Router ? Choisir un seul
- spec-reviewer :
SPEC.mdn'a pas vocation à mentionner ces primitives, skip ce sprint sauf si tu détectes une feature côté backend qui aurait dérivé.
Contraintes tests
vitest --runbaseline 212/212 doit tenir.- Ajouter 4 specs (1 par new component) :
Tabs.test.tsx— active state, hash sync, count pill renderAlertBanner.test.tsx— 4 variants rendent les bonnes classesBackLink.test.tsx— render + click navigationuseHashTab.test.tsx— hash read/write
tsc --noEmit+eslint --max-warnings=0clean.- Brutalism unit assertions : pour chaque new component, un test qui vérifie
expect(el).not.toHaveClass('rounded-md')(sauf pill counter) +.not.toHaveClass(/transition-/)+.not.toHaveClass(/shadow-/).
Out of scope (explicite)
- ❌ Side nav / drawer / breadcrumb component — top nav + BackLink suffit
- ❌ Global engagement selector top-bar (Spectrum pattern rejeté)
- ❌ Editor dynamic-column grid — pas d'éditeur dans Mimic
- ❌ Dark mode rework
- ❌ Toast queue system (les toasts actuels via
useToastrestent) - ❌ PDF/document deliverable
- ❌ Color palette changes
- ❌ Tokens spacing/typo dans tailwind.config
Definition of Done
- ✅ 5 primitives livrées + EngagementDetailPage refactorée en tabs + SimulationFormPage banners migrés + BackLink dédup × 3
- ✅ vitest 212+/212+ (4 new specs ajoutées), tsc + lint clean
- ✅ pytest 469/469 intact (zéro backend touché)
- ✅ Toutes tables passent à 32px row density (validation visuelle)
- ✅ Design-reviewer APPROVED (brutalism check sur les 4 new recipes)
- ✅ Code-reviewer APPROVED
- ✅ DESIGN.md amendé (3 subsections additives)
- ✅ CHANGELOG.md entrée sprint 11
- ✅ PR ouverte sur Gitea avec body