Captures the full output of autodev existing-code Phase A through Step 4 (Code Testability Revision) for the Azaion UI workspace: - Step 1 Document: _docs/02_document/ (FINAL_report, architecture, glossary, components/, modules/, diagrams/, system-flows, module-layout) plus _docs/00_problem/ + _docs/01_solution/ + _docs/legacy/ + _docs/how_to_test + README. - Step 2 Architecture Baseline: architecture_compliance_baseline.md. - Step 3 Test Spec: _docs/02_document/tests/ (environment, test-data, blackbox/performance/resilience/security/ resource-limit tests, traceability-matrix), enum_spec_snapshot, expected_results/results_report.md (98 rows), plus the run-tests.sh + run-performance-tests.sh runners. - Step 4 Code Testability Revision: 01-testability-refactoring/ run dir (list-of-changes C01-C07, deferred_to_refactor, analysis/research_findings + refactoring_roadmap) and the 7 child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/ plus _dependencies_table.md. - _docs/_autodev_state.md pins the cursor at Step 4 / refactor Phase 4 entry so /autodev resumes cleanly. Epic AZ-447 (UI testability gates) tracks the 7 child tasks that will land in subsequent commits. Co-authored-by: Cursor <cursoragent@cursor.com>
6.4 KiB
Module: src/components/DetectionClasses.tsx
Source:
src/components/DetectionClasses.tsx(99 lines) Topo batch: B3 (depends on B2 leaves:api/client,features/annotations/classColors,types/index)
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[]>('/api/annotations/classes').- 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/client—api.get<T>().../features/annotations/classColors—getClassColor(i),FALLBACK_CLASS_NAMES.../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: /api/annotations/classes — string-literal URL (testability fix scheduled for Step 4).
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 /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.