[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,181 @@
# Module: `src/features/settings/SettingsPage.tsx`
> **Source**: `src/features/settings/SettingsPage.tsx` (107 lines)
> **Topo batch**: B4 (depends on B3: `api/client`, `types/index`)
## Purpose
The per-tenant configuration screen. Three side-by-side panels:
**Tenant** (system settings: military unit, name, default camera
width and FoV), **Directories** (server-side filesystem paths for
videos, images, labels, results, thumbnails, GPS sat / route),
and **Aircrafts** (read-only list with star-toggle for default
selection). Replaces the legacy WPF `SettingsWindow.xaml` plus the
"system" tab (`_docs/legacy/wpf-era.md` §4).
Available to every authenticated user — `Header` does not gate
`/settings` behind a permission check. The backend is the authority
for who is allowed to write.
## Public interface
```ts
export default function SettingsPage(): JSX.Element
```
No props.
## Internal logic
- **State**:
- `system: SystemSettings | null` — loaded from
`GET /api/annotations/settings/system`. `null` until the GET
resolves; the panel does not render until then (`{system && (...)}`).
- `dirs: DirectorySettings | null` — analogous, from
`GET /api/annotations/settings/directories`.
- `aircrafts: Aircraft[]` — from `GET /api/flights/aircrafts`.
- `saving: boolean` — disables the two Save buttons during a PUT.
- **Bootstrap effect** (`useEffect([])`):
```ts
api.get<SystemSettings>('/api/annotations/settings/system').then(setSystem).catch(() => {})
api.get<DirectorySettings>('/api/annotations/settings/directories').then(setDirs).catch(() => {})
api.get<Aircraft[]>('/api/flights/aircrafts').then(setAircrafts).catch(() => {})
```
Three independent calls, all silently swallowed on error. Empty UI
on failure (no error banner). Flag for Step 4.
- **`saveSystem()`**:
1. Guard: `if (!system) return`.
2. `setSaving(true)`.
3. `await api.put('/api/annotations/settings/system', system)`.
4. `setSaving(false)`.
No optimistic update needed (the PUT body **is** the local state).
No re-fetch — assumes the server echoes the same values. **Error
path is missing**: a thrown PUT leaves `saving: true` permanently
(no `try/finally`). Flag for Step 4.
- **`saveDirs()`** — analogous against
`PUT /api/annotations/settings/directories`. Same missing
`try/finally` issue.
- **`handleToggleDefault(a)`** — duplicate of the same handler in
`AdminPage`: `PATCH /api/flights/aircrafts/${a.id}` with
`{ isDefault: !a.isDefault }` then optimistic local flip. Two copies
of the same logic in two pages — extract to a shared helper or to
`FlightContext` in Step 8 (the legacy WPF had a single
`AircraftService.SetDefault(...)`).
- **`field(label, value, onChange, type='text')`** — local helper that
renders a labeled `<input>`. Always controlled (`value={value ?? ''}`).
Converts numeric inputs via `parseInt(v) || 0` /
`parseFloat(v) || 0` at the call site. Note: `parseInt` / `parseFloat`
on an empty string returns `NaN`, and `NaN || 0` is `0` — so
clearing a numeric field silently writes `0`, not `null`. Flag for
Step 4 against the `SystemSettings` type which permits `null`.
- **Layout** — three independent flex children:
- Tenant (`w-[300px]` shrink-0): `field()` × 4 + Save.
- Directories (`w-[300px]` shrink-0): `field()` × 7 + Save.
- Aircrafts (`flex-1 max-w-sm`): list with star toggle.
## Dependencies
- **Internal**:
- `../../api/client` — `api`.
- `../../types` — `SystemSettings`, `DirectorySettings`, `Aircraft`.
- **External**: `react` (`useState`, `useEffect`),
`react-i18next` (`useTranslation`).
## Consumers (intra-repo)
- `src/App.tsx` — mounted at the `/settings` route inside the
protected tree.
## Data models
- `SystemSettings` includes `id, name, militaryUnit,
defaultCameraWidth, defaultCameraFoV, thumbnailWidth, thumbnailHeight,
thumbnailBorder, generateAnnotatedImage, silentDetection`. The page
exposes only the first four — every other field is read on GET and
echoed back on PUT untouched. Flag if a future cycle needs to expose
thumbnails / silent-detection toggles.
- `DirectorySettings` has all 7 directory fields exposed as text
inputs. Path validation is server-side only.
- `Aircraft` (`id, model, type, isDefault`) — same shape as in
`AdminPage`.
## Configuration
- **i18n keys consumed**: `settings.tenant`, `settings.directories`,
`settings.aircrafts`, `settings.save`. (Confirmed in
`src/i18n/en.json`.) Hardcoded English labels for every form field
("Military Unit", "Name", "Default Camera Width", "Default Camera
FoV", "Videos Dir", "Images Dir", "Labels Dir", "Results Dir",
"Thumbnails Dir", "GPS Sat Dir", "GPS Route Dir"). Flag for Step 4.
- **Tailwind tokens**: `bg-az-panel`, `bg-az-bg`, `bg-az-orange`,
`text-az-{text,muted,orange}`, `bg-az-blue/20`, `bg-az-green/20`,
`text-az-{blue,green}`, `border-az-border`. Defined in
`src/index.css`.
## External integrations
| Method | Path | Purpose |
|---|---|---|
| `GET` | `/api/annotations/settings/system` | Load tenant config |
| `PUT` | `/api/annotations/settings/system` | Save tenant config |
| `GET` | `/api/annotations/settings/directories` | Load directory paths |
| `PUT` | `/api/annotations/settings/directories` | Save directory paths |
| `GET` | `/api/flights/aircrafts` | Load aircraft list |
| `PATCH` | `/api/flights/aircrafts/{id}` | Toggle `isDefault` |
Routed by `nginx.conf` to `annotations/` and `flights/` backends.
## Security
- **No client-side write authorization check** — the page renders the
Save buttons for every user. The backend (`annotations/` service)
is the authority. Document in `security_approach.md` (Step 6).
- **Hardcoded internal directory paths** are **not** present in this
file (unlike `AdminPage`'s hardcoded GPS device IP) — every value
is server-supplied. Good.
- **Path inputs are free-text** — server-side path traversal
validation is mandatory. Verify the `annotations/` service rejects
e.g. `../../etc/passwd`. Flag for Step 6.
- **`saving` state can stick on PUT failure** because there is no
`try/finally`, leaving the Save button permanently disabled. Step 4
candidate (matches the `AdminPage` "AI/GPS save buttons do
nothing" pattern — there is a clear lack of UX-level error
handling across both admin/settings pages).
## Tests
None.
## Notes / open questions
- **Numeric clear-to-zero pitfall**: `parseInt('') || 0` writes `0`
for an empty input, not `null`. This silently overwrites a
legitimate `null` (per the `SystemSettings` type, all four numeric
fields are nullable). Step 4 fix: detect empty input and pass
`null`.
- **Aircraft toggle handler is duplicated** between `AdminPage` and
`SettingsPage` — extract to a shared helper. Step 8.
- **No optimistic concurrency**: two admins editing system settings
simultaneously will overwrite each other. The `id` field is sent
back on PUT but no `version` / `etag` / `If-Match` header. Flag for
Step 6 problem-extraction. Backend may not support optimistic
concurrency yet.
- **The Aircrafts panel is read-only-ish here** but allows the
star-toggle, same as `AdminPage`. The duplication suggests an open
question: is this intentional (settings is "personal preferences",
admin is "global config", but default-aircraft is a *global*
setting) or accidental? Surface to the user in Step 6 problem
extraction.
- **`saving` is a single global flag** even though the page has two
independent Save buttons (system / dirs). A user who clicks
"Save System" then quickly clicks "Save Dirs" while the first PUT
is in flight will see the Dirs button disabled too. Acceptable
given the latency budget; flag if both saves become slow.
- **`thumbnailWidth/Height/Border` and `generateAnnotatedImage`,
`silentDetection`** in `SystemSettings` are not exposed in the UI
but are echoed back on PUT. If a future cycle adds them, ensure the
GET → PUT round-trip preserves any concurrent change made by another
client between the GET and the PUT.