[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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 00:38:49 +03:00
parent da0a5aa187
commit 510df68bcf
84 changed files with 13065 additions and 0 deletions
@@ -0,0 +1,99 @@
# 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
```ts
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/client``api.get<T>()`.
- `../features/annotations/classColors``getClassColor(i)`, `FALLBACK_CLASS_NAMES`.
- `../types``DetectionClass` 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/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.