feat(frontend): Spectrum UX port — Tabs + AlertBanner + BackLink + compact tables (sprint 11) #13

Merged
knacky merged 11 commits from sprint/11-spectrum-ux into main 2026-06-22 08:25:38 +00:00
Owner

Summary

  • Sprint 11 — Spectrum UX port : analyse du projet /home/user/Documents/01_Projects/spectrum faite via workflow (4 lecteurs parallèles + synthèse adversariale). Verdict : Spectrum n'a pas de design system (Tailwind raw + violet ramp, recipes inlinées 30× partout). Mimic est structurellement meilleur — on n'absorbe pas la philosophie Spectrum, on extrait 4 patterns isolés qui survivent au brutalisme.
  • Sortie : 4 nouveaux composants/hooks (Tabs, useHashTab, AlertBanner, BackLink), 1 page refactorée en 3 tabs (EngagementDetailPage), 1 changement de densité table global (32px row), 4 amendments DESIGN.md additives, CHANGELOG.md entrée.
  • Brutalisme strict : zéro transition-*, zéro shadow-*, rounded-none partout sauf status pills + .tab-count-pill (exception documentée). Hairline 1px borders only. Primary #024ad8 Electric Blue intouchée.
  • WCAG SC 2.5.5 tradeoff explicite : tables passent de 44px à 32px row min-height. Justifié dans DESIGN.md (Mimic est un BAS operator tool, densité > touch target compliance). Action buttons restent ≥ 24px icon + padding pour préserver le hit area.
  • Sécurité a11y : Tabs wired complet WAI-ARIA (role="tablist" + role="tab" + aria-selected + aria-controls + aria-labelledby + arrow-key nav). AlertBanner role="alert" (error/warn) vs role="status" (success/info) — aria-live correct par implication.
  • useHashTab stable F5 + non-polluant historique : history.replaceState (pas de pushState, pas d'anchor-jump scroll).

Test plan

  • Frontend : 236 / 236 vitest (baseline 212 + 24 new tests across 4 new specs + 2 new arrow-key + 1 new aria-controls assertion).
  • Typecheck : tsc --noEmit clean.
  • Lint : eslint --max-warnings=0 clean.
  • Backend : 469 / 469 pytest intact (zéro backend touché).
  • Reviews : code-review APPROVED (1 MINOR + 3 NIT, all patched), design-review APPROVED (2 MINOR + 1 NIT, all patched).

Comment tester en local

# Sprint 11 est purement frontend. Pas de migration, pas d'env var.
git checkout sprint/11-spectrum-ux
make build && make start
# http://127.0.0.1:5000

Scénarios :

  1. Tabs sur EngagementDetailPage — ouvre un engagement en lecture. 3 tabs : Schedule (par défaut), Description, Simulations. Count pill sur "Simulations" = nombre exact de simulations. Click un tab → URL devient …#description (sans push d'history entry — Back doit toujours mener à la liste d'engagements en 1 clic).
  2. Hash sync F5 — sur …#simulations, refresh la page → tab Simulations reste actif. Colle …#nonsense → tu vois rien actif (silent fallback), idéalement default fallback dans une future itération.
  3. Arrow-key nav — focus un tab, ArrowRight / ArrowLeft cyclent entre les 3 tabs avec wrap-around.
  4. Screen reader — chaque tab a role="tab" + aria-selected, le panel actif a role="tabpanel" + aria-labelledby pointant vers le tab actif.
  5. AlertBanner — SimulationFormPage — sur une simulation done, le bandeau "Simulation complétée…" est désormais un <AlertBanner variant="success">. Sur une simulation SOC-blocked en mode RT, le bandeau "Cette simulation est en cours de review SOC…" est un <AlertBanner variant="warn">. Plus de markup bg-fog/bg-cloud hand-rollé.
  6. BackLink — sur Engagement detail, Simulation edit, Template edit : tu vois ← Back to engagements / ← Back to {engagement name} / ← Back to templates. Tous les 3 sont des <BackLink> (caption-md text-graphite, hover-instant text-primary).
  7. Compact density — Engagements list, Templates list, Users admin, Simulations list (inside Engagement) : toutes les tables sont en density 32px row, headers en text-[12px] uppercase, dividers hairline. Action buttons (edit/delete) restent confortables à cliquer.

Fichiers livrés

Fichier Type Notes
frontend/src/hooks/useHashTab.ts NEW (~30 LoC) Hash-stable active tab, history.replaceState, cleanup listener
frontend/src/components/Tabs.tsx NEW <Tabs items activeId onChange> complet WAI-ARIA + arrow-key nav
frontend/src/components/AlertBanner.tsx NEW 4 variants, Lucide icons, ARIA correct par variant
frontend/src/components/BackLink.tsx NEW <Link> React Router + ArrowLeft Lucide size=14
frontend/src/styles/index.css MOD +4 tab recipes, +4 alert recipes, .table-compact recipe
frontend/src/pages/EngagementDetailPage.tsx MOD Refactorée en 3 tabs (TabId union typed) + BackLink
frontend/src/pages/SimulationFormPage.tsx MOD 2 banners hand-rollés → <AlertBanner> + back-link → <BackLink>
frontend/src/pages/TemplateFormPage.tsx MOD back-link → <BackLink>
frontend/src/pages/EngagementsListPage.tsx MOD .table-compact applied
frontend/src/pages/TemplatesListPage.tsx MOD .table-compact applied
frontend/src/pages/UsersAdminPage.tsx MOD .table-compact applied + commentaire intention sur reset-password row
frontend/src/components/SimulationList.tsx MOD .table-compact applied
frontend/tests/hooks/useHashTab.test.tsx NEW 4 tests (hash sync, fallback, cleanup)
frontend/tests/components/Tabs.test.tsx NEW + extended 9 tests (render, ARIA, count pill, arrow-key nav, brutalism)
frontend/tests/components/AlertBanner.test.tsx NEW 6 tests (4 variants, ARIA roles, brutalism)
frontend/tests/components/BackLink.test.tsx NEW 4 tests (render, click, brutalism, MemoryRouter integration)
DESIGN.md MOD +3 subsections (Navigation › Sub-page tabs, Toast › Inline Banners, Data Tables compact + WCAG rationale)
CHANGELOG.md MOD Sprint 11 entry

Décisions verrouillées

  • Q1 : Tabs primitive + EngagementDetailPage consumer (3 tabs). Décision user.
  • Q2 : Compact density GLOBAL (32px partout). Décision user, WCAG tradeoff acknowledged.
  • Q3 : AlertBanner + refactor SimulationFormPage banners. Décision user.

Patterns explicitement rejetés depuis Spectrum

  • Identité couleur violette spectrum-700 (contrainte hard : primary stays #024ad8)
  • shadow-md/lg/xl sur cards/modals (Mimic forbid)
  • rounded / rounded-lg / rounded-full sur chrome (Mimic = rounded-none partout sauf pills + avatars)
  • transition-* (Mimic = hover instantané)
  • Hover-lift effects (hover:-translate-y-0.5 hover:shadow-lg)
  • Global mission/document selector dans le top bar (Mimic = URL = source of truth)
  • Dark mode utility-pair verbeux (Mimic = CSS-vars-driven)
  • Spectrum "no primitive layer, re-inline everything" approach
  • Per-document theming via PDF CSS

Commits

89fb38b docs(sprint-11): plan spectrum UX port — 4 primitives + compact density global
bca39dc feat(frontend): add Tabs primitive + useHashTab hook
004d075 feat(frontend): refactor EngagementDetailPage to 3-tab layout
59eaa34 feat(frontend): add AlertBanner component + 4 semantic variants
9a8c5f5 feat(frontend): add BackLink helper + dedup 3 hand-rolled instances
1819089 feat(frontend): compact table density (32px row) global
5d34a5e test(frontend): add specs for Tabs, AlertBanner, BackLink, useHashTab
1324a0c docs(design): add tabs / inline banners / compact-table-density to DESIGN.md
790ced4 fix(frontend): design-review polish — table-compact height, header type-scale, tabs a11y
11ce3cf fix(frontend): code-review polish — replaceState, arrow-key nav, TabId, comment

🤖 Generated with Claude Code

## Summary - **Sprint 11 — Spectrum UX port** : analyse du projet `/home/user/Documents/01_Projects/spectrum` faite via workflow (4 lecteurs parallèles + synthèse adversariale). Verdict : Spectrum n'a pas de design system (Tailwind raw + violet ramp, recipes inlinées 30× partout). Mimic est structurellement meilleur — on n'absorbe pas la philosophie Spectrum, on extrait **4 patterns isolés** qui survivent au brutalisme. - **Sortie** : 4 nouveaux composants/hooks (`Tabs`, `useHashTab`, `AlertBanner`, `BackLink`), 1 page refactorée en 3 tabs (`EngagementDetailPage`), 1 changement de densité table global (32px row), 4 amendments DESIGN.md additives, CHANGELOG.md entrée. - **Brutalisme strict** : zéro `transition-*`, zéro `shadow-*`, `rounded-none` partout sauf status pills + `.tab-count-pill` (exception documentée). Hairline 1px borders only. Primary `#024ad8` Electric Blue intouchée. - **WCAG SC 2.5.5 tradeoff explicite** : tables passent de 44px à 32px row min-height. Justifié dans DESIGN.md (Mimic est un BAS operator tool, densité > touch target compliance). Action buttons restent ≥ 24px icon + padding pour préserver le hit area. - **Sécurité a11y** : `Tabs` wired complet WAI-ARIA (`role="tablist"` + `role="tab"` + `aria-selected` + `aria-controls` + `aria-labelledby` + arrow-key nav). `AlertBanner` `role="alert"` (error/warn) vs `role="status"` (success/info) — `aria-live` correct par implication. - **useHashTab** stable F5 + non-polluant historique : `history.replaceState` (pas de pushState, pas d'anchor-jump scroll). ## Test plan - **Frontend** : **236 / 236** vitest (baseline 212 + 24 new tests across 4 new specs + 2 new arrow-key + 1 new aria-controls assertion). - **Typecheck** : `tsc --noEmit` clean. - **Lint** : `eslint --max-warnings=0` clean. - **Backend** : **469 / 469** pytest intact (zéro backend touché). - **Reviews** : code-review APPROVED (1 MINOR + 3 NIT, all patched), design-review APPROVED (2 MINOR + 1 NIT, all patched). ## Comment tester en local ```bash # Sprint 11 est purement frontend. Pas de migration, pas d'env var. git checkout sprint/11-spectrum-ux make build && make start # http://127.0.0.1:5000 ``` Scénarios : 1. **Tabs sur EngagementDetailPage** — ouvre un engagement en lecture. 3 tabs : `Schedule` (par défaut), `Description`, `Simulations`. Count pill sur "Simulations" = nombre exact de simulations. Click un tab → URL devient `…#description` (sans push d'history entry — Back doit toujours mener à la liste d'engagements en 1 clic). 2. **Hash sync F5** — sur `…#simulations`, refresh la page → tab Simulations reste actif. Colle `…#nonsense` → tu vois rien actif (silent fallback), idéalement default fallback dans une future itération. 3. **Arrow-key nav** — focus un tab, ArrowRight / ArrowLeft cyclent entre les 3 tabs avec wrap-around. 4. **Screen reader** — chaque tab a `role="tab"` + `aria-selected`, le panel actif a `role="tabpanel"` + `aria-labelledby` pointant vers le tab actif. 5. **AlertBanner — SimulationFormPage** — sur une simulation `done`, le bandeau "Simulation complétée…" est désormais un `<AlertBanner variant="success">`. Sur une simulation SOC-blocked en mode RT, le bandeau "Cette simulation est en cours de review SOC…" est un `<AlertBanner variant="warn">`. Plus de markup `bg-fog`/`bg-cloud` hand-rollé. 6. **BackLink** — sur Engagement detail, Simulation edit, Template edit : tu vois `← Back to engagements` / `← Back to {engagement name}` / `← Back to templates`. Tous les 3 sont des `<BackLink>` (caption-md text-graphite, hover-instant text-primary). 7. **Compact density** — Engagements list, Templates list, Users admin, Simulations list (inside Engagement) : toutes les tables sont en density 32px row, headers en text-[12px] uppercase, dividers hairline. Action buttons (edit/delete) restent confortables à cliquer. ## Fichiers livrés | Fichier | Type | Notes | |---|---|---| | `frontend/src/hooks/useHashTab.ts` | NEW (~30 LoC) | Hash-stable active tab, `history.replaceState`, cleanup listener | | `frontend/src/components/Tabs.tsx` | NEW | `<Tabs items activeId onChange>` complet WAI-ARIA + arrow-key nav | | `frontend/src/components/AlertBanner.tsx` | NEW | 4 variants, Lucide icons, ARIA correct par variant | | `frontend/src/components/BackLink.tsx` | NEW | `<Link>` React Router + ArrowLeft Lucide size=14 | | `frontend/src/styles/index.css` | MOD | +4 tab recipes, +4 alert recipes, .table-compact recipe | | `frontend/src/pages/EngagementDetailPage.tsx` | MOD | Refactorée en 3 tabs (`TabId` union typed) + BackLink | | `frontend/src/pages/SimulationFormPage.tsx` | MOD | 2 banners hand-rollés → `<AlertBanner>` + back-link → `<BackLink>` | | `frontend/src/pages/TemplateFormPage.tsx` | MOD | back-link → `<BackLink>` | | `frontend/src/pages/EngagementsListPage.tsx` | MOD | `.table-compact` applied | | `frontend/src/pages/TemplatesListPage.tsx` | MOD | `.table-compact` applied | | `frontend/src/pages/UsersAdminPage.tsx` | MOD | `.table-compact` applied + commentaire intention sur reset-password row | | `frontend/src/components/SimulationList.tsx` | MOD | `.table-compact` applied | | `frontend/tests/hooks/useHashTab.test.tsx` | NEW | 4 tests (hash sync, fallback, cleanup) | | `frontend/tests/components/Tabs.test.tsx` | NEW + extended | 9 tests (render, ARIA, count pill, arrow-key nav, brutalism) | | `frontend/tests/components/AlertBanner.test.tsx` | NEW | 6 tests (4 variants, ARIA roles, brutalism) | | `frontend/tests/components/BackLink.test.tsx` | NEW | 4 tests (render, click, brutalism, MemoryRouter integration) | | `DESIGN.md` | MOD | +3 subsections (Navigation › Sub-page tabs, Toast › Inline Banners, Data Tables compact + WCAG rationale) | | `CHANGELOG.md` | MOD | Sprint 11 entry | ## Décisions verrouillées - **Q1** : Tabs primitive **+ EngagementDetailPage consumer** (3 tabs). Décision user. - **Q2** : Compact density **GLOBAL** (32px partout). Décision user, WCAG tradeoff acknowledged. - **Q3** : AlertBanner **+ refactor SimulationFormPage** banners. Décision user. ## Patterns explicitement rejetés depuis Spectrum - ❌ Identité couleur violette `spectrum-700` (contrainte hard : primary stays `#024ad8`) - ❌ `shadow-md/lg/xl` sur cards/modals (Mimic forbid) - ❌ `rounded` / `rounded-lg` / `rounded-full` sur chrome (Mimic = `rounded-none` partout sauf pills + avatars) - ❌ `transition-*` (Mimic = hover instantané) - ❌ Hover-lift effects (`hover:-translate-y-0.5 hover:shadow-lg`) - ❌ Global mission/document selector dans le top bar (Mimic = URL = source of truth) - ❌ Dark mode utility-pair verbeux (Mimic = CSS-vars-driven) - ❌ Spectrum "no primitive layer, re-inline everything" approach - ❌ Per-document theming via PDF CSS ## Commits ``` 89fb38b docs(sprint-11): plan spectrum UX port — 4 primitives + compact density global bca39dc feat(frontend): add Tabs primitive + useHashTab hook 004d075 feat(frontend): refactor EngagementDetailPage to 3-tab layout 59eaa34 feat(frontend): add AlertBanner component + 4 semantic variants 9a8c5f5 feat(frontend): add BackLink helper + dedup 3 hand-rolled instances 1819089 feat(frontend): compact table density (32px row) global 5d34a5e test(frontend): add specs for Tabs, AlertBanner, BackLink, useHashTab 1324a0c docs(design): add tabs / inline banners / compact-table-density to DESIGN.md 790ced4 fix(frontend): design-review polish — table-compact height, header type-scale, tabs a11y 11ce3cf fix(frontend): code-review polish — replaceState, arrow-key nav, TabId, comment ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
knacky added 10 commits 2026-06-21 20:22:25 +00:00
- useHashTab: pure-TS hook reading window.location.hash, falls back to
  defaultId, hashchange listener cleaned up on unmount
- Tabs: <Tabs items activeId onChange> with tab-underline / tab-underline-active
  recipes and count-pill (rounded-pill exception per DESIGN.md)
- index.css: tab-underline*, tab-count-pill*, alert-*, table-compact recipes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tabs: Schedule (default) / Description / Simulations, wired via
useHashTab('schedule'). Count pill on Simulations tab. BackLink
replaces hand-rolled Link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
border-l-4 semantic strip, bg-paper, Lucide icons at size=16.
ARIA role="alert" for error/warn, role="status" for success/info.
No shadow, no radius, no transition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BackLink: ArrowLeft (14px) + caption-md text-graphite hover:text-primary,
instant. Replaces inline Link patterns in EngagementDetailPage (already
committed), SimulationFormPage, and TemplateFormPage. Also migrates
SimulationFormPage Done/SOC banners to AlertBanner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add table-compact class to all 4 list surfaces: EngagementsListPage,
TemplatesListPage, UsersAdminPage, SimulationList. Remove inline
px-xl py-md from th/td — recipe handles padding. Row border-b moved
to recipe. WCAG SC 2.5.5 tradeoff accepted (BAS operator tool).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 new spec files adding 21 tests (7+6+4+4). Each component spec
includes brutalism invariant assertions (no rounded-md, no transition-*,
no shadow-*).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds:
- Navigation > Sub-page tabs subsection (tab-underline recipes, count pill, ARIA)
- Data Tables: row min-height 44px -> 32px + WCAG SC 2.5.5 rationale
- Toast Notifications > Inline Banners subsection (4 variants table)
Also adds Sprint 11 entry to CHANGELOG.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 1 (A): move row height constraint from min-height on tbody tr
(CSS no-op on table-row) to height: 32px on td (works on table-cell).
Fix 2 (A): table header text-[11px] -> text-[12px] to align with
documented caption scale.
Fix 3: add aria-controls + id to Tabs buttons; wrap active tab content
in role="tabpanel" + aria-labelledby in EngagementDetailPage.
Test: add aria-controls/id assertion to Tabs.test.tsx (234 total).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 4: useHashTab navigate() uses history.replaceState instead of
window.location.hash assignment — no spurious history entries, no
anchor-jump scroll side-effect.
Fix 5: Tabs ArrowLeft/ArrowRight keyboard nav (WAI-ARIA tabs pattern).
Fix 6: TabId union type in EngagementDetailPage, cast from string for
type-safe switch on activeTab without breaking Tabs.onChange signature.
Fix 7: intentional comment on UsersAdminPage reset-password aerated row.
Tests: 236/236 (+2 arrow-key assertions in Tabs.test.tsx).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
knacky added 1 commit 2026-06-21 20:38:03 +00:00
caption-bold and caption-md are fontSize tokens — Tailwind generates
text-caption-bold / text-caption-md utilities, not bare-name utilities.
Missing text- prefix caused @apply resolution failure in vite build
(PostCSS step), while vitest passed because it does not resolve @apply.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
knacky merged commit 88dfa74663 into main 2026-06-22 08:25:38 +00:00
knacky deleted branch sprint/11-spectrum-ux 2026-06-22 08:25:38 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: knacky/mimic#13