Files
ui/_docs/02_document/modules/src__features__flights.md
T
Oleksandr Bezdieniezhnykh 510df68bcf [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>
2026-05-11 00:38:49 +03:00

13 KiB
Raw Blame History

Module group: src/features/flights/

Note

: this is a deliberately compact doc covering all 15 flights modules. Behaviour is mostly an in-progress port of mission-planner/ into the React 19 SPA. For the canonical product spec see _docs/ui_design/README.md (Flights Page Layout) and ../../../_docs/02_flights.md / 11_gps_denied.md in the parent suite repo (Flights API contract + GPS-Denied semantics).

Scope

Owns the /flights route. Lets the user:

  1. Browse / create / delete Flight rows (POST/DELETE /api/flights/...).
  2. Plan a mission on a Leaflet map: add waypoints, draw work-area / no-go rectangles, edit altitude + purpose per point, see live total distance, time, battery %.
  3. Toggle into GPS-Denied mode — opens an SSE stream /api/flights/{id}/live-gps (and the panel for orthophoto upload + correction once that backend wiring lands; today only the live-GPS readout is connected).
  4. Save waypoints back to the Flights API (/api/flights/{id}/waypoints).
  5. Import / export the plan as JSON.

Currently handles only the planning surface; the gps-denied orthophoto upload / correction inputs in _docs/ui_design/flights.html are not yet implemented.

Module map

Module Layer Responsibility
types.ts leaf All flight-feature-only types (FlightPoint, CalculatedPointInfo, MapRectangle, WindParams, AircraftParams, MovingPointInfo, ActionMode), plus tile URLs (TILE_URLS), PURPOSES (tank / artillery), and COORDINATE_PRECISION = 8.
mapIcons.ts leaf Three coloured Leaflet Icon instances + the default Leaflet pin (loaded from a CDN — see Findings).
flightPlanUtils.ts leaf Pure-ish helpers: newGuid, haversine calculateDistance (with plane climb/cruise/descend profile), OpenWeatherMap fetch, semi-empirical calculateBatteryPercentUsed, calculateAllPoints (sequential reduce), parseCoordinates, getMockAircraftParams.
WaypointList.tsx sub-component @hello-pangea/dnd reorderable list, hover-only Edit/Remove buttons, shows distance / time / battery / altitude per point.
AltitudeChart.tsx sub-component react-chartjs-2 Line chart of altitude over normalized distance; pulls all controllers via chart.js/auto.
WindEffect.tsx sub-component Two number inputs (heading 0360°, speed 030 m/s) with a small SVG arrow preview.
MiniMap.tsx sub-component 240×180 react-leaflet thumbnail anchored to a moving point; attributionControl={false}.
AltitudeDialog.tsx sub-component Add / Edit waypoint modal: lat / lng / altitude / meta: string[] purpose multi-select. Fully controlled.
MapPoint.tsx sub-component One waypoint marker: draggable, popup with altitude slider, purpose checkboxes, remove button.
DrawControl.tsx sub-component Headless Leaflet handler that draws work-area / prohibited-area rectangles via mousedown / mousemove / mouseup.
FlightListSidebar.tsx sub-component Left rail: flight list, "+ Create", inline-create row, telemetry date stub.
JsonEditorDialog.tsx sub-component Modal <textarea> over the plan JSON with live JSON.parse validation.
FlightParamsPanel.tsx composite Hosts WaypointList + AltitudeChart + WindEffect + all per-flight inputs (aircraft, initial altitude, FoV, comm address, action-mode buttons, totals strip, Save / Upload / EditAsJSON / Export).
FlightMap.tsx composite Wraps MapContainer; mounts MapPoint × N, DrawControl, MiniMap (when a point is moving), polyline + arrow decorator, satellite/classic toggle.
FlightsPage.tsx page Orchestrator: owns all state, talks to api/client, opens the SSE stream, mediates between sidebar / params panel / map / dialogs.

Key contracts (read by other docs)

  • FlightPoint: { id: string; position: { lat; lng }; altitude: number; meta: string[] }. meta{ 'tank', 'artillery' }. The shape diverges from the legacy WPF Point (radio, single purpose) — the React SPA uses checkboxes (multi).
  • CalculatedPointInfo: { bat: number /* % */; time: number /* hours */ }. Index i = state at point i after the segment from i-1. lastInfo.bat drives the Good / Caution / Low colour status (>12 / >5 / ≤5).
  • PURPOSES = [{ value: 'tank', label: 'options.tank' }, { value: 'artillery', label: 'options.artillery' }] — i18n keys are flights.planner.${label}.
  • JSON plan shape (handleEditJson / handleExport / handleJsonSave): { operational_height: { currentAltitude }, geofences: { polygons: [{ northWest, southEast, fence_type: 'EXCLUSION'|'INCLUSION' }] }, action_points: [{ point: { lat, lon }, height, action: 'search', action_specific: { targets: string[] } }] }. Used for both export-to-file and the JSON editor.
  • Tile URLs: classic OSM and an Esri ArcGIS World_Imagery (in types.ts). Both are direct upstream — neither goes through the suite satellite-provider/ proxy. See Findings.

External integrations

Endpoint / origin Where Direction Notes
GET /api/flights/aircrafts FlightsPage egress Aircraft selector population.
GET /api/flights/{id}/waypoints FlightsPage egress On flight select.
POST /api/flights FlightsPage egress Create flight from sidebar.
DELETE /api/flights/{id} FlightsPage egress After ConfirmDialog.
POST/DELETE /api/flights/{id}/waypoints[/{wp}] FlightsPage.handleSave egress Delete-all-then-recreate, sequentially.
GET /api/flights/{id}/live-gps (SSE) FlightsPage egress Open while in GPS mode + flight selected.
https://api.openweathermap.org/... flightPlanUtils.getWeatherData egress Direct browser→3rd-party. Hardcoded API key. See Findings.
tile.openstreetmap.org (TILE_URLS.classic) FlightMap, MiniMap egress Direct, no proxy.
server.arcgisonline.com/.../World_Imagery (TILE_URLS.satellite) FlightMap, MiniMap egress Direct, no proxy.
unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png mapIcons.defaultIcon egress CDN, version pinned to 1.7.1 while package is 1.9.4 (drift).
navigator.geolocation.getCurrentPosition FlightsPage mount browser API Fallback to hardcoded 47.242, 35.024 (Zaporizhzhia).

Findings carried into Step 4 / 6 / 8

These are the real findings; the per-module rationale is in git history of the deleted per-file docs. Numbered for cross-reference from state.json.notes.

  1. HARDCODED OPENWEATHER API KEYflightPlanUtils.ts:60. HIGH severity. Step 4 source-code fix; upstream rotation is a parallel user task.
  2. flightPlanUtils.calculateAllPoints does N sequential awaits to OpenWeatherMap — N × RTT latency. Not parallelisable as-is because info[i].bat depends on info[i-1]. Step 8 refactor: fetch weather first in parallel, then reduce.
  3. flightPlanUtils.calculateDistance mixes km and metres (R = 6371, altitudes converted /1000 inline, returned time = dist / aircraft.speed assumes km/h). No type forces correctness. Step 4.
  4. AircraftParams.batteryCapacity unit is ambiguous (Wh vs W·s). calculateBatteryPercentUsed divides W·s by it × 100 — only correct if W·s. Verify against mission-planner/src/services/calculateBatteryUsage.ts. Step 4.
  5. flightPlanUtils.getWeatherData swallows errors silently (catch { return null }); callers can't distinguish "no wind" from "key revoked". Step 4.
  6. mapIcons.defaultIcon CDN URL is leaflet@1.7.1 while package.json is 1.9.4. Step 4 — switch to bundled assets or match version.
  7. FlightMap and MiniMap bypass the suite satellite-provider/ proxy (Esri tiles direct from server.arcgisonline.com). Possible licence + rate-limit concern. Step 4 + architecture.md.
  8. MiniMap sets attributionControl={false} — drops OSM / Esri attribution. Possible licence-compliance gap. Step 4.
  9. MiniMap is fixed 240×180 + zoom 18 hardcoded — overflows below the 640px mobile breakpoint. Step 4 vs _docs/ui_design/README.md responsive specs.
  10. AltitudeDialog lacks Esc-to-close, backdrop-click-to-cancel, role="dialog", aria-modal — inconsistent with ConfirmDialog. Same for JsonEditorDialog. Pick one modal convention in Step 4.
  11. AltitudeDialog accepts any number for lat/lng (no [-90,90] / [-180,180] guard). AltitudeDialog.altitude and WindEffect inputs use Number('') → 0 silently — same pitfall noted in SettingsPage. Step 4.
  12. AltitudeDialog purpose multi-select vs WPF radio (single choice). Confirm intent in Step 6: is the SPA expanding the data model on purpose, or is this a UI bug?
  13. WindEffect max=360 allows duplicate heading at 0 vs 360. Step 4.
  14. WaypointList drag handle is the entire row (prevents text selection); Edit/Remove buttons are hover-only (unusable on touch); no a11y reorder announcements. Step 4 / Step 8 a11y.
  15. WaypointList.calculatedPointInfo[i] silently degrades to alt-only label if length mismatches; tightly coupled to FlightsPage keeping arrays in lockstep.
  16. AltitudeChart pulls every chart.js controller via chart.js/auto (bundle bloat); colours are duplicated as hex literals (drift from the az-* Tailwind tokens). Step 8.
  17. AltitudeDialog naming: file is "AltitudeDialog" but the dialog covers lat / lng / altitude / purpose. Port-vestige from mission-planner/. Rename to WaypointDialog in Step 8.
  18. flightPlanUtils.ts is single-file — newGuid + geo + weather + battery + parser + mock all together. Splitting into geo.ts / weather.ts / battery.ts / mockAircraft.ts is a Step 8 SRP candidate.
  19. FlightsPage.handleSave deletes all existing waypoints then recreates — N+M sequential PUT/DELETE round-trips, not transactional, no progress UI, partial failure leaves the flight half-saved.
  20. FlightsPage.handleSave body shape does NOT match the Flights API spec — UI will likely 400 on a strict server. Code POSTs { name, latitude, longitude, order }. Parent ../../../../_docs/02_flights.md §3 CreateWaypointRequest requires { Geopoint: {Lat, Lon, MGRS}, Source: WaypointSource, Objective: WaypointObjective, OrderNum, Height }. Mismatches: (a) lat/lon not nested under Geopoint; (b) field is order, spec is OrderNum; (c) Source, Objective, Height not sent at all; (d) UI sends name which the spec does not define on Waypoint (Waypoint interface in src/types/index.ts:76 invents name). This collides with finding #19 — every save will round-trip waypoints in the wrong shape. PRIORITY for Step 4. Open question for Step 6: are these columns being added to the Flights API schema, or is the React UI to be aligned to the spec? Same comment for altitude and meta which have no place in the current spec.
  21. FlightsPage.useEffect bootstraps getMockAircraftParams() as the active aircraft regardless of what the backend returns — the dropdown choice is cosmetic for now. Real wiring is a Step 6 / Step 8 follow-up.
  22. GPS-Denied panel is partial — only the SSE live-GPS readout is wired; orthophoto upload, GPS correction (per _docs/ui_design/README.md) are not. Step 6 problem extraction.
  23. handleImport silently drops the file picker if the user cancels (if (!file) return) — fine. But handleJsonSave's catch uses alert(...) for a UX-grade error — replace with the project's modal/toast pattern in Step 4.
  24. MapPoint popup recomputes the marker DOM offset on every drag move to choose dx/dy for the moving-point indicator. Acceptable, but the (marker as unknown as { _icon: HTMLElement })._icon cast leaks Leaflet internals.
  25. DrawControl registers global mousedown/mousemove/mouseup on the map while a draw mode is active and disables map.dragging for the duration — fine, but no Esc-to-cancel mid-draw.
  26. FlightContext ceiling: FlightsPage reads flights from useFlight() which fetches with pageSize=1000 (already flagged in FlightContext doc). Won't surface here, but selectFlight is fire-and-forget — if the PUT to /api/flights/select fails the next page reload reverts the choice without notice.

What's intentionally NOT here

  • The orthophoto-upload / GPS-correction sub-panel (in _docs/ui_design/flights.html but not in source).
  • Any per-segment annotation-time-window logic (that's the Annotations module).
  • Any aircraft battery model that respects altitude-dependent air density (constant 1.05 kg/m³ in source).

Tests

None — confirmed by 00_discovery.md §5. Documented test gap is owned by Steps 3 / 57 of autodev (test-spec → implement → run).

Cross-doc references

  • Parent suite Flights API contract: ../../../../_docs/02_flights.md (DTOs, endpoint shapes).
  • Parent suite GPS-Denied: ../../../../_docs/11_gps_denied.md (SSE event shape, correction flow).
  • UI spec: ../../ui_design/README.md (Flights Page Layout, GPS-Denied panel toggle, mobile breakpoint).
  • Port-source: mission-planner/src/flightPlanning/* — covered separately as one consolidated doc.