Commit Graph

8 Commits

Author SHA1 Message Date
ux-frontend
6aa0078fd3 feat(frontend): wire session to real /auth/me + drop sessionStorage mock
Foundations for the sprint 1 backend wiring. No UI behavior change beyond
the loading state in AppShell, but everything below the wire is now real:

- vite.config.ts adds `server.proxy['/api']` → http://localhost:5000
  (overridable via VITE_DEV_API_TARGET). In prod Caddy routes /api → backend
  on the same origin, so the same `/api/v1/...` paths work without changes.
- src/types/api.ts hand-rolled against the backend Pydantic schemas.
  User / Engagement / EngagementCreate / Login / ApiError / ApiValidationError.
  Should be regenerated from OpenAPI once backend exposes it.
- src/lib/api.ts: thin fetch wrapper. Always credentials:'include' so the
  HttpOnly session cookie travels. 4xx/5xx normalize into ApiClientError
  with typed `body` (ApiError | ApiValidationError | null). No retry loop —
  that's TanStack Query's policy.
- src/session/sessionApi.ts: 1:1 functions for /auth/me, /auth/login,
  /auth/logout. fetchMe maps 401 → null so "unauthenticated" is data, not
  an error.
- src/session/useSession.ts: now a TanStack Query hook against
  SESSION_QUERY_KEY (`['session']`). Returns { user, isLoading, isError,
  signOut, isSigningOut }. Cookie is the source of truth, server is the
  resolver, query is the cache.
- Drop sessionStorage mock layer entirely: src/mocks/session.ts,
  src/session/SessionContext.{tsx,context.ts}, src/routing/Root.tsx all
  removed. No more provider tree — QueryClientProvider in App.tsx is the
  only global state container.
- AppShell renders a "resolving session" state during /auth/me's first
  flight so users with a valid cookie don't see a /login flash on direct
  navigation to a protected URL.
- StatusRail gains an optional `sessionState="resolving"` slot used by
  the loading shell.
- Sidebar's Sign-out wires POST /auth/logout, invalidates the session
  cache, and always navigates to /login regardless of the call outcome
  (a failed logout still expires the local cache so users aren't stuck
  on a broken cookie).
- types/roles.ts loses SessionUser (replaced by api.ts User which is the
  authoritative shape).
2026-05-23 04:26:28 +02:00
knacky
649194b174 chore(frontend): add multi-stage Dockerfile + nginx SPA config
Some checks failed
ci / backend (lint + typecheck + unit tests) (push) Failing after 1s
ci / frontend (lint + typecheck + build + unit tests) (push) Failing after 0s
Production image for the frontend dist.

Stage 1 (build): node:22-alpine, `npm ci --ignore-scripts` from the
committed lockfile, `npm run build`. Output lands in /app/dist.

Stage 2 (runtime): docker.io/nginxinc/nginx-unprivileged:alpine.
- Upstream-maintained variant that runs as the nginx user (uid 101)
  out of the box. /var/cache/nginx and /var/run/nginx are pre-owned,
  no chown gymnastics needed in our layer. Vanilla nginx:alpine fails
  at startup as non-root because client_temp mkdir is denied.
- Listens on 8080 (non-privileged port, matches the unprivileged
  variant convention).
- nginx.conf serves /usr/share/nginx/html with SPA `try_files`
  fallback for client-side routing, long-cache headers on
  /assets/ (Vite hashed bundles), a plaintext /healthz endpoint
  for Caddy / Prometheus blackbox, and server_tokens off.

.dockerignore excludes node_modules, dist, .vite, coverage,
playwright-report, .env*, .git, editor dirs. Keeps .env.example.

Smoke local validated with `podman build -t mimic-frontend:smoke .`
and `podman run -p 127.0.0.1:18080:8080`:
  /healthz -> 200 "ok"
  /        -> 200 index.html (508 B)
  /spa/x   -> 200 (SPA fallback)
  /assets  -> Cache-Control: max-age=31536000, public, immutable
