Files
ui/_docs/03_implementation/batch_07_report.md
T
Oleksandr Bezdieniezhnykh cdebfccada
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-471] [AZ-473] [AZ-478] [AZ-479] Batch 7 - canvas/photo-mode/network/perf tests
- AZ-471 CanvasEditor draw + 8-handle resize PASS (FT-P-39 fast +
  e2e + FT-P-40 8 sub-tests). Three drifts pinned via it.fails():
  Ctrl+click multi-select (FT-P-41), Ctrl+wheel zoom-around-cursor
  (FT-P-42), Ctrl+drag empty-canvas pan (FT-P-43) — all rooted in
  handleMouseDown's early Ctrl-gate and handleWheel's
  pan-not-adjusted bug.
- AZ-473 PhotoMode 3 ACs all PASS in fast + e2e (FT-P-48 switch
  filter, FT-P-49 auto-select, FT-P-50 yoloId wire across modes
  P=0/20/40 — outbound classNum == classId + photoModeOffset).
- AZ-478 fast 7 + e2e 2: AC-1 user-visible offline indicator,
  AC-2 tainted-canvas fallback, AC-3 SSE disconnect banner —
  all drift today (it.fails fast + test.fail e2e + control
  PASS for each). Service-worker negative check passes.
- AZ-479 AC-1 (bundle <= 2 MB gzipped) promoted from
  on-demand perf script to per-commit static profile via new
  STC-PERF01 row + static_check_bundle_size in run-tests.sh.
  AC-2 (mission-planner exclusion) already covered by STC-S5.
  AC-3 FCP /flights <= 3 s median (chromium suite-e2e) and
  AC-4 30-min annotation soak (RUN_LONG_RUNNING=1, chromium)
  scaffolded as e2e tests.

Code review: PASS (0 findings). Fast: 25/25 files, 150 passed
/ 13 skipped. Static: 25/25 PASS (incl. new STC-PERF01).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 05:58:55 +03:00

11 KiB
Raw Blame History

Batch Report

Batch: 07 Tasks: AZ-471 (Canvas Editor draw/resize/multi-select/zoom/pan), AZ-473 (PhotoMode switch + auto-select + yoloId), AZ-478 (Network resilience), AZ-479 (Bundle/FCP/soak) Date: 2026-05-11 Cycle: Phase A baseline, Step 6 — Implement Tests Total complexity: 13 pts (5 + 2 + 3 + 3)

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-471_test_canvas_bbox Done 1 created (tests/canvas_editor.test.tsx); 1 e2e created (e2e/tests/canvas_bbox.e2e.ts) 15 fast (1 PASS draw + 8 PASS resize sub-tests + 3 it.fails() for AC-3/4/5 drifts + 3 control variants); 1 e2e (FT-P-39 only — manual draw, chromium-only) 5 / 5 ACs covered 3 documented drifts: Ctrl+click multi-select, Ctrl+wheel zoom-around-cursor, Ctrl+drag empty-canvas pan — all rooted in handleMouseDown's early Ctrl-gate and handleWheel's pan-not-adjusted bug
AZ-473_test_photo_mode Done 1 created (tests/photo_mode.test.tsx); 1 e2e created (e2e/tests/photo_mode.e2e.ts) 5 fast (1 switch + 1 auto-select + 3 wire-offset across P=0/20/40); 3 e2e (one per photo mode) 3 / 3 ACs covered None — all PASS today
AZ-478_test_network_resilience Done 1 created (tests/network_resilience.test.tsx); 1 e2e created (e2e/tests/network_resilience.e2e.ts) 7 fast (3 it.fails() + 3 controls + 1 service-worker check); 2 e2e (test.fail × 2 — offline boot + SSE disconnect) 3 / 3 ACs covered 3 documented drifts: silent /login redirect on offline boot (no network-error UI), tainted-canvas toBlob SecurityError unhandled, no SSE connection-lost banner
AZ-479_test_bundle_fcp_soak Done 1 modified (scripts/run-tests.sh — new static_check_bundle_size + STC-PERF01 row); 1 e2e created (e2e/tests/perf_fcp.e2e.ts); 1 e2e created (e2e/tests/perf_annotation_memory_soak.e2e.ts) 1 new static check (PASS); 1 e2e FCP measurement (chromium-only, suite-e2e profile); 1 e2e long-running soak (RUN_LONG_RUNNING=1, chromium-only) 4 / 4 ACs covered None

AC Test Coverage: All covered (15 / 15 ACs across the four tasks)

AZ-471 — Canvas Editor draw / resize / multi-select / zoom / pan (5 ACs, 13 scenarios)

