Reported by the user: the blue-side dropzone said "≤ 25 MB each" but nowhere did it list the accepted extensions, and the OS file picker showed "All files" — so an operator could spend the time picking a `.exe` only to get a 400 back. - New constants `EVIDENCE_ALLOWED_EXTENSIONS` + `EVIDENCE_MAX_BYTES` in `lib/missions.ts`. Manual mirror of the backend whitelist (commented cross-reference). One source of truth on the client. - Dropzone now prints `Accepted: .png · .jpg · .jpeg · .pdf · .txt · .log · .json · .csv · .evtx · .zip · max 25 MB / file` (testid `evidence-allowed-formats`). - File input gains `accept=".png,.jpg,..."` so the OS picker pre-filters to those extensions instead of "All files". - `handleFiles` rejects drag-and-drops of unsupported extensions on the client too (still re-checked server-side — defence in depth, not a security boundary). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Metamorph frontend
Vite + React 18 + TypeScript + TailwindCSS. Design tokens from ../tasks/design.md are in tailwind.config.ts.
Local dev
npm install
npm run dev # http://localhost:5173 (proxies /api/* to http://localhost:8000)
Build
npm run build # outputs to dist/
npm run preview # serves dist/ on http://localhost:8080
Quality
npm run typecheck
npm run lint
npm run format
Layout
src/
├── App.tsx # M0 home page (health check + design tokens demo)
├── main.tsx
├── index.css # Tailwind base + tinted-accent utilities
├── components/ui/ # RTOps design primitives: Card, Tag, SectionHeader, FlowNode, Button
├── lib/
│ ├── api.ts # fetch wrapper (M2 will replace with auth-aware client)
│ └── cn.ts # classnames + ACCENTS palette
└── vite-env.d.ts