feat: sprint 4 — UI polish + dark mode + workflow tightening + process hygiene #7

Merged
knacky merged 15 commits from sprint/4-ui-polish into main 2026-05-28 04:01:21 +00:00
Owner

Summary

  • Dark mode (toggle topbar light/dark/system + localStorage persistence) avec split de tokens ink (text themed) vs slab (surface dark fixe non-inversée). Audit complet des composants pour zéro couleur hardcodée hors-tokens.
  • MITRE matrix modal overhaul : grille 12 colonnes, plus de scroll horizontal à 1280×720, look proche d'attack.mitre.org. Sélection de tactique (TA-id) en plus des techniques et sub-techniques.
  • Refonte input MITRE : autocomplete inline + bouton matrice à droite (suppression des boutons textuels "Add Technique"/"Quick Search"). Chips compacts avec référence seule (T-id ou TA-id) + tooltip nom au survol.
  • Done = terminal + Reopen : PATCH sur une simu done retourne 409 ; nouveau cas transition done → review_required ouvert aux 3 rôles. UI cohérente (read-only + bouton Reopen seul).
  • Engagement auto-status : planned → active automatique à la première simulation in_progress. Pas de retour arrière auto.
  • UI polish : dédoublonnage CTA "New" sur EngagementsList, alignement enfin pixel-perfect sur UsersAdmin (3e tentative, refactor structural 3-row grid), audit boutons (icônes lucide + label court).
  • Process hygiene : nouvel agent design-reviewer (run après frontend-builder), screenshots devenu MANDATORY au DoD du frontend-builder.
  • Infra : scripts/open-pr.sh + make open-pr SPRINT=N TITLE="..." BODY=path — cette PR est ouverte avec ce nouveau target (dogfood AC-25.4).

Test plan

  • Backend : 193/193 pytest (ruff + mypy clean).
  • Frontend : 92/92 vitest (typecheck + lint clean).
  • E2e Playwright : 158/158 (toutes les ACs sprint 4 US-17 → US-23 + régression sprint 1-3 verts).
  • Migration 0004 : Alembic round-trip testé (NOT NULL contrainte vérifiée post-upgrade).
  • Reproductibilité : tests migration utilisent maintenant Path(__file__) (plus de hardcoded paths).
  • 14 commits sur la branche, dont 4 fixes post-design-review et 2 fixes post-code-review.

Comment tester en local

make build && make start                            # auto-détecte podman si pas de docker
make create-admin USER=alice PASS=changeme8         # si premier setup
# Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut)

Scénarios :

  1. Theme toggle — clique le ☀️ / 🌙 / 🖥 dans la topbar pour cycler light/dark/system. Recharge → la pref persiste.
  2. Matrix modale — ouvre une simu, click l'icône matrice. Toutes les 12 tactiques tiennent sans scroll horizontal. Click sur un header de tactique (ex: DISCOVERY) = sélection TA-id en plus des techniques.
  3. Tags compacts — sélectionne T1059 + TA0007 → chips affichent T1059 et TA0007 (référence seule). Survol = nom complet en tooltip.
  4. Done terminal — clôture une simu (status → done). Toute édition désactivée. Click "Reopen" → retour à review_required, édition redevient possible.
  5. Engagement auto-active — crée un nouvel engagement (planned), crée+remplis une simu, sauve. L'engagement passe auto en active.
  6. UsersAdmin alignement — visite /admin/users. Les 3 labels Username/Password/Role sont sur la même baseline, idem pour les inputs et le bouton Create.
  7. PR helper — vérifier que cette PR a été créée via make open-pr (dogfood).

Notes

  • Breaking API : GET /api/mitre/matrix retourne maintenant tactic_id en TA-format (TA0007) au lieu du slug interne (discovery). Aligne sur le format accepté par PATCH tactic_ids. Caught by e2e — fixed in commit a824df0.
  • Spec drift résolu : sprint 3 §0 SPEC update qui avait été oublié au commit est porté ici dans ba313a3.
  • Sprint 5 prévu : templates de simulations pré-remplies (item 8 du QA sprint 3).

🤖 Generated with Claude Code