2026-05-22 19:59:09 +02:00
ux-frontend
b505a654f8 fix(frontend): address M1-M3 polish from code-reviewer
M1 — Single SessionProvider via nested router.
  The previous router had two route entries with `path: '/'`
  (Navigate, AppShell) plus a separate `/login` entry, each wrapped in
  its own RootLayout. That instantiated SessionProvider three times,
  forking state the moment session writes diverged across siblings.
  Replaced by one Root route with SessionProvider + <Outlet />, and
  index/login/AppShell-children nested underneath. RootLayout (the
  per-tree wrapper) is now obsolete and deleted; the new Root component
  lives in src/routing/Root.tsx (addresses NIT N4 as a side effect).

M2 — Typo: "pollign" → "polling" in LiveCockpitPage masthead.

M3 — Replace asymmetric `?? 'rt_operator'` / `?? 'soc_analyst'`
  fallbacks in LiveCockpitPage with an explicit `if (!user) return null;`
  guard placed after all hooks (rules-of-hooks). AppShell already
  redirects unauthenticated visitors to /login, so the guard documents
  the invariant rather than introducing one.

NITs N1-N3, N5-N7 recorded in tasks/todo.md as sprint 1+ follow-ups.
2026-05-21 20:44:32 +02:00
ux-frontend
12bc33469c feat(frontend): wireframes for 5 MVP screens + audit (F0.3)
Mock-data wireframes covering spec §5 / §9 surface area. All read from
src/mocks/fixtures.ts — no backend wiring yet. Each screen is built from
the design-system primitives (Panel, Pill, Button, label-system, status-dot)
and adheres to the instrumentation-grade visual grammar.

Screens:
- /login              LoginPage — RT vs SOC mode switch (segmented), role-tinted.
                       RT form picks rt_operator / rt_lead at sign-in (mock only).
                       SOC form takes a session token (out-of-band, D-006).
                       Left rail carries mission brief + platform telemetry.
- /engagements        EngagementsPage — mission roster table (codename, client,
                       status, c2_type, operators, SOC count, window).
- /runs               LiveCockpitPage — the cornerstone screen. 3-column layout:
                       steps timeline | step detail (resolved command,
                       output, evidence, detection) | side rail
                       (DetectionPanel for SOC; EvidencePanel +
                       DetectionPanel readonly + CleanupPanel for RT).
                       Control bar (F6 pause/skip/retry/abort) is lead-RT-only.
                       Stats header: steps done, detected/partial/missed counts.
- /scenarios          ScenarioComposerPage — 3-column composer:
                       filterable TTP library | ordered steps with delays
                       | inspector (params from params_schema_json, target
                       host list, jinja2 cleanup template preview).
                       c2_type locked at scenario level (D-F3 / H33).
- /library            TtpLibraryPage — catalog table with stealth-variant
                       flagging, source provenance (custom/atr/mission),
                       payload_type chip, tags. Import journal / ATR buttons.
- /reports            ReportPage — restricted MITRE matrix (techniques
                       played only, H29), narration timeline, integrity
                       hash footer (SHA-256, H19/H24/F9). PDF/JSON/MD
                       export buttons.
- /audit              AuditPage — append-only journal viewer (lead RT only,
                       F13). Tabular timestamp/actor/role/action/resource.

UX guardrails baked in:
- SOC analysts never see RT-only controls (conditional rendering, not just
  disabled state). UI layer mirrors backend RBAC but does not replace it.
- Layout density and dark-first palette tuned for long purple sessions
  (sober contrast, no flash, status colors carry information without
  being shouted).
- Live cockpit reserves a clear visual slot for cleanup-failed alerts
  (R-T5) — currently a Pill, real alert UX lands when the WebSocket is
  wired in sprint 1+.
