Files
ui/_docs/02_document/modules/src__components__FlightContext.md
Oleksandr Bezdieniezhnykh 17d5bb45e7
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-485] [AZ-486] Cycle 1 docs refresh (Step 13)
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>
2026-05-12 00:01:04 +03:00

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 by GET endpoints.flights.collection('pageSize=1000') (= /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):

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([])):

  1. refreshFlights() (no await — runs in parallel with #2).
  2. api.get<UserSettings>(endpoints.annotations.settingsUser())
    • if settings?.selectedFlightId is truthy: api.get<Flight>(endpoints.flights.flight(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):

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.)
    • ../typesFlight, 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 (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 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 useCallbacks for missing setFlights (technically stable, but the linter doesn't know). Acceptable; project-wide ESLint config does not currently enforce.