# Batch Report **Batch**: 05 **Tasks**: AZ-461 (Detection endpoints sync/async/long-video), AZ-464 (Bulk-validate URL/body/UI sync), AZ-470 (Panel-width debounced PUT + rehydration), AZ-472 (DetectionClasses load + hotkeys + click + fallback) **Date**: 2026-05-11 **Cycle**: Phase A baseline, Step 6 — Implement Tests **Total complexity**: 9 pts (2 + 2 + 2 + 3) ## Task Results | Task | Status | Files Modified | Tests | AC Coverage | Issues | |------|--------|---------------|-------|-------------|--------| | AZ-461_test_detection_endpoints | Done | 1 created (`tests/detection_endpoints.test.tsx`); 1 e2e created (`e2e/tests/detection_endpoints.e2e.ts`) | 4 fast (2 pass + 2 `it.fails()` per spec QUARANTINE / drift, 2 controls); 2 e2e (1 PASS + 1 `test.fail`) | 3 / 3 ACs covered | 2 documented drifts: production POSTs single-endpoint `/api/detect/` regardless of mediaType (no async-video route — AC-25 lifts QUARANTINE); `api.post` sets only Authorization header (no `X-Refresh-Token` — Phase B wires it) | | AZ-464_test_bulk_validate | Done | 1 created (`tests/bulk_validate.test.tsx`); 1 e2e created (`e2e/tests/bulk_validate.e2e.ts`) | 3 fast (2 pass + 1 `it.fails()` for body-shape drift + 1 control); 3 e2e (2 PASS + 1 `test.fail`) | 3 / 3 ACs covered | 1 documented drift: production sends `{annotationIds, status: AnnotationStatus.Validated (=2)}` instead of contract `{ids, targetStatus: 30}` (flips with AC-04 wire enum scheme) | | AZ-470_test_panel_width_persistence | Done | 1 created (`tests/panel_width_persistence.test.tsx`); 1 e2e created (`e2e/tests/panel_width_persistence.e2e.ts`) | 5 fast (3 `it.fails()` + 2 controls — every AC is `it.fails()` per spec note); 1 e2e (`test.fail`) | 3 / 3 ACs covered | 1 systemic drift: `useResizablePanel` hook holds local state only — no PUT to `/api/annotations/settings/user` on resize-end, no rehydration of seeded `panelWidths` on reload (entire task is Phase-B-target) | | AZ-472_test_detection_classes | Done | 1 created (`tests/detection_classes.test.tsx`); 1 e2e created (`e2e/tests/detection_classes.e2e.ts`) | 7 fast (5 pass + 2 `it.fails()` for hotkey drift); 1 e2e (PASS) | 4 / 4 ACs covered | 1 documented drift: production hotkey logic uses `classes[idx + photoMode]` against a dense array — yields wrong class for P=20 and out-of-range for P=40 (flips with filter-then-index OR sparse length-60 array). P=0 PASS (coincidentally) | ## AC Test Coverage: All covered (13 / 13 ACs across the four tasks) ### AZ-461 — Detection endpoints (3 ACs, 6 scenarios) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | AC-1 / FT-P-11 (sync image detect URL) | `tests/detection_endpoints.test.tsx` + `e2e/tests/detection_endpoints.e2e.ts` | fast + e2e | PASS — production POSTs `/api/detect/` matching the contract regex | | AC-2 / FT-P-12 (async video detect endpoint + SSE — QUARANTINE) | `tests/detection_endpoints.test.tsx` | fast | `it.fails()` — runs end-to-end, emits "FT-P-12 awaits AC-25 / async video detect impl" log per spec | | AC-2 / control: production POSTs `/api/detect/` regardless of mediaType (drift pin) | same | fast | PASS — pins single-endpoint drift | | AC-3 / FT-P-13 (long-video detect carries `X-Refresh-Token`) | `tests/detection_endpoints.test.tsx` + `e2e/tests/detection_endpoints.e2e.ts` | fast + e2e | `it.fails()` (fast) + `test.fail` (e2e) — production sets only Authorization | | AC-3 / control: production sets only `Authorization` on detect (current behavior) | `tests/detection_endpoints.test.tsx` | fast | PASS — proves spy machinery + Authorization presence | **AC summary**: - AC-1 sync URL canary → PASS today (numeric media id satisfies `^/api/detect/[0-9]+$`). - AC-2 async video / SSE → `it.fails()` + control + log per QUARANTINE rule. - AC-3 X-Refresh-Token header → `it.fails()` + control pinning Authorization-only drift. ### AZ-464 — Bulk-validate (3 ACs, 4 scenarios) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | AC-1 / FT-P-20 URL canary | `tests/bulk_validate.test.tsx` + `e2e/tests/bulk_validate.e2e.ts` | fast + e2e | PASS — production POSTs `/api/annotations/dataset/bulk-status` | | AC-2 / FT-P-20 body shape `{ids, targetStatus: 30}` | same | fast + e2e | `it.fails()` (fast) + `test.fail` (e2e) | | AC-2 / control: body is `{annotationIds, status: AnnotationStatus.Validated}` (current shape) | `tests/bulk_validate.test.tsx` | fast | PASS — pins field-name + status-value drift | | AC-3 / FT-P-21 + NFT-PERF-07 (UI sync ≤ 2 000 ms) | `tests/bulk_validate.test.tsx` + `e2e/tests/bulk_validate.e2e.ts` | fast + e2e | PASS — wall-clock from click to all rows showing Validated badge ≤ 2 s | **AC summary**: - AC-1 URL canary → PASS. - AC-2 body shape → `it.fails()` + control proving production's drift shape (both field names AND status value differ from contract). - AC-3 UI sync → PASS within 2 s (production calls `fetchItems()` after the 200 returns). ### AZ-470 — Panel-width debounced PUT + rehydration (3 ACs, 5 scenarios) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | AC-1 / FT-P-37 + NFT-PERF-08 (debounce window) | `tests/panel_width_persistence.test.tsx` | fast | `it.fails()` — production never PUTs | | AC-1 / control: production emits ZERO PUTs during a resize today | same | fast | PASS — pins no-writer drift | | AC-2 / FT-P-37 (PUT body carries `panelWidths`) | same | fast | `it.fails()` — depends on AC-1 writer landing | | AC-3 / FT-P-38 (rehydration on reload) | same + `e2e/tests/panel_width_persistence.e2e.ts` | fast + e2e | `it.fails()` (fast) + `test.fail` (e2e) — no rehydration effect | | AC-3 / control: production renders panels at constructor defaults (250 / 200) ignoring seeded settings | `tests/panel_width_persistence.test.tsx` | fast | PASS — pins drift | **AC summary**: - Entire AZ-470 is a Phase-B-target group per task spec (`useResizablePanel` has no settings writer / reader today). - Every AC is `it.fails()`; controls pin the current no-writer + constructor-default behavior. - Tests flip green automatically once `useResizablePanel` is wired to `` save/load. ### AZ-472 — DetectionClasses (4 ACs, 8 scenarios) | Scenario | Where | Profile | Status | |----------|-------|---------|--------| | AC-1 / FT-P-44 (load contract) | `tests/detection_classes.test.tsx` + `e2e/tests/detection_classes.e2e.ts` | fast + e2e | PASS — GET `/api/annotations/classes` observed at mount; 9 entries rendered for P=0 | | AC-2 / FT-P-45 P=0 (keys 1..9 → ids 0..8) | `tests/detection_classes.test.tsx` | fast | PASS — coincidentally aligns since offset is 0 | | AC-2 / FT-P-45 P=20 (keys 1..9 → ids 20..28) | same | fast | `it.fails()` — production's `classes[idx + 20]` lands in the 40s window against the dense length-27 array | | AC-2 / FT-P-45 P=40 (keys 1..9 → ids 40..48) | same | fast | `it.fails()` — `classes[idx + 40]` exceeds array length; `cls` is undefined | | AC-3 / FT-P-46 (click path) | same | fast | PASS — `userEvent.click` fires `onSelect(c.id)` | | AC-4 / FT-P-47 fallback on `[]` | same | fast | PASS — `FALLBACK_CLASS_NAMES` rendered when API returns empty | | AC-4 / FT-P-47 fallback on 500 | same | fast | PASS — `FALLBACK_CLASS_NAMES` rendered on server error | | AC-4 / fallback id set equals `[0..N-1, 20..20+N-1, 40..40+N-1]` | same | fast | PASS — pins fallback contract for downstream AZ-473 dependants | **AC summary**: - AC-1 load → PASS at mount. - AC-2 hotkey arithmetic → P=0 PASS, P=20 + P=40 `it.fails()` for documented production drift. - AC-3 click → PASS. - AC-4 fallback → 3 scenarios PASS (empty, 500, id-set). ## Code Review Verdict: PASS See `_docs/03_implementation/reviews/batch_05_review.md` for the full 7-phase walkthrough. - 0 Critical, 0 High, 0 Medium, 0 Low findings. - All `it.fails()` placements anchored to either explicit task-spec QUARANTINE direction (AZ-461 AC-2) or documented production drift with control test pinning the current shape. - Architecture compliance (Phase 7): no layer-direction violations; tests are leaves of the import graph; no new cyclic dependencies; static profile (STC-S6, STC-S13) re-confirms. ## Auto-Fix Attempts: 0 PASS verdict — no auto-fix loop entered. ## Stuck Agents: None Each task implemented in a single sequential pass. No file rewritten 3+ times; no approach pivots. ## Test Run Summary - `bun run test:fast` — 18 files / 102 passed / 13 skipped / 7.31 s. - `./scripts/run-tests.sh --static-only` — all 21 static checks PASS / 17.95 s. - `ReadLints` — clean on all 8 changed files. ## Documented Drifts (cumulative across batch) | Drift | Where | Spec/AC affected | Resolves when | |-------|-------|------------------|---------------| | Single-endpoint detect (no `/api/detect/video/...`) | `src/features/annotations/AnnotationsSidebar.tsx` (Detect button handler) | AZ-461 AC-2 | AC-25 (Phase B async-video path) | | `X-Refresh-Token` header absent on detect | `src/api/client.ts` request fn | AZ-461 AC-3 | Phase B (header wiring per Step 4 / F7) | | Bulk-validate body shape `{annotationIds, status}` vs contract `{ids, targetStatus}` | `src/features/dataset/DatasetPage.tsx` | AZ-464 AC-2 | AC-04 wire enum scheme | | Status value `AnnotationStatus.Validated` (=2) vs contract 30 | same | AZ-464 AC-2 | AC-04 wire enum scheme | | `useResizablePanel` has no PUT writer | `src/hooks/useResizablePanel.ts` | AZ-470 AC-1 + AC-2 | Phase B (debounced settings writer) | | `useResizablePanel` has no rehydration reader | same | AZ-470 AC-3 | Phase B (reads `panelWidths` from settings on mount) | | Hotkey index formula `classes[idx + P]` against dense array | `src/components/DetectionClasses.tsx` (keydown handler) | AZ-472 AC-2 (P=20, P=40) | Either filter-then-index switch OR sparse length-60 fixture | ## Next Batch: AZ-454, AZ-456 epics likely complete after this batch — 14 → 10 tasks remaining in `todo/`. Cumulative review (batches 04–06) triggers after the next batch per Step 14.5 (K=3 cadence).