feat(frontend): SimulationFormPage 3-tab layout (Red Team / SOC / Tâche C2) (sprint 13) #15

Open
knacky wants to merge 3 commits from sprint/13-sim-tabs into main
Owner

Summary

  • Sprint 13 — SimulationFormPage 3-tab layout : aligne le pattern Tabs du sprint 11 (EngagementDetailPage 3→2 tabs) sur SimulationFormPage. Passe de la 2-col [Red Team | SOC] + C2TasksPanel en-dessous à 3 tabs Red Team / SOC / Tâche C2 avec sticky bar contextuelle, count pill C2 réactif, et state disabled gating SOC + C2 selon status + RBAC.
  • Tabs primitive étendu : ajout du support disabled per-item dans Tabs.tsx (cf. sprint 11). aria-disabled, classe .tab-underline-disabled, ArrowLeft/Right skip les tabs disabled (avec guard anti-boucle infinie si tous disabled).
  • useHashTab : nouveau param optionnel validIds?: string[] pour fallback gracieux si le hash pointe ailleurs. Back-compatible (EngagementDetailPage non modifiée).
  • RBAC strict : tab Tâche C2 désactivé pour les rôles SOC (back-end renvoie 403 sur /c2/tasks pour SOC depuis sprint 8 — sans ce gate, SOC voyait un panel cassé en cliquant le tab).
  • Reviews 2 passes : design APPROVED + code CHANGES REQUESTED → polish appliqué (1 MAJOR + 3 MINOR) → tout APPROVED.

Dépendance — sprints 11 + 12

