Phase B cycle 1 was a structural refactor only: F4 (barrel imports + STC-ARCH-01) and F7 (endpoint builders + STC-ARCH-02). This commit brings docs in line with source after the cycle, no code changes. Module docs (12 consumers): swap every /api/<service>/... literal in code snippets and integration tables for the matching endpoints.* builder; note the barrel import migration in Dependencies. New module doc: src__api__endpoints.md (public surface, F4 barrel re-export note, STC-ARCH-02 enforcement, contract-test reference). Architecture compliance baseline: mark F4 + F7 CLOSED with commit hashes (23746ec,8a461a2). 01_api-transport component description: add endpoints.ts + barrel to Internal Interfaces, close the F7 caveat, extend Module Inventory. ripple_log_cycle1.md: Task Step 0.5 reverse-dep analysis records the import-graph closure (no extra docs needed beyond the direct set). Carry-over reports landed alongside the docs: - test_run_report_phase_b_cycle1.md (Step 11 outcome) - implementation_report_refactor_phase_b_cycle1.md (cycle summary) State file: trimmed to the autodev <30-line target; Steps 14 + 15 recorded as SKIPPED with rationale (no security or perf surface changed in this cycle); pointer moved to Step 16 (Deploy). Co-authored-by: Cursor <cursoragent@cursor.com>
6.4 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 endpoints.flights.collection('pageSize=1000')(=/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[] }>(endpoints.flights.collection('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>(endpoints.annotations.settingsUser())→- if
settings?.selectedFlightIdis truthy:api.get<Flight>(endpoints.flights.flight(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(endpoints.annotations.settingsUser(), { 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(barrel) —api,endpoints. (Since AZ-485 / F4 + AZ-486 / F7: barrel import + endpoint builders.)../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 (typed builders from ../api/endpoints, since AZ-486 / F7): endpoints.flights.collection('pageSize=1000'), endpoints.flights.flight(id), endpoints.annotations.settingsUser() — producing /api/flights?pageSize=1000, /api/flights/{id}, /api/annotations/settings/user respectively. 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. (Note: the literal lives in the caller, not in the endpoints.flights.collection builder — moving the ceiling into the builder is a future change.)
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.