Mirrors the backend Pydantic `SyncResultOut` in TS so the mutation result is
properly typed end-to-end. `(res as { duration_ms: number })` cast removed
from MitrePage.tsx; `apiPost<MitreSyncResult>` carries the contract.
Also annotated the unused query-key factories in mitre.ts so the next reader
knows they're parked for M5 template-form consumption (not dead).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hierarchical 3-column drill-down was hard to scan and forced a stateful
walk per tag. Replaced with a flat, columns-as-tactics matrix that mirrors
attack.mitre.org/# — every cell is a one-click select target, with inline
sub-technique expand via a `+N` chevron.
- New endpoint GET /api/v1/mitre/matrix returns the full grid (tactics →
techniques → sub-techniques nested) in a single ~55 KB response, so the
SPA renders the whole matrix without firing 15 parallel queries. Two
pytest tests added (nested structure + auth required).
- MitreTagPicker.tsx rewritten as a horizontal-scrolling matrix:
- Click a tactic header → select the tactic (cyan filled).
- Click a technique cell → select the technique (orange filled).
- Click the `+N` chevron → expand sub-techniques inline within the column.
- Click a sub-technique → select (purple filled).
- Single Filter field matches on external_id or name across all kinds.
- Selection chips at the top, clickable to remove.
- `aria-pressed` on every clickable cell for screen readers and Playwright.
- e2e test updated to walk the new flow (click cell → assert aria-pressed,
expand chevron, click sub, verify chip + JSON preview, filter to T1078).
- Spec §F2 + §F12 + todo.md M4 entry updated to make the matrix layout the
canonical UI for MITRE tagging (so future spec-reviewer passes accept it).
- testing-m4.md walkthrough rewritten for the flat picker.
DoD post-refactor: make test-api → 53 passed (was 51), make e2e → 34 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- lib/mitre.ts: shared types (MitreTactic, Technique, Subtechnique, MitreTag
kind/id/external_id/name) + TanStack query keys.
- components/MitreTagPicker.tsx: three-column controlled picker (tactic →
technique → subtechnique), multi-select with chip-removal, autocomplete on
each column, ARIA labels for screen readers. Returns MitreTag[] via
value/onChange — drop-in for M5 template forms.
- pages/MitrePage.tsx: status card (version, source URL, last_sync), admin-
gated Trigger Sync button with success/error alerts, picker showcase, JSON
preview of the current selection.
- Layout adds MITRE nav link for any logged-in user; App.tsx adds the
/mitre route under RequireAuth. HomePage roadmap bumped to next: M5
templates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>