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>
13 KiB
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.mdin the parent suite repo (Flights API contract + GPS-Denied semantics).
Scope
Owns the /flights route. Lets the user:
- Browse / create / delete
Flightrows (POST/DELETE /api/flights/...). - 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 %.
- 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). - Save waypoints back to the Flights API (
/api/flights/{id}/waypoints). - 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 0–360°, speed 0–30 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 WPFPoint(radio, single purpose) — the React SPA uses checkboxes (multi).CalculatedPointInfo:{ bat: number /* % */; time: number /* hours */ }. Indexi= state at pointiafter the segment fromi-1.lastInfo.batdrives the Good / Caution / Low colour status (>12 / >5 / ≤5).PURPOSES = [{ value: 'tank', label: 'options.tank' }, { value: 'artillery', label: 'options.artillery' }]— i18n keys areflights.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(intypes.ts). Both are direct upstream — neither goes through the suitesatellite-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.
- HARDCODED OPENWEATHER API KEY —
flightPlanUtils.ts:60. HIGH severity. Step 4 source-code fix; upstream rotation is a parallel user task. flightPlanUtils.calculateAllPointsdoes N sequentialawaits to OpenWeatherMap — N × RTT latency. Not parallelisable as-is becauseinfo[i].batdepends oninfo[i-1]. Step 8 refactor: fetch weather first in parallel, then reduce.flightPlanUtils.calculateDistancemixes km and metres (R = 6371, altitudes converted/1000inline, returnedtime = dist / aircraft.speedassumes km/h). No type forces correctness. Step 4.AircraftParams.batteryCapacityunit is ambiguous (Wh vs W·s).calculateBatteryPercentUseddivides W·s by it × 100 — only correct if W·s. Verify againstmission-planner/src/services/calculateBatteryUsage.ts. Step 4.flightPlanUtils.getWeatherDataswallows errors silently (catch { return null }); callers can't distinguish "no wind" from "key revoked". Step 4.mapIcons.defaultIconCDN URL is leaflet@1.7.1 whilepackage.jsonis 1.9.4. Step 4 — switch to bundled assets or match version.FlightMapandMiniMapbypass the suitesatellite-provider/proxy (Esri tiles direct fromserver.arcgisonline.com). Possible licence + rate-limit concern. Step 4 +architecture.md.MiniMapsetsattributionControl={false}— drops OSM / Esri attribution. Possible licence-compliance gap. Step 4.MiniMapis fixed 240×180 + zoom 18 hardcoded — overflows below the 640px mobile breakpoint. Step 4 vs_docs/ui_design/README.mdresponsive specs.AltitudeDialoglacks Esc-to-close, backdrop-click-to-cancel,role="dialog",aria-modal— inconsistent withConfirmDialog. Same forJsonEditorDialog. Pick one modal convention in Step 4.AltitudeDialogaccepts any number for lat/lng (no[-90,90]/[-180,180]guard).AltitudeDialog.altitudeandWindEffectinputs useNumber('')→ 0 silently — same pitfall noted inSettingsPage. Step 4.AltitudeDialogpurpose 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?WindEffectmax=360allows duplicate heading at 0 vs 360. Step 4.WaypointListdrag 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.WaypointList.calculatedPointInfo[i]silently degrades to alt-only label if length mismatches; tightly coupled toFlightsPagekeeping arrays in lockstep.AltitudeChartpulls every chart.js controller viachart.js/auto(bundle bloat); colours are duplicated as hex literals (drift from theaz-*Tailwind tokens). Step 8.AltitudeDialognaming: file is "AltitudeDialog" but the dialog covers lat / lng / altitude / purpose. Port-vestige frommission-planner/. Rename toWaypointDialogin Step 8.flightPlanUtils.tsis single-file — newGuid + geo + weather + battery + parser + mock all together. Splitting intogeo.ts/weather.ts/battery.ts/mockAircraft.tsis a Step 8 SRP candidate.FlightsPage.handleSavedeletes all existing waypoints then recreates — N+M sequential PUT/DELETE round-trips, not transactional, no progress UI, partial failure leaves the flight half-saved.FlightsPage.handleSavebody 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 §3CreateWaypointRequestrequires{ Geopoint: {Lat, Lon, MGRS}, Source: WaypointSource, Objective: WaypointObjective, OrderNum, Height }. Mismatches: (a) lat/lon not nested underGeopoint; (b) field isorder, spec isOrderNum; (c)Source,Objective,Heightnot sent at all; (d) UI sendsnamewhich the spec does not define onWaypoint(Waypointinterface insrc/types/index.ts:76inventsname). 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 foraltitudeandmetawhich have no place in the current spec.FlightsPage.useEffectbootstrapsgetMockAircraftParams()as the activeaircraftregardless of what the backend returns — the dropdown choice is cosmetic for now. Real wiring is a Step 6 / Step 8 follow-up.- 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. handleImportsilently drops the file picker if the user cancels (if (!file) return) — fine. ButhandleJsonSave's catch usesalert(...)for a UX-grade error — replace with the project's modal/toast pattern in Step 4.MapPointpopup 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 })._iconcast leaks Leaflet internals.DrawControlregisters globalmousedown/mousemove/mouseupon the map while a draw mode is active and disablesmap.draggingfor the duration — fine, but no Esc-to-cancel mid-draw.FlightContextceiling:FlightsPagereadsflightsfromuseFlight()which fetches withpageSize=1000(already flagged inFlightContextdoc). Won't surface here, butselectFlightis fire-and-forget — if the PUT to/api/flights/selectfails 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.htmlbut 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 / 5–7 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.