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>
5.9 KiB
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
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 byGET /api/flights?pageSize=1000.selectedFlight: Flight | null— the active flight, ornullif none. Survives across pages because the provider is mounted above the route tree.
refreshFlights() (useCallback, no deps):
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([])):
refreshFlights()(noawait— runs in parallel with #2).api.get<UserSettings>('/api/annotations/settings/user')→- if
settings?.selectedFlightIdis truthy:api.get<Flight>('/api/flights/${settings.selectedFlightId}')→setSelectedFlight(f). - errors at every step silently swallowed.
- if
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):
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,UserSettingstypes.
- External:
react(createContext,useContext,useState,useEffect,useCallback,ReactNode).
Consumers (intra-repo)
From the §7a dependency graph:
src/App.tsx— mountsFlightProviderinsideProtectedRouteand above the route tree (soselectedFlightsurvives navigation between/flights,/annotations,/dataset).src/components/Header.tsx— displays the currently-selected flight name.src/features/flights/FlightsPage.tsx— the primary editor; callsselectFlighton row click.src/features/annotations/MediaList.tsx— filters media byselectedFlight.id.src/features/dataset/DatasetPage.tsx— same.
Data models
FlightandUserSettingsfromsrc/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 asUserSettingskeeps aselectedFlightIdfield.
Security
- No auth checks here — relies on
api/client.tsto inject the bearer; backend enforces visibility. - Silent failure on every async call — same caveat as
AuthContext. A misconfigured/api/flightswill leave the user with an empty list and no error indication. Flag for Step 6security_approach.mdand 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 aselectedFlightIdthat'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,selectedFlightstaysnulland the user sees nothing selected. Acceptable but worth documenting. selectFlight(null)PUTs{ selectedFlightId: null }— this clears the persisted preference. Confirm theannotations/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([])forrefreshFlightsis fine becausesetFlightsis stable. The empty deps array means the bootstrap effect fires exactly once (as intended) — but ESLint withreact-hooks/exhaustive-depswould still flag bothuseCallbacks for missingsetFlights(technically stable, but the linter doesn't know). Acceptable; project-wide ESLint config does not currently enforce.