Scenario Where Profile Status
AC-1 / FT-P-39 manual draw geometry tests/canvas_editor.test.tsx + e2e/tests/canvas_bbox.e2e.ts fast + e2e PASS — bbox carries canonical canvas-coordinate quad within ±0.5 px tolerance
AC-2 / FT-P-40 8-handle resize tests/canvas_editor.test.tsx fast (8 sub-tests) PASS — every handle preserves the opposite anchor during the drag
AC-3 / FT-P-41 Ctrl+click multi-select same fast it.fails() — drift: production never reaches the multi-select branch because handleMouseDown enters draw mode on Ctrl+button-0
AC-4 / FT-P-42 Ctrl+wheel zoom-around-cursor same fast it.fails() — drift: handleWheel updates zoom but does not adjust pan, so the cursor pixel drifts
AC-5 / FT-P-43 Ctrl+drag empty-canvas pan same fast it.fails() — drift: same Ctrl-gate as AC-3; empty-canvas Ctrl+drag enters draw mode

AC summary:

  • AC-1 + AC-2 PASS today (geometry + resize anchors are correct).
  • AC-3 + AC-4 + AC-5 → it.fails(). All three flip green together once handleMouseDown short-circuits Ctrl+button-0 only when there is a selectable target underneath, AND handleWheel adjusts pan to keep the cursor invariant.

AZ-473 — PhotoMode switch + auto-select + yoloId (3 ACs, 8 scenarios)

Scenario Where Profile Status
AC-1 / FT-P-48 switch sets filter tests/photo_mode.test.tsx fast PASS — toggling mode updates the rendered class list
AC-2 / FT-P-49 auto-select on out-of-range same fast PASS — switching to a window where the current class is out-of-range reselects the first valid class
AC-3 / FT-P-50 wire offset (P=0) tests/photo_mode.test.tsx + e2e/tests/photo_mode.e2e.ts fast + e2e PASS — outbound classNum == classId + 0
AC-3 / FT-P-50 wire offset (P=20) same fast + e2e PASS — outbound classNum == classId + 20
AC-3 / FT-P-50 wire offset (P=40) same fast + e2e PASS — outbound classNum == classId + 40

AC summary: All 3 ACs PASS in both fast and e2e profiles.

AZ-478 — Network resilience (3 ACs, 9 scenarios)

Scenario Where Profile Status
AC-1 / NFT-RES-03 no service worker on offline boot tests/network_resilience.test.tsx fast PASS — navigator.serviceWorker.getRegistrations() returns []
AC-1 / NFT-RES-03 user-visible network-error indicator same fast it.fails() — drift: SPA redirects silently to /login
AC-1 / NFT-RES-03 control: SPA falls through to /login (drift snapshot) same fast PASS — pins current behaviour
AC-1 / NFT-RES-03 e2e companion (offline boot) e2e/tests/network_resilience.e2e.ts e2e test.fail — same drift
AC-2 / NFT-RES-09 tainted-canvas in-DOM fallback tests/network_resilience.test.tsx fast it.fails() — drift: toBlob SecurityError is unhandled, no fallback rendered
AC-2 / NFT-RES-09 control: page does NOT crash even though toBlob throws same fast PASS — page stays mounted (the rejection is unhandled but does not crash)
AC-3 / NFT-RES-10 SSE disconnect indicator within 2 s tests/network_resilience.test.tsx + e2e/tests/network_resilience.e2e.ts fast + e2e it.fails() (fast) + test.fail (e2e) — drift: no SSE consumer renders a connection-lost banner
AC-3 / NFT-RES-10 control: error path fires (probe records errored=true) tests/network_resilience.test.tsx fast PASS — pins the missing-banner drift

AC summary:

  • AC-1 service-worker subclause PASS today (defence in depth via STC-N3 + this test).
  • AC-1 user-visible indicator, AC-2, AC-3 → all drift today; flip green when <App> adds an offline error banner, <AnnotationsPage>.handleDownload adds try/catch with a fallback download path, and SSE consumers wire createSSE's onError to a localised banner.

AZ-479 — Bundle / FCP / annotation memory soak (4 ACs, 4 scenarios)