## Summary - **Dark mode** (toggle topbar light/dark/system + `localStorage` persistence) avec split de tokens `ink` (text themed) vs `slab` (surface dark fixe non-inversée). Audit complet des composants pour zéro couleur hardcodée hors-tokens. - **MITRE matrix modal overhaul** : grille 12 colonnes, plus de scroll horizontal à 1280×720, look proche d'attack.mitre.org. Sélection de tactique (TA-id) en plus des techniques et sub-techniques. - **Refonte input MITRE** : autocomplete inline + bouton matrice à droite (suppression des boutons textuels "Add Technique"/"Quick Search"). Chips compacts avec référence seule (T-id ou TA-id) + tooltip nom au survol. - **Done = terminal + Reopen** : PATCH sur une simu `done` retourne 409 ; nouveau cas transition `done → review_required` ouvert aux 3 rôles. UI cohérente (read-only + bouton Reopen seul). - **Engagement auto-status** : `planned → active` automatique à la première simulation `in_progress`. Pas de retour arrière auto. - **UI polish** : dédoublonnage CTA "New" sur EngagementsList, alignement enfin pixel-perfect sur UsersAdmin (3e tentative, refactor structural 3-row grid), audit boutons (icônes lucide + label court). - **Process hygiene** : nouvel agent `design-reviewer` (run après frontend-builder), screenshots devenu MANDATORY au DoD du frontend-builder. - **Infra** : `scripts/open-pr.sh` + `make open-pr SPRINT=N TITLE="..." BODY=path` — cette PR est ouverte avec ce nouveau target (dogfood AC-25.4). ## Test plan - Backend : **193/193** pytest (`ruff` + `mypy` clean). - Frontend : **92/92** vitest (`typecheck` + `lint` clean). - E2e Playwright : **158/158** (toutes les ACs sprint 4 US-17 → US-23 + régression sprint 1-3 verts). - Migration 0004 : Alembic round-trip testé (NOT NULL contrainte vérifiée post-upgrade). - Reproductibilité : tests migration utilisent maintenant `Path(__file__)` (plus de hardcoded paths). - 14 commits sur la branche, dont 4 fixes post-design-review et 2 fixes post-code-review. ## Comment tester en local ```bash make build && make start # auto-détecte podman si pas de docker make create-admin USER=alice PASS=changeme8 # si premier setup # Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut) ``` Scénarios : 1. **Theme toggle** — clique le `☀️ / 🌙 / 🖥` dans la topbar pour cycler light/dark/system. Recharge → la pref persiste. 2. **Matrix modale** — ouvre une simu, click l'icône matrice. Toutes les 12 tactiques tiennent sans scroll horizontal. Click sur un header de tactique (ex: DISCOVERY) = sélection TA-id en plus des techniques. 3. **Tags compacts** — sélectionne T1059 + TA0007 → chips affichent `T1059` et `TA0007` (référence seule). Survol = nom complet en tooltip. 4. **Done terminal** — clôture une simu (status → done). Toute édition désactivée. Click "Reopen" → retour à `review_required`, édition redevient possible. 5. **Engagement auto-active** — crée un nouvel engagement (planned), crée+remplis une simu, sauve. L'engagement passe auto en `active`. 6. **UsersAdmin alignement** — visite `/admin/users`. Les 3 labels Username/Password/Role sont sur la même baseline, idem pour les inputs et le bouton Create. 7. **PR helper** — vérifier que cette PR a été créée via `make open-pr` (dogfood). ## Notes - Breaking API : `GET /api/mitre/matrix` retourne maintenant `tactic_id` en TA-format (`TA0007`) au lieu du slug interne (`discovery`). Aligne sur le format accepté par PATCH `tactic_ids`. Caught by e2e — fixed in commit `a824df0`. - Spec drift résolu : sprint 3 §0 SPEC update qui avait été oublié au commit est porté ici dans `ba313a3`. - Sprint 5 prévu : templates de simulations pré-remplies (item 8 du QA sprint 3). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
knacky added 15 commits 2026-05-27 19:42:36 +00:00
The sprint 3 plan §0 updated SPEC.md § Simulation to reflect multi-techniques
(plural + autocomplete + matrix modal + sub-techniques). That edit sat in the
sprint 3 worktree but was never committed, so PR #6 merged the multi-tech
code without the corresponding spec text. Applying it here at the start of
sprint 4 so SPEC and main are aligned again.

