LoginPage
- RT mode now POSTs /api/v1/auth/login with controlled username/password
fields. Success seeds the session cache via queryClient.setQueryData and
navigates to /engagements. 401 surfaces as the generic
"Identifiants invalides" — no echo of the backend detail (avoids
user enumeration leaks).
- SOC mode kept visually for masthead continuity but disabled with a
"sprint 2" placeholder pointing at the deferred
POST /api/v1/auth/soc/session endpoint.
- Removed the sprint-0 mock role-picker.
EngagementsPage
- MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles
both bare-array and { items: [] } envelope shapes — backend has not
pinned this yet).
- Distinct loading / empty / error states. Error row surfaces an HTTP
code and a Retry button. Empty state offers the create dialog.
- Column shape aligned with the real Engagement schema (snake_case:
name, client_name, c2_type, start_date, end_date). Dropped mock-only
columns (operators, socAnalysts) — those land when backend exposes
/engagements/:id/members and /engagements/:id/soc-sessions counts.
engagementsApi.ts
- fetchEngagements + createEngagement, both bound to /api/v1/engagements.
- ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without
re-knowing the key.
EngagementCreateDialog (frontend-design skill — new non-trivial component)
- "Arm engagement" mission-control dialog. Backdrop is a graphite dim
with a faint scanline overlay (no soft blur) — reads as "cockpit
paused while you issue a command", not as a SaaS modal.
- Surface --surface-3 with corner-marks and an amber hairline accent
under the title; underline-style inputs that light amber on focus;
label-system uppercase microtypography throughout.
- Esc + outside-click close (suspended while the mutation is in flight).
- Rudimentary tab focus trap.
- 422 Pydantic errors map per-field via the last loc segment;
401/5xx surface as a generic top-of-form alert.
- On 201 invalidates ['engagements'] and closes.