Wrap up cycle 3 across the autodev existing-code Phase B steps that follow Implement (Steps 12-15), plus the cross-workspace prerequisite ticket filed for AZ-512. Step 12 - Test-Spec Sync: - Un-quarantine FT-P-01 in traceability-matrix (closed by AZ-510) - Add AZ-510 chained /users/me failure-path test reference under AC-23 - Note AZ-512 deferral status under O9 (P12 Phase B target) Step 13 - Update Docs (task mode): - Refresh src__auth__AuthContext module doc with AZ-510 wire shape (POST refresh + chained /users/me + bootstrapInflight guard) - Add usersMe() to src__api__endpoints module doc + consumer note - Rename src__features__annotations__classColors module doc to src__class-colors__classColors (matches AZ-511 git mv); refresh header - Refresh src__components__DetectionClasses + src__features__annotations module group doc for the new class-colors barrel import path - Update components/11_class-colors Module Inventory to point at the renamed module doc filename - Rewrite system-flows.md Flow F2 (Bearer auto-refresh) with the AZ-510 POST + chained /users/me sequence; close Finding B3 references - Generate ripple_log_cycle3 documenting all changed source files, their reverse-dependency search results, and the docs touched Step 14 - Security Audit (cycle-3 delta): - Resume mode against cycle-2 baseline; cycle-2 artifacts untouched - Re-run bun audit on both roots: clean (cycle-2 inline fix held) - Re-rate OWASP A06: FAIL -> PASS; A07: PASS_WITH_KNOWN -> PASS (B3 closed by AZ-510) - New finding F-SAST-CY3-1 (LOW): __resetBootstrapInflightForTests exposed via src/auth public barrel; defer to hygiene cycle - Verdict: FAIL -> PASS_WITH_WARNINGS; one HIGH (F-SAST-1 mission-planner git-history key, unchanged) remains - Add amendment banner to cycle-2 security_report.md Step 15 - Performance Test: - Static profile NFT-PERF-01 PASS (290 575 B gzipped vs 2 MB budget; ~14% of budget; no regression from AZ-510 surface additions) - E2E profile SKIP (Playwright perf project still pending AZ-457..AZ-482); legitimate skip per test-run skill, gap acknowledged in report - AZ-510 200ms p95 chain NFR verified at spec level only - no CI gate yet (covered by future AZ-457..AZ-482 work) Cross-workspace prerequisite (AZ-513 just filed): - Updated _docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md to reflect AZ-513 filing on admin/ workspace (parent epic AZ-509, Blocks link to AZ-512). Companion task spec added in admin/ repo (separate commit there, owned by admin/ workspace). State file: advanced to Step 16 (Deploy) per autodev existing-code flow. Co-authored-by: Cursor <cursoragent@cursor.com>
6.8 KiB
Module: src/components/DetectionClasses.tsx
Source:
src/components/DetectionClasses.tsx(99 lines) Topo batch: B3 (depends on B2 leaves:api/client,class-colors(via barrel),types/index) Last refresh: 2026-05-13 —getClassColor+FALLBACK_CLASS_NAMESimport migrated from'../features/annotations/classColors'to'../class-colors'barrel by AZ-511.
Purpose
A side-panel widget that lets the annotator pick a detection class (1–9 or click) and a photo mode (Regular, Winter, Night). Loads the class catalogue from the backend and falls back to a hardcoded list when the API returns empty / errors. Replaces the legacy WPF detection-class picker referenced in _docs/legacy/wpf-era.md §"What survived".
Public interface
interface Props {
selectedClassNum: number
onSelect: (classNum: number) => void
photoMode: number
onPhotoModeChange: (mode: number) => void
}
export default function DetectionClasses(props: Props): JSX.Element
Fully controlled — the parent owns selectedClassNum and photoMode. The current contract for photoMode values is integer offsets 0 (Regular), 20 (Winter), 40 (Night), matching the photoMode field of DetectionClass (src/types/index.ts) and the offsets baked into FALLBACK_CLASSES below.
Internal logic
- Class catalogue load (mount-only
useEffect):api.get<DetectionClass[]>(endpoints.annotations.classes())(=/api/annotations/classes, since AZ-486 / F7).- On a non-empty array →
setClasses(list). - On an empty array OR a thrown error →
setClasses(FALLBACK_CLASSES).
FALLBACK_CLASSESis a module-private 3 × |FALLBACK_CLASS_NAMES| matrix:- For each mode offset in
[0, 20, 40], build one class entry per name inFALLBACK_CLASS_NAMES(imported fromfeatures/annotations/classColors.ts). - Each entry:
{ id: i + modeOffset, name, shortName: name.slice(0, 3), color: getClassColor(i), maxSizeM: 10, photoMode: modeOffset }. - This means: in offline / API-down mode, the ID range is
0–N-1(Regular),20–20+N-1(Winter),40–40+N-1(Night). The backend's class IDs MUST follow the same convention or fallback↔backend handoff yields ID collisions. Confirm in Step 4 via theannotations/service contract.
- For each mode offset in
- Numeric hotkeys 1–9 (effect keyed on
[classes, photoMode, onSelect]):keydownonwindow.parseInt(e.key)→ if 1–9, picksclasses[(num - 1) + photoMode].- Bug-shaped:
photoModeis0 | 20 | 40(an offset), not a row count.classes[idx + 0]is correct for Regular; for Winter/Night the indexidx + 20/idx + 40is meaningful only whenclassesis in the contiguous[0..N-1, 20..20+N-1, 40..40+N-1]shape thatFALLBACK_CLASSESproduces. If the backend returns its classes in a different order, hotkey 1–9 will pick the wrong class. Verify backend ordering in Step 4 — this is the principal correctness risk in this module. Flag.
- Auto-select first class on mode change (effect keyed on
[classes, photoMode, selectedClassNum, onSelect]):- Filter
classesto the activephotoMode. - If
selectedClassNumis not within that filtered set, callonSelect(modeClasses[0].id). - This guarantees the parent always holds a class ID consistent with the selected photo mode.
- Filter
- Render:
- Class list — only entries whose
photoModematches the active mode. - Photo-mode bar — three buttons (Sunny / Snowflake / Moon icons) for Regular / Winter / Night.
- Class list — only entries whose
Dependencies
- Internal:
../api(barrel) —api,endpoints. (Since AZ-485 / F4 + AZ-486 / F7.)../features/annotations/classColors—getClassColor(i),FALLBACK_CLASS_NAMES. (Cross-component import preserved; flagged in Consumers below.)../types—DetectionClasstype.
- External:
react,react-i18next,react-icons/md,react-icons/fa.
Consumers (intra-repo)
From the §7a dependency graph:
src/features/annotations/AnnotationsPage.tsxsrc/features/dataset/DatasetPage.tsx
This is the canonical example of the cross-layer import flagged in _docs/02_document/00_discovery.md §8: a components/ (shared) module importing from features/annotations/. Two clean fixes are in scope for Step 4 / Step 8:
- Lift
classColors.tsintosrc/components/detection/(orsrc/shared/) and update the two consumers. - Or: move
DetectionClassesitself intosrc/features/annotations/since both consumers are infeatures/.
Data models
DetectionClass (from src/types/index.ts) — see _docs/02_document/modules/src__types__index.md.
FALLBACK_CLASSES is module-private; see Internal logic above.
Configuration
Endpoint: endpoints.annotations.classes() → /api/annotations/classes (typed builder from ../api/endpoints, since AZ-486 / F7).
Photo-mode value set is {0, 20, 40} — hardcoded, mirrored by FALLBACK_CLASSES. If the backend grows a fourth mode (e.g. thermal, IR), every consumer of photoMode will need a coordinated change.
Tailwind tokens: bg-az-orange (Regular), bg-az-blue (Winter), bg-purple-600 (Night). Defined in src/index.css.
External integrations
- HTTP
GET endpoints.annotations.classes()(=/api/annotations/classes) →DetectionClass[]. Backed by theannotations/service (.NET) pernginx.conf.
Security
- Silent failure on class load:
.catch(() => setClasses(FALLBACK_CLASSES))swallows the error and the user sees the fallback. Acceptable for UX continuity, but the lack of any user-visible signal means a misconfigured/api/annotations/classesdeploy could go unnoticed in prod. Flag for Step 6security_approach.md/ Step 8. - No input that flows back to the server here.
- The
getClassColor(i)palette is deterministic; no PII.
Tests
None.
Notes / open questions
getPhotoModeSuffixredundancy (carry-over from B1):classColors.tsexposesgetPhotoModeSuffix()that derives the same suffix the typedDetectionClass.photoModefield already encodes. OnceclassColors.tsis repositioned (see Consumers),getPhotoModeSuffixis a deletion candidate. Defer to Step 8.FALLBACK_CLASS_NAMES.lengthis implicitly assumed to be ≤ 9 by the hotkey code (only 1–9 are bound). If the catalogue grows to 10+ entries, hotkeys can no longer cover the tail. Acceptable for now.- Mode-button colours don't use
az-tokens for Night (bg-purple-600,text-purple-400instead of anaz-purpletoken). Cosmetic inconsistency; flag for Step 4 against_docs/ui_design/README.mdcolour palette. - The class list is rendered with
1.,2., … prefixes derived fromi+1— so the hotkey number always matches the visible label inside the active mode. Good. - Does not handle modifier keys (
Shift,Ctrl) on numeric hotkeys; pressingShift+5will trigger the5branch. Fine for now.