[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,106 @@
# Module: `src/components/FlightContext.tsx`
> **Source**: `src/components/FlightContext.tsx` (52 lines)
> **Topo batch**: B3 (depends on B2 leaves: `api/client`, `types/index`)
## Purpose
A React context that holds the SPA's currently-selected flight and the cached flight list, keeps the selection in sync with the per-user backend setting, and exposes a refresh trigger. Sibling of `AuthContext.tsx` — both are mounted near the root (`App.tsx`) so any feature page can access flight state without prop-drilling. Replaces the legacy WPF "current mission" singleton (`_docs/legacy/wpf-era.md` §"What survived").
## Public interface
```ts
interface FlightState {
flights: Flight[]
selectedFlight: Flight | null
selectFlight: (f: Flight | null) => void
refreshFlights: () => Promise<void>
}
export function useFlight(): FlightState
export function FlightProvider({ children }: { children: ReactNode }): JSX.Element
```
`FlightContext` itself is module-private. Consumers must go through `useFlight()`.
## Internal logic
State:
- `flights: Flight[]` — most recent list returned by `GET /api/flights?pageSize=1000`.
- `selectedFlight: Flight | null` — the active flight, or `null` if none. Survives across pages because the provider is mounted above the route tree.
**`refreshFlights()`** (`useCallback`, no deps):
```ts
const data = await api.get<{ items: Flight[] }>('/api/flights?pageSize=1000')
setFlights(data.items ?? [])
```
Errors are silently swallowed (`try { ... } catch {}`). `pageSize=1000` is a hardcoded ceiling — see Configuration.
**Bootstrap effect** (`useEffect` keyed on `[refreshFlights]`, runs once because `refreshFlights` is `useCallback([])`):
1. `refreshFlights()` (no `await` — runs in parallel with #2).
2. `api.get<UserSettings>('/api/annotations/settings/user')`
- if `settings?.selectedFlightId` is truthy: `api.get<Flight>('/api/flights/${settings.selectedFlightId}')``setSelectedFlight(f)`.
- errors at every step silently swallowed.
The selected flight is therefore looked up by **its own GET**, not by indexing into the cached `flights` list. This is intentional — the user might have a `selectedFlightId` that fell off the first 1000 flights.
**`selectFlight(f)`** (`useCallback`, no deps):
```ts
setSelectedFlight(f)
api.put('/api/annotations/settings/user', { selectedFlightId: f?.id ?? null }).catch(() => {})
```
Optimistic — local state updates immediately; the persisted setting is fire-and-forget. If the PUT fails, the next reload will fall back to the previously-stored ID and the user's selection silently reverts. Flag for Step 4.
## Dependencies
- **Internal**:
- `../api/client``api`.
- `../types``Flight`, `UserSettings` types.
- **External**: `react` (`createContext`, `useContext`, `useState`, `useEffect`, `useCallback`, `ReactNode`).
## Consumers (intra-repo)
From the §7a dependency graph:
- `src/App.tsx` — mounts `FlightProvider` inside `ProtectedRoute` and above the route tree (so `selectedFlight` survives navigation between `/flights`, `/annotations`, `/dataset`).
- `src/components/Header.tsx` — displays the currently-selected flight name.
- `src/features/flights/FlightsPage.tsx` — the primary editor; calls `selectFlight` on row click.
- `src/features/annotations/MediaList.tsx` — filters media by `selectedFlight.id`.
- `src/features/dataset/DatasetPage.tsx` — same.
## Data models
- `Flight` and `UserSettings` from `src/types/index.ts` — see `_docs/02_document/modules/src__types__index.md`.
## Configuration
Endpoints (string-literal): `/api/flights?pageSize=1000`, `/api/flights/{id}`, `/api/annotations/settings/user`. Routed by `nginx.conf` to the `flights/` and `annotations/` services.
`pageSize=1000` is a **hardcoded ceiling** — if the system ever has >1000 flights, the cache is silently truncated. The `flights/` service contract probably caps at 1000; verify and document in `data_parameters.md` (Step 6). Flag.
## External integrations
- `flights/` (.NET) — `GET /api/flights`, `GET /api/flights/{id}`.
- `annotations/` (.NET) — `GET /api/annotations/settings/user`, `PUT /api/annotations/settings/user`. The user-settings store is reused for the selected-flight pointer; this is a slight abstraction leak (selected flight is logically a UI-state preference, not an annotation setting), but it works as long as `UserSettings` keeps a `selectedFlightId` field.
## Security
- **No auth checks here** — relies on `api/client.ts` to inject the bearer; backend enforces visibility.
- **Silent failure on every async call** — same caveat as `AuthContext`. A misconfigured `/api/flights` will leave the user with an empty list and no error indication. Flag for Step 6 `security_approach.md` and Step 8 hardening.
## Tests
None.
## Notes / open questions
- **Race**: bootstrap fires `refreshFlights()` AND the user-settings GET in parallel. If the user-settings GET resolves first with a `selectedFlightId` that's not in the 1000 cached flights, the per-flight GET still succeeds. If the 1000 list is somehow stale and the per-flight GET 404s, `selectedFlight` stays `null` and the user sees nothing selected. Acceptable but worth documenting.
- **`selectFlight(null)` PUTs `{ selectedFlightId: null }`** — this clears the persisted preference. Confirm the `annotations/` service treats this as "unset" and not as an error or no-op. Flag for Step 4.
- **No realtime invalidation**: if another tab / user creates a flight, this client won't know until the next `refreshFlights()` call. The SPA also has SSE (`api/sse.ts`) but does NOT subscribe to flight updates. Mark as a Step 8 / future-feature hook.
- **`useCallback([])` for `refreshFlights`** is fine because `setFlights` is stable. The empty deps array means the bootstrap effect fires exactly once (as intended) — but ESLint with `react-hooks/exhaustive-deps` would still flag both `useCallback`s for missing `setFlights` (technically stable, but the linter doesn't know). Acceptable; project-wide ESLint config does not currently enforce.