From 5030f4bd8345ce939d1abee5fdffbdfd933c7d2e Mon Sep 17 00:00:00 2001 From: Knacky Date: Fri, 15 May 2026 09:51:23 +0200 Subject: [PATCH] docs(m7): backfill changelog + testing-m7 for the two post-merge UX fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback flagged that the doc didn't reflect the two hotfixes shipped after the M7 PR: - evidence whitelist surfaced in the dropzone + OS picker pre-filter - executed_at override fixed in non-UTC timezones (no more time-snap) Added a CHANGELOG entry per fix and a §3.5 in tasks/testing-m7.md walking through the timezone semantics of the datetime-local input. spec.md is left untouched — these are UX/implementation fixes, not contract changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 12 ++++++++++++ tasks/testing-m7.md | 22 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fae7d2e..60c8b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented here. Format: [Keep a Cha ## [Unreleased] +### Fixed (post-M7 UX feedback — evidence whitelist visibility) +- **Evidence dropzone didn't tell the operator which extensions are accepted, and the OS file picker showed "All files"** (`frontend/src/pages/MissionTestPage.tsx`): an operator could spend the time picking a `.exe` only to receive a 400 back. Surfaced the whitelist in the UI: + - Dropzone now prints `Accepted: .png · .jpg · .jpeg · .pdf · .txt · .log · .json · .csv · .evtx · .zip · max 25 MB / file` (testid `evidence-allowed-formats`). + - `` pre-filters the OS picker to those extensions. + - `handleFiles` rejects drag-and-drops of unsupported extensions client-side (still re-checked server-side — defence in depth, not a security boundary). +- Constants `EVIDENCE_ALLOWED_EXTENSIONS` + `EVIDENCE_MAX_BYTES` in `frontend/src/lib/missions.ts` keep a single source of truth client-side. Manual mirror of `app/services/evidence.py:ALLOWED_EXTS` + `MAX_BYTES`; cross-referenced via comments so the next bump touches both files. + +### Fixed (post-M7 UX feedback — executed_at override editable in any timezone) +- **Time portion of the `executed_at` override was un-editable in non-UTC timezones** (`frontend/src/pages/MissionTestPage.tsx:RedZone`): the naive `new Date(executedAt).toISOString().slice(0, 16)` round-trip on every keystroke silently shifted the hour by the local TZ offset, snapping the time field back to UTC each render. The date could be changed (offset shifts both source and target by the same amount), but the hour couldn't stick. +- Fix: keep the local state in `YYYY-MM-DDTHH:MM` form (`executedAtLocal`) and only convert to/from UTC ISO at the boundaries — initial sync from server (`isoToLocalInputValue`) and submit (`localInputValueToIso`). +- Also tightened the `useEffect` reset on both Red and Blue zones to depend on `test.id` instead of the whole `test` object so a polling refetch (every 15 s) no longer wipes an in-progress edit. The 15 s activity poll returns a fresh object reference even when the row's content is unchanged. + ### Fixed (post-M7 review pass — spec-reviewer + code-reviewer) - **Idempotent transition leaked false success to a wrong-side user** (`backend/app/services/mission_tests.py:570`): a blue-only viewer POSTing `target_state="executed"` while the test was already executed got a 200 idempotent response, falsely advertising that they held `mission.write_red_fields`. Reordered the gate so the side-perm check runs *before* the idempotency short-circuit, with a new `_IDEMPOTENT_SIDE` table that asks "which side originally produced this state?" — re-asserting that perm even on no-op replays. Test `test_idempotent_transition_still_checks_side_perm`. - **Cross-mission evidence access not pinned by a test** (`backend/tests/test_mission_tests.py:test_evidence_member_of_other_mission_gets_404`): added explicit coverage that a user who is a blue member of mission B sees 404 on an evidence row attached to mission A. The chain walk in `_resolve_evidence_chain` already enforced this, but the regression test was missing. diff --git a/tasks/testing-m7.md b/tasks/testing-m7.md index cca113e..0f022db 100644 --- a/tasks/testing-m7.md +++ b/tasks/testing-m7.md @@ -75,11 +75,29 @@ Rapport HTML : `e2e/playwright-report/`. - Bouton **Save blue fields** (analogue à la zone red). - **Evidence dropzone** : - Drag & drop ou bouton **Pick files** (multi-fichiers). - - Limite côté client à 25 MB/file (garde-fou UX), refus serveur stricte - à 25 MB. + - Sous la dropzone, la liste des extensions acceptées est affichée : + `Accepted: .png · .jpg · .jpeg · .pdf · .txt · .log · .json · .csv · .evtx · .zip · max 25 MB / file` + (testid `evidence-allowed-formats`). + - Le sélecteur OS est pré-filtré via l'attribut `accept` — pas de "All files". + - Un drag&drop d'une extension hors-liste est rejeté côté client avec + le message d'erreur en rouge ; le serveur re-vérifie quoi qu'il + arrive (`unsupported_extension` / `too_large`). + - Limite : **25 MB / fichier** (côté client = garde-fou UX, côté serveur = + stricte avec stream cap chunk-par-chunk). - Table récap : nom · taille · uploader · `sha256[:12]…` · link download + bouton soft-delete. +### 3.5 Override executed-at — pièges timezone +- Le toggle **Override executed-at timestamp** affiche un input + `datetime-local` qui parle en **heure locale du navigateur** (ex. + Europe/Paris UTC+2). L'état local du composant garde la valeur sous + forme `YYYY-MM-DDTHH:MM` ; la conversion en UTC ISO n'a lieu qu'au + submit. Donc si tu tapes `10:30` à Paris, le serveur reçoit + `08:30:00+00:00` — c'est attendu. +- Le polling activity (15 s) ne re-sync l'état local **que** sur changement + d'identité du test (`useEffect([test.id])`) — une frappe en cours n'est + jamais écrasée par un refetch. + ### 3.4 Indicateur d'activité - À l'arrivée sur la page, le polling `GET /missions//activity` démarre