**Objectif** : absorber les 7 retours QA sprint 3 (UI/UX, workflow, alignement) + livrer le dark mode + durcir le process UI (design-reviewer agent + screenshots mandatory) + automatiser l'ouverture de PR. Pas de hotfix sprint 3 séparé — tout dans sprint 4 (décision user 2026-05-27).
**Pourquoi** : QA sprint 3 — `EngagementsListPage` montre 2 boutons "New engagement" + "Create engagement" qui font la même chose ; le bouton Create de `UsersAdminPage` reste mal aligné malgré le fix sprint 2.
**Critères d'acceptation**
- [ ] AC-17.1 : `EngagementsListPage` n'affiche qu'UN SEUL bouton "New engagement". Le doublon "Create engagement" est supprimé.
- [ ] AC-17.2 : convention nouveaux boutons d'action (Create / Add / Save / Delete) : icône lucide-react ou unicode + label court (≤ 8 chars), pas de phrases. Audit des boutons existants : ne refactoriser que ceux qui dépassent ce seuil, garder les "Mark for review" / "Clear all" qui sont déjà courts ou ont une sémantique sans icône évidente. Boutons à passer en icône+label : "Save Red Team" → "Save" + icône, "Save SOC" → "Save SOC" + icône, "ADD TECHNIQUE" → "+" + "Add", "QUICK SEARCH" → "🔍" + "Search".
- [ ] AC-17.3 : `UsersAdminPage` formulaire "Create account" — les 3 FormField (Username, Password, Role) ont leurs labels alignés sur la même baseline ET leurs inputs alignés sur la même baseline. Le bouton Create est aligné horizontalement avec la rangée des inputs. Pixel-perfect au niveau visuel à 1280×720.
- [ ] AC-18.1 : `PATCH /api/simulations/<sid>` avec status courant `done` retourne **409**`{error: "simulation is done — reopen first"}` quel que soit le rôle.
- [ ] AC-18.2 : nouvelle transition `POST /api/simulations/<sid>/transition {to: "review_required"}` quand status courant == `done` → 200, autorisée admin + redteam + soc. Met à jour `updated_at`.
- [ ] AC-18.3 : la transition `→ review_required` depuis `pending`/`in_progress` garde le comportement sprint 2 (admin/redteam only). La nouvelle règle s'ajoute SEULEMENT pour le cas `done`.
- [ ] AC-18.4 : sur `SimulationFormPage`, quand status == `done` :
- Tous les champs (RT + SOC) sont disabled.
-`MitreTechniquesField` en read-only (chips sans ×, input + icône matrice masqués).
- L'action bar affiche UNIQUEMENT un bouton "Reopen" (visible admin/redteam/soc).
- Save RT, Save SOC, Mark for review, Close, Delete sont masqués.
- [ ] AC-18.5 : click Reopen → POST transition, toast `'Simulation reopened'`, badge se met à jour, les champs redeviennent éditables selon le rôle.
- [ ] AC-19.1 : quand une simulation transitionne vers `in_progress` (auto-transition via PATCH RT-field non vide), si son engagement parent est `planned`, l'engagement passe à `active` dans la même unité de travail DB.
- [ ] AC-19.2 : si l'engagement est déjà `active` ou `closed`, pas de changement.
- [ ] AC-19.3 : aucun retour arrière auto. La transition `closed` reste manuelle.
- [ ] AC-19.4 : le frontend invalide `["engagement", eid]` et `["engagements"]` après chaque PATCH/transition simulation pour récupérer le statut à jour.
### US-20 — Matrice MITRE : look attack.mitre.org + pas de scroll horizontal
**Pourquoi** : QA sprint 3 — la matrice actuelle a un scroll horizontal et un layout maison.
**Critères d'acceptation**
- [ ] AC-20.1 : `MitreMatrixModal` est élargi à `max-w-[98vw]`.
- [ ] AC-20.2 : layout 12 colonnes (12 tactiques Enterprise) qui tiennent SANS scroll horizontal à 1280×720 min. Largeur cellule technique ~95-110px (vs 220px actuel), font `text-[12px]`.
- [ ] AC-20.3 : couleurs cohérentes DESIGN.md ET visuellement proches de attack.mitre.org : header tactic avec fond contrasté + label uppercase tracking, techniques en cellules `bg-canvas` avec hairline border, hover `bg-fog`, sélectionnée `bg-primary` texte blanc.
- [ ] AC-20.4 : scroll vertical autorisé (`max-h-[80vh] overflow-y-auto`). Jamais de scroll horizontal.
- [ ] AC-20.5 : sub-techniques expand/collapse PRÉSERVÉ — pas de régression sprint 3 AC-15.2. Compteur "N selected" par tactique reste lisible.
- [ ] AC-20.6 : screenshot comparaison Mimic matrix vs attack.mitre.org joint au summary frontend-builder.
### US-21 — Sélection de tactique en plus des techniques
**Pourquoi** : QA sprint 3 — l'utilisateur veut tagger une simulation par TACTIQUE (ex : `TA0007 Discovery`) sans devoir choisir une technique précise.
- [ ] AC-21.1 : modèle `Simulation` gagne un champ `tactic_ids` (colonne JSON, liste de strings TA-id, défaut `[]`). Séparé de `techniques`.
- [ ] AC-21.2 : migration Alembic `0004_simulation_tactic_ids.py` — ADD COLUMN `tactic_ids` (JSON, NOT NULL, default `[]`). Pas besoin de batch pour ADD COLUMN (SQLite natif). Aucun backfill (default suffit).
- [ ] AC-21.3 : sérialisation Simulation expose `tactics: [{id, name}]` enrichi à partir de `tactic_ids` (id snapshot + name dérivé du bundle MITRE au runtime, comme pour `techniques`).
- [ ] AC-21.4 : `PATCH /api/simulations/<sid>` accepte `{tactic_ids: ["TA0007", ...]}`. Validation : chaque ID doit exister (préfixe `TA`, présent dans `_TACTIC_ORDER`). Dedup serveur. ID inconnu → 400. Bundle non chargé → 503.
- [ ] AC-21.5 : `tactic_ids` est ajouté au gate SOC : `(REDTEAM_FIELDS | {"technique_ids", "tactic_ids"}) & payload.keys()`. SOC envoie → 403. Auto-transition se déclenche aussi si `tactic_ids` non vide.
- [ ] AC-21.6 : `MitreMatrixModal` — le header de chaque colonne tactique devient cliquable (toggle de la tactique elle-même). État visuel distinct des techniques sélectionnées. Compteur passe à `N+M selected` (techniques + tactique).
- [ ] AC-21.7 : `MitreTechniquesField` — tactiques sélectionnées affichées comme chips distincts (style différencié : `bg-primary text-canvas` au lieu de `bg-primary-soft text-primary-deep`). × pour retirer. Auto-save sur add/remove.
- [ ] AC-22.1 : sous le label "MITRE Techniques", le composant affiche :
- Une rangée de chips (techniques + tactiques sélectionnées).
- En dessous, une rangée `[input texte autocomplete] [icône matrice]`.
- L'input fait l'autocomplete inline (debounce 200ms, dropdown ↑↓Enter, comme sprint 2 mais EMBARQUÉ).
- L'icône matrice à droite ouvre `MitreMatrixModal`.
- Aucun bouton textuel "Add Technique" ni "Quick Search".
- [ ] AC-22.2 : les chips affichent UNIQUEMENT la référence (T-id ou TA-id, ex : `T1059.001` ou `TA0007`). Le nom apparaît au survol via `title=` attribute.
- [ ] AC-22.3 : `MitreTechniquePicker` existant est intégré dans le nouveau layout comme l'autocomplete inline. Garde la signature `onSelect`.
- [ ] AC-22.4 : empty state : message court ("No techniques selected") dans la zone des chips. L'input et l'icône matrice restent visibles.
- [ ] AC-22.5 : mode read-only (SOC sur simu non-done, ou tous sur simu done) : chips sans ×, input + icône cachés.
- [ ] AC-23.4 : Tailwind `darkMode: 'class'` activé. Classe `dark` appliquée sur `<html>` selon le résolu. Tokens DESIGN.md étendus avec variantes dark (canvas, paper, ink, graphite, charcoal, etc.). Primary HP Electric Blue garde sa teinte.
- [ ] AC-23.5 : tous les composants principaux audités et utilisent les classes Tailwind `dark:bg-...` / `dark:text-...`. Pas de couleur hardcodée.
- [ ] AC-23.6 : screenshots light + dark de `EngagementsListPage`, `SimulationFormPage`, `MitreMatrixModal` ouverte. Joints au summary.
**Pourquoi** : sprint 4 framing acté. Sprint 2/3 avait laissé passer des bugs visuels faute de pass design dédié.
**Critères d'acceptation**
- [ ] AC-24.1 : nouveau fichier `.claude/agents/design-reviewer.md`. Brief : revoit le diff frontend + les screenshots fournis par le frontend-builder, audit alignement / hiérarchie typo / DESIGN.md token usage / responsive sanity / cohérence visuelle. Read-only. Lance après frontend-builder, avant code-reviewer.
- [ ] AC-24.2 : `.claude/agents/frontend-builder.md` mis à jour pour rendre EXPLICITE que screenshots sont MANDATORY avant de marquer la tâche terminée (au moins 1 par feature visible / état modifié). Liste explicite des screenshots attendus dans le summary.
- [ ] AC-24.3 : workflow sprint mis à jour dans SPEC.md § Workflows : ajouter design-reviewer entre frontend-builder et code-reviewer.
- Upgrade : `op.add_column('simulations', sa.Column('tactic_ids', sa.JSON(), nullable=False, server_default=sa.text("'[]'")))`. ADD COLUMN OK sans batch sur SQLite. `server_default` règle le NOT NULL pour les lignes existantes.
- Downgrade : `with op.batch_alter_table('simulations') as batch_op: batch_op.drop_column('tactic_ids')`.
- Test : schéma post-upgrade a `tactic_ids` NOT NULL avec default `[]`.
1.**Guard `done` (AC-18.1)** : tout en haut de `apply_patch`, AVANT le check RBAC, si `simulation.status == "done"` → 409 `{error: "simulation is done — reopen first"}`.
3.**Validation `tactic_ids`** upfront (similaire à `technique_ids`) : tous les IDs validés contre le bundle, dedup `dict.fromkeys`. Bundle non chargé → 503.
4.**Auto-transition** : ajouter le check `len(payload["tactic_ids"]) > 0` au calcul `auto_trigger`.
5.**Transition `done → review_required` (AC-18.2)** : ajouter ce cas au state machine. Autorisé admin + redteam + soc. Update `updated_at`. Autres transitions depuis `done` → 409.
6.**Hook engagement auto-status (AC-19.1)** : après une transition de simu vers `in_progress` (auto OU manual), appeler une fonction `_maybe_activate_engagement(simulation)` qui, si `simulation.engagement.status == "planned"`, set `engagement.status = "active"` et add à la session (commit en même temps que la simu).
**API `simulations.py`** :
- PATCH : le check status==done est fait dans `apply_patch` (voir au-dessus).
- Transition : accepter le nouveau cas done → review_required pour admin/redteam/soc.
-`test_simulations_done_readonly.py` (nouveau) : PATCH simu done → 409 (admin/redteam/soc). Reopen via transition → 200. Autres transitions depuis done → 409. Après reopen, PATCH OK.
-`test_engagement_lifecycle.py` (nouveau) : création simu → engagement reste `planned`. PATCH simu → simu in_progress + engagement active. Engagement déjà active → pas de changement. Engagement closed → pas de changement.
- Migration test : `tactic_ids` column NOT NULL après upgrade 0004 (similaire au pattern Alembic round-trip sprint 3).
- Adapter `test_simulations_crud.py`, `test_simulations_patch.py`, `test_simulations_workflow.py` si nécessaire pour les assertions sur `tactics` et la garde done.
-`EngagementsListPage.tsx` : supprimer le doublon "Create engagement". Garder un seul CTA "New" + icône `+` (selon convention AC-17.2).
-`UsersAdminPage.tsx` : retravailler la grille pour pixel-perfect alignment. Choix laissé au builder (align-items: stretch + align-self, ou restructurer en 2 rangées).
- Audit boutons : refactoriser ceux qui dépassent ≤ 8 chars. Garder "Mark for review" / "Clear all" / "Reopen" sans icône si pas d'icône évidente. Boutons à passer en icône+label : "Save Red Team" → icône + "Save", "Save SOC" → icône + "Save SOC", "ADD TECHNIQUE" → "+" + "Add" (rendu obsolète par US-22), "QUICK SEARCH" → "🔍" + "Search" (rendu obsolète par US-22).
**US-18 — Done read-only + Reopen**
-`SimulationFormPage.tsx` :
- Quand `simulation.status === 'done'` : tous champs disabled, `MitreTechniquesField disabled`, action bar montre UNIQUEMENT "Reopen" + icône (`↻`).
-`useUpdateSimulation` et `useTransitionSimulation` : ajouter `["engagement", eid]` et `["engagements"]` aux invalidations après mutation réussie. Pas d'autre changement visuel.
**US-20 — Matrice MITRE attack.mitre.org look**
-`MitreMatrixModal.tsx` overhaul :
-`max-w-[98vw]`, `max-h-[80vh] overflow-y-auto`, JAMAIS de scroll horizontal.
-`display: grid; grid-template-columns: repeat(12, minmax(0, 1fr))` pour répartir équitablement.
- Plus de boutons textuels "Add Technique" / "Quick Search".
- Chips compacts (T-id ou TA-id seul, name en `title=`).
- Empty state minimal.
**US-23 — Dark mode**
-`Layout.tsx` : toggle theme dans la topbar. Hook `useTheme()` (localStorage + media query). 3 états avec cycle.
-`tailwind.config.ts` : `darkMode: 'class'`. Tokens étendus avec variantes dark (recommandé via CSS variables sous `.dark { ... }` dans `index.css`, comme ça les composants n'ont pas à dupliquer leurs classes).
- Audit tous les composants : aucune couleur hardcodée (pas de `bg-white`, `text-black`, `#xxxxxx` inline). Tous passent un check visuel light + dark.
6.**Engagement auto trigger** : `planned → active` sur 1ère simu in_progress (auto-transition ou manual). Pas de retour arrière auto. ✓ mémoire
7.**PR helper token source** : `~/.git-credentials` (parse user + token via sed, cf [[reference-gitea-pr-api]]). ✓ 2026-05-27
8.**Workflow design-reviewer** : insérée entre frontend-builder et code-reviewer, read-only. Diff frontend + screenshots. Format rapport à la code-reviewer mais focus visuel/design. ✓ mémoire
9.**Screenshots frontend-builder** : MANDATORY au sprint 4, en sortie du frontend-builder, paths absolus dans summary, refus de marquer la tâche done sans. ✓ mémoire
1. ✅ Team-lead a re-appliqué le SPEC sprint 3 oublié (`ba313a3`).
2. ✅ User a validé les 4 décisions ouvertes (tactic separated, theme system, matrix qualitative, token from ~/.git-credentials). Avec les 5 acquises en mémoire (sprint 4 scope), ça fait 9 décisions arrêtées.
3. 🟡 Team-lead met à jour SPEC.md § Workflows + § Décisions techniques (§0).
4. 🟡 Spec-reviewer valide le plan vs SPEC.md (anti-trous comme à sprint 3 — RBAC field-level, batch SQLite, scope ambigu).