Branche basée sur sprint/12-i18n-fr (PR #14 toujours ouverte). Le diff incluera temporairement les commits sprint 11 + 12 jusqu'à ce que PR #14 merge — il se réduira automatiquement à sprint 13 seul.

Test plan

  • Frontend : 254 / 254 vitest (baseline 245 sprint 12 + 8 nouveaux sprint 13 + 1 polish SOC×C2). tsc --noEmit clean, eslint --max-warnings=0 clean.
  • Build : pnpm build clean (✓ 2.54s) — PostCSS résout tous les @apply, leçon sprint 11 feedback_css_apply_tokens respectée.
  • Backend : 469 / 469 pytest intact (zéro file backend touché).
  • E2E : non re-joué (mur Vite proxy connu). Smoke manuel ci-dessous.

Comment tester en local

git checkout sprint/13-sim-tabs
make build && make start
# http://127.0.0.1:5000

Scénarios :

  1. RT par défaut (pending) — Ouvre une sim pending. Tab Red Team actif (underline bleu), tabs SOC (greyed text-steel, non-clickable) et Tâche C2 (normal — admin RT). Sticky bar [ENREGISTRER + MARQUER POUR RÉVISION + Supprimer (rouge à droite)]. Footer FR.
  2. Pass to SOC (review_required) — Clique MARQUER POUR RÉVISION → status flip à review_required → tab SOC devient enabled. Clique-le. Le panel SOC apparaît avec les 4 fields (Source de log, Logs, Commentaire SOC, Numéro d'incident). Sticky bar bascule : [ENREGISTRER SOC + CLORE].
  3. C2 tab admin — Clique Tâche C2 depuis n'importe quel status (admin/redteam). Count pill = nombre de tâches C2 (caché si 0). C2TasksPanel rendu plein écran. Pas de sticky bar sur ce tab (actions sont inline dans le tab Red Team).
  4. C2 tab SOC — Login en SOC, ouvre une sim review_required. Tab SOC enabled (correct), tab Tâche C2 greyed (cohérent avec le 403 backend). Clique impossible.
  5. Mode création — Nouveau sim depuis EngagementDetailPage. Tabs visibles, SOC + Tâche C2 greyed. Form minimal (Nom + Techniques MITRE). Sticky bar [CRÉER LA SIMULATION + ANNULER].
  6. Done state — Sim en done. AlertBanner success au-dessus des tabs ("Cette simulation est terminée…"). Tous tabs enabled. Sticky bar SOC : [ENREGISTRER SOC + Réouvrir].
  7. Hash sync — F5 sur #soc d'une sim pending → tab Red Team actif (safeTab fallback). URL bar auto-corrigée à #red-team via useEffect sync.
  8. Keyboard nav — Focus un tab, ArrowRight cycle vers le suivant en skippant les disabled (RT → C2 si SOC disabled, pas RT → SOC bloqué).

Fichiers modifiés

Path Type Notes
frontend/src/components/Tabs.tsx MOD TabItem.disabled + click guard + nav skip-disabled + aria-disabled
frontend/src/hooks/useHashTab.ts MOD validIds?: string[] optionnel, back-compat sprint 11
frontend/src/styles/index.css MOD nouvelle recipe .tab-underline-disabled (text-steel cursor-not-allowed)
frontend/src/pages/SimulationFormPage.tsx MOD 3-tab refactor, sticky bar contextuelle, AlertBanner SOC-blocked supprimé, safeTab guard + useEffect sync
frontend/src/i18n/fr.json MOD 3 nouvelles keys simulation.form.tab.{redTeam,soc,c2}
DESIGN.md MOD paragraphe additif sur disabled variant + aria-controls
CHANGELOG.md MOD entrée sprint 13
frontend/tests/components/Tabs.test.tsx MOD +5 tests disabled state (click-no-fire, ArrowRight skip, ArrowLeft skip, aria-disabled, class application)
frontend/tests/SimulationFormPage.test.tsx MOD refactor important — tabs au lieu de 2-col, SOC×C2 RBAC test, count pill C2, Reopen via SOC tab

Décisions verrouillées (de la phase planning)

  • Q1 : Mode création → tabs visibles avec SOC + C2 désactivés (aria-disabled)
  • Q2 : Tab SOC désactivé tant que status ∉ {review_required, done}
  • Q3 : Sticky bar entièrement contextuelle par tab — RT [Save + Mark for review + Delete] / SOC [Save SOC + Close + Reopen] / C2 [aucune sticky bar]
  • Q4 : Count pill sur C2 tab = c2Tasks.length

Décisions team-lead (appliquées)

  • Default active tab = red-team
  • URL hash : #red-team / #soc / #c2
  • Delete dans tab RT (auteur de la sim)
  • Reopen dans tab SOC (cohérence avec Close)
  • AlertBanner Done : au-dessus des tabs (status global)
  • AlertBanner SOC-blocked : supprimé (le tab disabled le remplace)
  • C2TasksPanel : rendu dans le panel du tab C2
  • [Execute via C2] + [Import C2 history] : restent dans le tab Red Team (actions RT qui déclenchent C2)

Findings résolus (reviews)

Source Sévérité Fix
Code MAJOR RBAC C2 tab disabled pour SOC role (c2TabDisabled = isNew || isSoc) + nouveau test
Code MINOR UX/URL useEffect sync safeTab ↔ activeTab quand stale hash
Code MINOR A11y type="submit" form=""type="button" sur new-sim submit
Design MINOR Visibility .tab-underline-disabled : text-graphite opacity-50text-steel (meilleur contraste)
Design NIT Tooltip Skipped (visuel suffit, complexité Tabs pas justifiée)

Out of scope (reconnu)

  • "Simulations" double label+h1 sur EngagementDetailPage (sprint 11 carry-over)
  • ForbiddenState / NotFoundState components (n'existent pas)
  • Multi-langue (FR uniquement)
  • Backend changes

Commits

9b6b682 docs(sprint-13): plan SimulationFormPage 3-tab layout (RT/SOC/C2)
0378792 feat(frontend): SimulationFormPage 3-tab layout (Red Team / SOC / Tâche C2)
578a7c3 fix(frontend): SOC C2 tab RBAC gate + safeTab sync + disabled tab contrast (review polish)

🤖 Generated with Claude Code

## Summary - **Sprint 13 — SimulationFormPage 3-tab layout** : aligne le pattern Tabs du sprint 11 (EngagementDetailPage 3→2 tabs) sur SimulationFormPage. Passe de la 2-col `[Red Team | SOC]` + C2TasksPanel en-dessous à **3 tabs** `Red Team / SOC / Tâche C2` avec sticky bar **contextuelle**, count pill C2 réactif, et state `disabled` gating SOC + C2 selon status + RBAC. - **Tabs primitive étendu** : ajout du support `disabled` per-item dans `Tabs.tsx` (cf. sprint 11). `aria-disabled`, classe `.tab-underline-disabled`, ArrowLeft/Right skip les tabs disabled (avec guard anti-boucle infinie si tous disabled). - **`useHashTab`** : nouveau param optionnel `validIds?: string[]` pour fallback gracieux si le hash pointe ailleurs. Back-compatible (EngagementDetailPage non modifiée). - **RBAC strict** : tab Tâche C2 désactivé pour les rôles SOC (back-end renvoie 403 sur `/c2/tasks` pour SOC depuis sprint 8 — sans ce gate, SOC voyait un panel cassé en cliquant le tab). - **Reviews 2 passes** : design APPROVED + code CHANGES REQUESTED → polish appliqué (1 MAJOR + 3 MINOR) → tout APPROVED. ## Dépendance — sprints 11 + 12 Branche basée sur `sprint/12-i18n-fr` (PR #14 toujours ouverte). Le diff incluera temporairement les commits sprint 11 + 12 jusqu'à ce que PR #14 merge — il se réduira automatiquement à sprint 13 seul. ## Test plan - **Frontend** : **254 / 254** vitest (baseline 245 sprint 12 + 8 nouveaux sprint 13 + 1 polish SOC×C2). `tsc --noEmit` clean, `eslint --max-warnings=0` clean. - **Build** : `pnpm build` clean (✓ 2.54s) — PostCSS résout tous les `@apply`, leçon sprint 11 `feedback_css_apply_tokens` respectée. - **Backend** : **469 / 469** pytest intact (zéro file backend touché). - **E2E** : non re-joué (mur Vite proxy connu). Smoke manuel ci-dessous. ## Comment tester en local ```bash git checkout sprint/13-sim-tabs make build && make start # http://127.0.0.1:5000 ``` Scénarios : 1. **RT par défaut (`pending`)** — Ouvre une sim `pending`. Tab `Red Team` actif (underline bleu), tabs `SOC` (greyed `text-steel`, non-clickable) et `Tâche C2` (normal — admin RT). Sticky bar `[ENREGISTRER + MARQUER POUR RÉVISION + Supprimer (rouge à droite)]`. Footer FR. 2. **Pass to SOC (`review_required`)** — Clique `MARQUER POUR RÉVISION` → status flip à `review_required` → tab `SOC` devient enabled. Clique-le. Le panel SOC apparaît avec les 4 fields (Source de log, Logs, Commentaire SOC, Numéro d'incident). Sticky bar bascule : `[ENREGISTRER SOC + CLORE]`. 3. **C2 tab admin** — Clique `Tâche C2` depuis n'importe quel status (admin/redteam). Count pill = nombre de tâches C2 (caché si 0). C2TasksPanel rendu plein écran. **Pas de sticky bar** sur ce tab (actions sont inline dans le tab Red Team). 4. **C2 tab SOC** — Login en SOC, ouvre une sim `review_required`. Tab `SOC` enabled (correct), tab `Tâche C2` greyed (cohérent avec le 403 backend). Clique impossible. 5. **Mode création** — Nouveau sim depuis EngagementDetailPage. Tabs visibles, `SOC` + `Tâche C2` greyed. Form minimal (Nom + Techniques MITRE). Sticky bar `[CRÉER LA SIMULATION + ANNULER]`. 6. **Done state** — Sim en `done`. AlertBanner success au-dessus des tabs ("Cette simulation est terminée…"). Tous tabs enabled. Sticky bar SOC : `[ENREGISTRER SOC + Réouvrir]`. 7. **Hash sync** — F5 sur `#soc` d'une sim `pending` → tab `Red Team` actif (safeTab fallback). URL bar auto-corrigée à `#red-team` via `useEffect` sync. 8. **Keyboard nav** — Focus un tab, ArrowRight cycle vers le suivant en skippant les disabled (RT → C2 si SOC disabled, pas RT → SOC bloqué). ## Fichiers modifiés | Path | Type | Notes | |---|---|---| | `frontend/src/components/Tabs.tsx` | MOD | `TabItem.disabled` + click guard + nav skip-disabled + `aria-disabled` | | `frontend/src/hooks/useHashTab.ts` | MOD | `validIds?: string[]` optionnel, back-compat sprint 11 | | `frontend/src/styles/index.css` | MOD | nouvelle recipe `.tab-underline-disabled` (text-steel cursor-not-allowed) | | `frontend/src/pages/SimulationFormPage.tsx` | MOD | 3-tab refactor, sticky bar contextuelle, AlertBanner SOC-blocked supprimé, `safeTab` guard + useEffect sync | | `frontend/src/i18n/fr.json` | MOD | 3 nouvelles keys `simulation.form.tab.{redTeam,soc,c2}` | | `DESIGN.md` | MOD | paragraphe additif sur disabled variant + aria-controls | | `CHANGELOG.md` | MOD | entrée sprint 13 | | `frontend/tests/components/Tabs.test.tsx` | MOD | +5 tests disabled state (click-no-fire, ArrowRight skip, ArrowLeft skip, aria-disabled, class application) | | `frontend/tests/SimulationFormPage.test.tsx` | MOD | refactor important — tabs au lieu de 2-col, SOC×C2 RBAC test, count pill C2, Reopen via SOC tab | ## Décisions verrouillées (de la phase planning) - **Q1** : Mode création → tabs visibles avec SOC + C2 **désactivés** (`aria-disabled`) - **Q2** : Tab SOC désactivé tant que status ∉ `{review_required, done}` - **Q3** : Sticky bar **entièrement contextuelle** par tab — RT [Save + Mark for review + Delete] / SOC [Save SOC + Close + Reopen] / C2 [aucune sticky bar] - **Q4** : Count pill sur C2 tab = `c2Tasks.length` ## Décisions team-lead (appliquées) - Default active tab = `red-team` - URL hash : `#red-team` / `#soc` / `#c2` - Delete dans tab RT (auteur de la sim) - Reopen dans tab SOC (cohérence avec Close) - AlertBanner Done : au-dessus des tabs (status global) - AlertBanner SOC-blocked : supprimé (le tab disabled le remplace) - C2TasksPanel : rendu dans le panel du tab C2 - `[Execute via C2]` + `[Import C2 history]` : restent dans le tab Red Team (actions RT qui déclenchent C2) ## Findings résolus (reviews) | Source | Sévérité | Fix | |---|---|---| | Code MAJOR | RBAC | C2 tab disabled pour SOC role (`c2TabDisabled = isNew \|\| isSoc`) + nouveau test | | Code MINOR | UX/URL | `useEffect` sync `safeTab ↔ activeTab` quand stale hash | | Code MINOR | A11y | `type="submit" form=""` → `type="button"` sur new-sim submit | | Design MINOR | Visibility | `.tab-underline-disabled` : `text-graphite opacity-50` → `text-steel` (meilleur contraste) | | Design NIT | Tooltip | Skipped (visuel suffit, complexité Tabs pas justifiée) | ## Out of scope (reconnu) - **"Simulations" double label+h1** sur EngagementDetailPage (sprint 11 carry-over) - **`ForbiddenState` / `NotFoundState` components** (n'existent pas) - **Multi-langue** (FR uniquement) - **Backend changes** ## Commits ``` 9b6b682 docs(sprint-13): plan SimulationFormPage 3-tab layout (RT/SOC/C2) 0378792 feat(frontend): SimulationFormPage 3-tab layout (Red Team / SOC / Tâche C2) 578a7c3 fix(frontend): SOC C2 tab RBAC gate + safeTab sync + disabled tab contrast (review polish) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
knacky added 3 commits 2026-06-22 09:01:25 +00:00
- Tabs: disabled per-item (aria-disabled, native disabled, tab-underline-disabled,
  arrow-key skip); aria-controls wired
- useHashTab: optional validIds fallback to defaultId
- SimulationFormPage: 3-tab refactor; SOC tab gated on review_required|done;
  C2 tab disabled in create mode; contextual sticky bar hidden on C2 tab;
  safeTab guard against stale hash; SOC-blocked banner removed
- fr.json: simulation.form.tab.{redTeam,soc,c2} keys
- DESIGN.md: disabled tab variant + aria-controls documented
- Tests: 253 passing (5 new Tabs disabled tests, SimulationFormPage rewritten
  for tab layout)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- c2TabDisabled: add isSoc condition — SOC role has no access to /c2/tasks (backend 403)
- safeTab sync: useEffect before early returns using detail.data?.status; syncs
  window.location.hash when hash points to a now-disabled tab after status transition
- create-mode submit button: type="submit" form="" → type="button" (form association fix)
- .tab-underline-disabled: text-graphite opacity-50 → text-steel (consistent with
  btn-outline:disabled; better perceptual contrast)
- tests: 254 passing (+ SOC × C2 tab disabled assertion)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin sprint/13-sim-tabs:sprint/13-sim-tabs
git checkout sprint/13-sim-tabs
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#15