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

15 Commits

Author SHA1 Message Date
Knacky
6d2bb091e2 docs: sprint 4 wrap-up — CHANGELOG + README + 7 lessons + plan final
- 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>
2026-05-27 21:41:47 +02:00
Knacky
43ab7073f1 test(e2e): un-skip AC-21.6 — backend matrix fix landed
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>
2026-05-27 21:38:17 +02:00
Knacky
7d81ce9785 test(e2e): fill coverage gaps — +N suffix + focus-trap cycle
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>
2026-05-27 21:33:18 +02:00
Knacky
a824df06b2 fix(backend): AC-21.6 — matrix tactic_id returns TA-format (TA0007 not slug)
- 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>
2026-05-27 21:30:48 +02:00
Knacky
5aa839d105 test(e2e): sprint 4 acceptance tests — US-17 to US-23
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>
2026-05-27 21:27:12 +02:00
Knacky
e99286ef8e fix(frontend): sprint 4 post-code-review — btn-ink uses slab token + unify New CTA label
- 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>
2026-05-27 20:40:06 +02:00
Knacky
988de841e5 fix(backend): sprint 4 post-review — relative paths + dead branch removal
- 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>
2026-05-27 20:39:37 +02:00
Knacky
fc530af78b fix(frontend): post-code-review NITs — btn-ink uses @apply bg-slab, unify New engagement label
- 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>
2026-05-27 20:39:01 +02:00
Knacky
9964d058f4 fix(frontend): sprint 4 design-review — slab token + UsersAdmin alignment + dark hairlines + badge contrast
- 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>
2026-05-27 20:28:32 +02:00
Knacky
892692f3b8 fix(frontend): post-design-review — slab token split + badge contrast + modal backdrop + dark shadows
- 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>
2026-05-27 20:19:16 +02:00
Knacky
f5ea9d16af feat(frontend): sprint 4 — dark mode + matrix overhaul + tactic selection + done read-only + UI polish
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>
2026-05-27 20:06:01 +02:00
Knacky
d5ab1fd26f feat(backend): sprint 4 — tactic_ids + done guard + engagement auto-status
- 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>
2026-05-27 19:52:02 +02:00
Knacky
0f6ae857b3 feat(infra): design-reviewer agent + PR helper (US-24 + US-25)
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>
2026-05-27 19:41:34 +02:00
Knacky
89eccad1eb docs(sprint-4): plan + SPEC updates (Done terminal, engagement auto, UI/UX, workflows)
- 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>
2026-05-27 19:41:16 +02:00
Knacky
ba313a3880 docs(spec): carry over sprint 3 SPEC update missed in PR #6
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>
2026-05-27 19:14:25 +02:00