Files
ui/_docs/02_document/modules/src__components__DetectionClasses.md
T
Oleksandr Bezdieniezhnykh 510df68bcf [AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs
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>
2026-05-11 00:38:49 +03:00

6.4 KiB
Raw Blame History

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 (19 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_CLASSES is a module-private 3 × |FALLBACK_CLASS_NAMES| matrix:
    • For each mode offset in [0, 20, 40], build one class entry per name in FALLBACK_CLASS_NAMES (imported from features/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 0N-1 (Regular), 2020+N-1 (Winter), 4040+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 the annotations/ service contract.
  • Numeric hotkeys 19 (effect keyed on [classes, photoMode, onSelect]):
    • keydown on window. parseInt(e.key) → if 19, picks classes[(num - 1) + photoMode].
    • Bug-shaped: photoMode is 0 | 20 | 40 (an offset), not a row count. classes[idx + 0] is correct for Regular; for Winter/Night the index idx + 20 / idx + 40 is meaningful only when classes is in the contiguous [0..N-1, 20..20+N-1, 40..40+N-1] shape that FALLBACK_CLASSES produces. If the backend returns its classes in a different order, hotkey 19 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 classes to the active photoMode.
    • If selectedClassNum is not within that filtered set, call onSelect(modeClasses[0].id).
    • This guarantees the parent always holds a class ID consistent with the selected photo mode.
  • Render:
    • Class list — only entries whose photoMode matches the active mode.
    • Photo-mode bar — three buttons (Sunny / Snowflake / Moon icons) for Regular / Winter / Night.

Dependencies

  • Internal:
    • ../api/clientapi.get<T>().
    • ../features/annotations/classColorsgetClassColor(i), FALLBACK_CLASS_NAMES.
    • ../typesDetectionClass type.
  • External: react, react-i18next, react-icons/md, react-icons/fa.

Consumers (intra-repo)

From the §7a dependency graph:

  • src/features/annotations/AnnotationsPage.tsx
  • src/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:

  1. Lift classColors.ts into src/components/detection/ (or src/shared/) and update the two consumers.
  2. Or: move DetectionClasses itself into src/features/annotations/ since both consumers are in features/.

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/classesDetectionClass[]. Backed by the annotations/ service (.NET) per nginx.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/classes deploy could go unnoticed in prod. Flag for Step 6 security_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

  • getPhotoModeSuffix redundancy (carry-over from B1): classColors.ts exposes getPhotoModeSuffix() that derives the same suffix the typed DetectionClass.photoMode field already encodes. Once classColors.ts is repositioned (see Consumers), getPhotoModeSuffix is a deletion candidate. Defer to Step 8.
  • FALLBACK_CLASS_NAMES.length is implicitly assumed to be ≤ 9 by the hotkey code (only 19 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-400 instead of an az-purple token). Cosmetic inconsistency; flag for Step 4 against _docs/ui_design/README.md colour palette.
  • The class list is rendered with 1., 2., … prefixes derived from i+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; pressing Shift+5 will trigger the 5 branch. Fine for now.