[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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 00:38:49 +03:00
parent da0a5aa187
commit 510df68bcf
84 changed files with 13065 additions and 0 deletions
@@ -0,0 +1,106 @@
# 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 KEY**`flightPlanUtils.ts:60`. HIGH severity. Step 4 source-code fix; upstream rotation is a parallel user task.
2. **`flightPlanUtils.calculateAllPoints` does N sequential `await`s** 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.