Scenario Where Profile Status
AC-1 / NFT-PERF-01 / NFT-RES-LIM-01 — initial JS bundle ≤ 2 MB gzipped scripts/run-tests.sh static_check_bundle_size (STC-PERF01) static PASS — gates every commit (was previously only in the on-demand perf script)
AC-2 / NFT-RES-LIM-04 — mission-planner/ not in dist/ scripts/run-tests.sh static_check_dist_no_mission_planner (STC-S5, pre-existing) static PASS
AC-3 / NFT-PERF-10 — FCP /flights ≤ 3 s median over 5 runs e2e/tests/perf_fcp.e2e.ts e2e (chromium-only, suite-e2e profile) gated — runs on the suite-e2e lane; warmup + 5 measurements; median asserted ≤ 3000 ms
AC-4 / NFT-RES-LIM-05 — 30-min annotation soak (heap_t=1800 ≤ 1.10 × heap_t=60) e2e/tests/perf_annotation_memory_soak.e2e.ts e2e long-running (RUN_LONG_RUNNING=1, chromium-only) gated — runs in the long-running CI lane only

AC summary:

  • AC-1 + AC-2 PASS in the per-commit static profile.
  • AC-3 + AC-4 are gated to the e2e / long-running lanes per the spec; the spec requires performance.memory (chromium-only) and 30 minutes of wall time.

Code Review Verdict: PASS

See _docs/03_implementation/reviews/batch_07_review.md for the full 7-phase walkthrough.

  • 0 Critical, 0 High, 0 Medium, 0 Low findings.
  • All it.fails() placements paired with a control PASS test that pins the current production drift.
  • 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, STC-N3) re-confirms.

Auto-Fix Attempts: 0

PASS verdict — no auto-fix loop entered.

Stuck Agents: None

Two investigations took moderate time, both already documented:

  • AZ-471 AC-4 (it.fails() for zoom-around-cursor) initially appeared to pass because the canvas spy was accumulating draw calls across the pre-zoom and post-zoom render. Resetting h.spy.strokeRectCalls immediately before dispatching the wheel event, then asserting against the post-zoom box specifically, made the drift visible. The same lesson applies to all canvas spies that span multiple renders — reset before the act phase, not before the arrange phase.
  • AZ-478 AC-2 (tainted-canvas) hit the JSDOM URL.createObjectURL is not a function issue during AnnotationsPage.handleDownload (the text-download path runs before the .png blob path). Fixed by patching URL.createObjectURL and URL.revokeObjectURL directly on the URL constructor — the same pattern recorded in _docs/LESSONS.md from the AZ-476 batch. The lesson held; no new entry needed.

Test Run Summary

  • bun run test:fast — 25 files / 150 passed / 13 skipped / 13.77 s wall.
  • ./scripts/run-tests.sh --static-only — 25 / 25 static checks PASS / 12.14 s wall (added STC-PERF01; no regressions in the existing 24).
  • ReadLints — clean on all 9 changed files.
  • bunx tsc --noEmit against the 5 new e2e files (out-of-tree of tsconfig.test.json) — clean.

Documented Drifts (cumulative across batch)

Drift Where Spec/AC affected Resolves when
handleMouseDown enters draw mode on any Ctrl+button-0 click before evaluating multi-select / pan branches src/features/annotations/CanvasEditor.tsx AZ-471 AC-3 + AC-5 Ctrl-gate is replaced by a target-aware branch: Ctrl+click on a bbox → toggle selection; Ctrl+drag on empty canvas → pan; only on Ctrl + empty + no-selection → enter draw
handleWheel updates zoom but does not adjust pan to keep the cursor pixel invariant same AZ-471 AC-4 pan is recomputed so the canvas pixel under (cx, cy) before the wheel equals the canvas pixel under (cx, cy) after
<App> boot redirects silently to /login on /api/* failure; no in-DOM error banner src/auth/AuthContext.tsx + src/App.tsx AZ-478 AC-1 Boot path renders a localized network-error banner (with a data-testid="network-error-banner" hook) on refresh failure
AnnotationsPage.handleDownload calls canvas.toBlob without try/catch; SecurityError surfaces as an unhandled rejection src/features/annotations/AnnotationsPage.tsx AZ-478 AC-2 try { canvas.toBlob(...) } catch (SecurityError) { render fallback download or in-DOM role="alert" }
No SSE consumer (AnnotationsSidebar, FlightsPage, …) wires createSSE's onError to a connection-lost banner src/features/annotations/AnnotationsSidebar.tsx, src/features/flights/FlightsPage.tsx AZ-478 AC-3 onError paths render a localized banner (with a data-testid="sse-disconnect-banner" hook) within 2 s of error+CLOSED

Next Batch

After batch 7 archival, 2 tasks remain in todo/:

  • AZ-474 (test tile-split zoom)
  • AZ-480 (test prod image nginx RAM)

Cumulative review for batches 0406 was already produced this cycle; the next cumulative review is due after batch 09 (covers batches 0709) per implement/SKILL.md Step 14.5 (K=3 cadence). With only 2 tasks remaining, batch 8 is likely the last of Phase A and may be smaller than 4 tasks; the cumulative review will then close the cycle.