Lesson captured in tasks/lessons.md for sprint 4 wrap-up: always
git status before declaring sprint complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- tasks/todo.md: sprint 4 plan with 9 user stories (US-17 → US-25), 9 décisions arrêtées
- SPEC.md § Fonctionnement: Done is terminal, Reopen returns to review_required (open to all roles); engagement auto-flips planned → active when any simulation hits in_progress, no auto-rollback
- SPEC.md § Référentiel MITRE: sprint 3 multi-tech + sprint 4 tactic_ids separated field
- SPEC.md § UI/UX (new): theming light/dark/system with system default, button convention (icon + ≤8-char label), modal focus trap V1
- SPEC.md § Workflows: design-reviewer inserted between frontend-builder and code-reviewer; PR via make open-pr

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
US-24 — Process hygiene UI:
- New .claude/agents/design-reviewer.md (model: opus, read-only) — visual + design-system reviewer that runs after frontend-builder and before code-reviewer. Audits alignment, DESIGN.md tokens, light/dark consistency, typo hierarchy, whitespace rhythm, responsive sanity at 1280x720, button convention, V1 a11y. Output format mirrors code-reviewer.
- Updated .claude/agents/frontend-builder.md DoD: screenshots are MANDATORY (one per feature/state introduced or modified, light+dark when theming is in scope). Hard block on "Dev server not started" — must be flagged explicitly. Screenshots feed the design-reviewer step.

US-25 — PR helper:
- scripts/open-pr.sh wraps `POST /api/v1/repos/{owner}/{repo}/pulls`. Detects host/owner/repo from `git remote get-url origin`, reads basic-auth credentials from `~/.git-credentials` (same source as `git push`, no token in env), uses jq to compose the multiline-safe payload. Validates args, prints PR URL on success, exits non-zero with the server message on failure.
- Makefile target `open-pr TITLE="..." BODY=path/to/body.md [BASE=main]` wraps the script with the same arg validation.
- README.md "Make targets" table extended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Simulation model: add tactic_ids JSON column (nullable=False, default=[])
- Migration 0004: ADD COLUMN tactic_ids (server_default='[]', no batch needed)
- mitre.py: add _TACTIC_IDS map, lookup_tactic(), get_tactic_name()
- simulation_workflow.py: done guard (409) before RBAC; SOC gate += tactic_ids;
  _resolve_tactic_ids() validates against hardcoded map; auto-transition += tactic_ids;
  transition done→review_required is Reopen (all 3 roles); _maybe_activate_engagement hook
- serializers.py: _enrich_tactics() → serialize_simulation adds tactics:[{id,name}]
- test_simulations_tactics.py: valid/invalid/dedup/SOC gate/auto-transition/no-bundle
- test_simulations_done_readonly.py: 409 all roles, Reopen all roles, invalid transitions, after-reopen ok
- test_engagement_lifecycle.py: planned→active on auto-transition, already active/closed unchanged, migration 0004 round-trip
- Updated test_simulations_patch.py + test_simulations_workflow.py for AC-18 behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
US-17: fix duplicate "Create engagement" button, icon conventions (Save/RotateCcw/Grid2x2), UsersAdminPage form baseline alignment
US-18: done status fully read-only + Reopen button (done → review_required) for all roles
US-19: invalidate engagement queries on simulation PATCH/transition for auto-status propagation
US-20: MitreMatrixModal rewritten — CSS grid 12-column layout, no horizontal scroll, attack.mitre.org compact look
US-21: tactic header clickable in matrix, tactic chips (MitreTacticTag) in field, single atomic PATCH with technique_ids + tactic_ids
US-22: MitreTechniquesField chips-only area + inline search input + matrix icon button; chips show ID-only (name in title=)
US-23: useTheme hook — 3-state light/dark/system, CSS variables, Tailwind darkMode class, localStorage persistence

92/92 tests passing, typecheck and lint clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add fixed slab/slab-text/slab-muted tokens so utility strip and footer never
  invert to near-white in dark mode (root token split: ink is themed text,
  slab is fixed dark surface)
- btn-ink uses fixed #111827 so confirm dialogs stay dark-on-dark readable
- Toast error surface switched to slab; success uses text-white (not text-ink-on)
- StatusBadge active and SimulationStatusBadge review_required/done use text-white
  instead of text-canvas/text-ink-on (prevents near-black text on colored pill
  in dark mode)
- Modal backdrops (MitreMatrixModal, ConfirmDialog) switched to .modal-backdrop
  class (fixed rgba(0,0,0,0.6)) instead of bg-ink/60 which turned near-white