2026-05-21 20:31:24 +02:00
ux-frontend
ef081c8c28 feat(frontend): role-aware app shell + routing skeleton (F0.4)
- Role enum (rt_operator, rt_lead, soc_analyst) aligned with spec §3 / F11.
  Frontend predicates (isRT, isLead, isSOC) drive layout only — backend
  remains the source of truth for permissions (D-008).
- SessionContext split into Provider (TSX) and hook (useSession) to satisfy
  react-refresh/only-export-components.
- AppShell composes StatusRail (link health, active run, UTC clock, build) +
  Sidebar (role-conditional nav with keyboard shortcut hints) + Outlet.
  Unauthenticated visitors redirect to /login.
- StatusRail uses pulsing status-dot pattern and label-system micro-typo
  (uppercase 10px, 0.08em tracking) to evoke an instrument-panel header.
- Router (createBrowserRouter): /login outside the shell, all app routes
  nested inside the shell. RootLayout extracted to its own file for
  fast-refresh compliance.

Routes (sprint 0, flat):
  /login                 LoginPage
  /engagements           EngagementsPage
  /library               TtpLibraryPage (RT only — gated client-side, will
                                          be re-enforced by backend RBAC)
  /scenarios             ScenarioComposerPage (RT only)
  /runs                  LiveCockpitPage
  /reports               ReportPage
  /audit                 AuditPage (lead RT only)

Sub-routes under /engagements/:eid land in sprint 1+ when real scoping
arrives.
2026-05-21 20:31:01 +02:00
ux-frontend
1562478a54 feat(frontend): provisional design system tokens + Logo placeholder (F0.2)
Aesthetic direction: instrumentation-grade telemetry (mission-control / SDR ops),
NOT shadcn defaults, NOT generic AI/SaaS. Mature palette: graphite surface scale,
CRT-amber for RT accent, steel-blue for SOC accent, sage/ochre/rust for detection
status — no neon, no rainbow.

Token layout designed to host the PR3 graphic charter without component churn:
  1. Primitives (--mc-*)        raw OKLCH scales
  2. Semantics (--accent-*, --status-*, --state-*, --surface-*, --line-*, …)
  3. Tailwind @theme mapping    semantic tokens → utilities

Includes:
- src/styles/theme.css       full token surface + base reset + scrollbars + grain
- src/styles/fonts.css       IBM Plex @font-face (self-host only)
- src/styles/globals.css     entry CSS file
- Logo (full/compact/mark) with corner-mark composition
- Panel, Pill, Button primitives reading exclusively from semantic tokens
- Logo.test.tsx (3 cases, Vitest + Testing Library)
2026-05-21 20:30:41 +02:00
ux-frontend
80ca4641a3 feat(frontend): bootstrap Vite + React 19 + TS strict toolchain (F0.1)
- Vite 8 / React 19 / TS 6 strict (noUncheckedIndexedAccess, no baseUrl deprecation)
- Tailwind 4 via @tailwindcss/vite (no PostCSS step)
- TanStack Query 5, react-router-dom 7, Recharts, clsx
- Vitest + Testing Library + jsdom for unit tests
- Playwright skeleton + first smoke spec (login redirect)
- ESLint flat config: typescript-eslint type-checked, react-hooks, react-refresh, prettier
- Prettier config (semi, single quotes, 100-col, lf)
- IBM Plex font @font-face declarations targeting /fonts/ (self-host, no CDN — OPSEC)
2026-05-21 20:30:23 +02:00
knacky
047583eb9c chore: bootstrap repo skeleton with sprint 0 plan
- .gitignore (Python, Node, RT/maldev hygiene, secrets)
- README.md (project framing, stack, conventions, status)
- CHANGELOG.md (team kickoff decisions Q1/Q2/Q3, T2/T3/T4, auth strategy)
- tasks/spec-decisions.md (D-001..D-007 arbitrations on top of frozen spec)
- tasks/todo.md (sprint 0 backlog: B0.* / F0.* / S0.* / R0.*)
- tasks/lessons.md (empty, populated as work progresses)
- backend/ frontend/ docs/ scaffolding

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:10:47 +02:00