mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 10:41:10 +00:00
[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:
@@ -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.
|
||||
Reference in New Issue
Block a user