- Card shadow lifted in dark mode via .dark .card-product override
- MitreMatrixModal panel uses shadow-floating-dark in dark mode
- UsersAdminPage form: items-start + explicit label-height spacer on button
  column for pixel-perfect baseline alignment (AC-17.3 structural fix)

92/92 tests passing, typecheck and lint clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- bump dark hairline from #374151 → #4b5563 for visible table borders
- topbar header bg-canvas → bg-paper for dark-mode lift vs canvas body
- UsersAdminPage create-form: Option A structural 3-row grid (labels / inputs / hints)
  to fix AC-17.3 alignment; removes FormField wrapper that caused row-height misalignment
- EngagementsListPage: replace text "+ New" with lucide Plus icon per design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- btn-ink: replace inline background-color #111827 with @apply bg-slab (and add
  slab-hover token #1f2937 for the hover state) so the token system is consistent
- EngagementsListPage: header button "+ New" → "+ New engagement" to match
  empty-state CTA label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_engagement_lifecycle.py, test_simulations_techniques.py: replace hardcoded
  absolute paths with Path(__file__).parent.parent / migrations/... (portable)
- simulation_workflow.py: remove dead branch in transition() — the IN_PROGRESS
  hook was unreachable since _ALLOWED_TRANSITIONS only targets review_required/done

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- btn-ink hover: bg-slab-hover (unnecessary new token) → bg-paper (existing token,
  same #1f2937 value in dark, avoids token sprawl)
- tailwind.config.ts: remove slab-hover token added in fc530af
- EngagementsListPage: both CTAs unified to "+ New" (≤8 chars convention, AC-17.2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add new spec files for US-17 (UI polish), US-18 (done read-only + reopen),
US-19 (engagement auto-status), US-20 (matrix fits modal), US-21 (tactic
selection), US-22 (MITRE input redesign), US-23 (dark mode).

Adapt sprint 2/3 specs for sprint 4 UI renames: matrix icon button replaces
text buttons, inline search replaces Quick Search, Save replaces Save Red Team,
New replaces New Engagement, topbar uses bg-slab tokens, Apply N item(s) replaces
Apply N technique(s), done→review_required transition now valid (Reopen flow).

Mark AC-21.6 Apply-from-modal as test.fail: known defect where /api/mitre/matrix
returns slug tactic IDs but PATCH /simulations/:id expects TA-format IDs.

Final result: 156 passed, 0 failed (1 expected failure via test.fail).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mitre.py: add _SLUG_TO_TA_ID reverse map; _build_matrix() now emits tactic_id
  as TA-id (e.g. "TA0007") so frontend can send it back verbatim in PATCH tactic_ids
- test_mitre.py: update all matrix assertions to use TA-ids; add
  test_get_matrix_tactic_id_is_ta_format regression guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add two tests omitted from the initial sprint 4 run:
- us21: SimulationList MITRE column shows "TA0007 +2" for 1 tactic + 2 techniques
- us20: MitreMatrixModal Tab wraps to first focusable, Shift+Tab wraps to last

Suite: 158 passed, 0 failed (1 expected test.fail for AC-21.6 slug defect).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove test.fail annotation from AC-21.6 "Apply from modal includes
tactic in result". GET /api/mitre/matrix now returns tactic_id in TA-format
("TA0007") so the PATCH succeeds and the tactic chip appears.

Update button selector in both AC-21.6 tests from title*="discovery"
to title*="TA0007" to match the fixed matrix response format.

Suite: 158 passed, 0 failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CHANGELOG: sprint 4 entry under [Unreleased] (covers all 9 US: dark mode, MITRE matrix overhaul, tactic_ids, done read-only + Reopen, engagement auto-status, UI polish, design-reviewer agent, PR helper, screenshots mandatory). Sprint 3 moved to its own [Sprint 3] section.
- README: status bump, test counts refreshed (193/92/158).
- tasks/lessons.md: 7 sprint-4 lessons captured (git status before sprint close, endpoint round-trip mismatch caught only by e2e, ink vs slab token split, structural row layout > class tweaks, hardcoded paths in migration tests, screenshots with auth, builder cross-context summaries as accidental re-dispatch).
- tasks/todo.md: status flipped to 🟢 SPRINT COMPLET, execution sequence ticks updated with commit hashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
knacky merged commit 9873c535c6 into main 2026-05-28 04:01:21 +00:00
knacky deleted branch sprint/4-ui-polish 2026-05-28 04:01:21 +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#7