Files
ui/_docs/02_document/modules/src__components__DetectionClasses.md
T
Oleksandr Bezdieniezhnykh 17d5bb45e7
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-485] [AZ-486] Cycle 1 docs refresh (Step 13)
Phase B cycle 1 was a structural refactor only: F4 (barrel imports +
STC-ARCH-01) and F7 (endpoint builders + STC-ARCH-02). This commit
brings docs in line with source after the cycle, no code changes.

Module docs (12 consumers): swap every /api/<service>/... literal in
code snippets and integration tables for the matching endpoints.*
builder; note the barrel import migration in Dependencies.

New module doc: src__api__endpoints.md (public surface, F4 barrel
re-export note, STC-ARCH-02 enforcement, contract-test reference).

Architecture compliance baseline: mark F4 + F7 CLOSED with commit
hashes (23746ec, 8a461a2).

01_api-transport component description: add endpoints.ts + barrel to
Internal Interfaces, close the F7 caveat, extend Module Inventory.

ripple_log_cycle1.md: Task Step 0.5 reverse-dep analysis records the
import-graph closure (no extra docs needed beyond the direct set).

Carry-over reports landed alongside the docs:
- test_run_report_phase_b_cycle1.md (Step 11 outcome)
- implementation_report_refactor_phase_b_cycle1.md (cycle summary)

State file: trimmed to the autodev <30-line target; Steps 14 + 15
recorded as SKIPPED with rationale (no security or perf surface
changed in this cycle); pointer moved to Step 16 (Deploy).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 00:01:04 +03:00

6.6 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[]>(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_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 (barrel) — api, endpoints. (Since AZ-485 / F4 + AZ-486 / F7.)
    • ../features/annotations/classColorsgetClassColor(i), FALLBACK_CLASS_NAMES. (Cross-component import preserved; flagged in Consumers below.)
    • ../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: 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 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.