From 8dde2db9d2e44e86532b88c0b41c77b79150011e Mon Sep 17 00:00:00 2001 From: gnezim Date: Wed, 15 Apr 2026 09:44:01 +0300 Subject: [PATCH] Add Phase 4 master plan and install leaflet dependency Write the flights-map master plan covering sub-plans 4A-4D. Add leaflet + @types/leaflet to support the map canvas component. --- .../2026-04-15-phase-4-flights-map-master.md | 77 +++++++++++++++++++ package.json | 2 + pnpm-lock.yaml | 23 ++++++ 3 files changed, 102 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-15-phase-4-flights-map-master.md diff --git a/docs/superpowers/plans/2026-04-15-phase-4-flights-map-master.md b/docs/superpowers/plans/2026-04-15-phase-4-flights-map-master.md new file mode 100644 index 00000000..9280d179 --- /dev/null +++ b/docs/superpowers/plans/2026-04-15-phase-4-flights-map-master.md @@ -0,0 +1,77 @@ +# Phase 4 -- Flights Map (Master Plan) + +## Goal + +Port the Angular `flights-map` feature (single `/flights-map` route) to the React/ModernJS codebase as a Module Federation remote component. The map uses Leaflet for interactive marker/polyline rendering and is behind the `flightsMap` feature flag. + +## Angular analysis summary + +- **Route:** `/flights-map` (start page with embedded Leaflet map) +- **Feature flag:** `flightsMap` (env-based) +- **APIs:** `destinations/v1` (search routes by departure/arrival), `days/.../flights-map/v1` (calendar availability) +- **Leaflet map:** markers (blue default, blue-small zoom-dependent, orange selected), polylines (solid direct, dashed connecting), popups, tooltips, zoom-aware layer visibility +- **Components:** start page container, map body (sole Leaflet consumer), filter panel, meta tags, loader/empty overlays +- **No SignalR** -- data is static, fetched on filter change +- **SEO:** meta tags only in Angular; design spec requires JSON-LD `WebPage` schema + +## Sub-plans + +### 4A -- Types + API + Hooks + +**Files:** `src/features/flights-map/types.ts`, `api.ts`, `api.test.ts`, `hooks/useFlightsMapSearch.ts`, `hooks/useFlightsMapCalendar.ts` + +- Define `IFlightRoute`, `IMapMarker`, `IMapPolyline`, `FlightsMapSearchParams`, `FlightsMapCalendarParams` +- Define `IDestinationsResponse`, `IFlightsMapDaysResponse` +- API: `searchDestinations(client, params)` -> `GET destinations/1`, `getFlightsMapCalendar(client, params)` -> `GET days/{date}/200/{route}/flights-map/v1` +- Hooks: `useFlightsMapSearch(params)`, `useFlightsMapCalendar(params)` +- Feature flag: `useFeatureFlag("flightsMap")` via env config +- TDD: ~10 tests for API functions + +### 4B -- Leaflet Map Wrapper + +**File:** `src/features/flights-map/components/MapCanvas.tsx` + +- **Only file that imports `leaflet`** (design spec constraint) +- Wrapped in `React.lazy()` + `` (SSR-safe) +- Accepts `markers`, `polylines`, `onMarkerClick` etc. as props -- does not own state +- Renders markers (blue/orange icons), polylines (solid/dashed), popups, tooltips +- Great-circle arc calculation for polyline rendering +- Install `leaflet` + `@types/leaflet` +- No TDD (Leaflet requires real DOM) + +### 4C -- Route Pages + Feature Components + +**Files:** +- `src/routes/[lang]/flights-map/page.tsx` -- route page with SEO + feature flag guard +- `src/features/flights-map/components/FlightsMapStartPage.tsx` -- container (filter + map + loading/empty states) +- `src/features/flights-map/components/FlightsMapFilter.tsx` -- search filter (departure, arrival, connections, domestic/international toggles, date picker) +- `src/features/flights-map/components/ClientOnly.tsx` -- SSR-safe wrapper + +### 4D -- SEO + JSON-LD + Parity + +**Files:** +- `src/features/flights-map/seo.ts` -- `buildFlightsMapSeo(t, locale, canonicalOrigin)` +- `src/features/flights-map/json-ld.ts` -- JSON-LD `WebPage` schema builder +- `src/features/flights-map/seo.test.ts` -- ~5 integration tests +- Update barrel `src/features/flights-map/index.ts` +- Update `src/mf/expose/FlightsMap.tsx` from stub to real + +## Dependency graph + +``` +4A (types/api/hooks) --> 4B (map canvas, needs types) + --> 4C (pages/components, needs hooks) +4A + 4B + 4C --------> 4D (SEO + barrel + MF expose update) +``` + +## Verification + +```bash +pnpm typecheck && pnpm lint && pnpm test && pnpm build:standalone +``` + +## Constraints + +- Do NOT touch `ClientApp/`, ASP.NET, `wwwroot/` +- No `Co-Authored-By` lines in commits +- ~8-12 commits total diff --git a/package.json b/package.json index 2e043bf1..8165c778 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "i18next-icu": "^2.0.0", "i18next-resources-to-backend": "^1.0.0", "intl-messageformat": "^10.0.0", + "leaflet": "^1.9.4", "lru-cache": "^10.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -48,6 +49,7 @@ "@testing-library/jest-dom": "^6", "@testing-library/react": "^16.3.2", "@testing-library/react-hooks": "^8.0.1", + "@types/leaflet": "^1.9.21", "@types/node": "^24.0.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 745a3509..4e719a7f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: intl-messageformat: specifier: ^10.0.0 version: 10.7.18 + leaflet: + specifier: ^1.9.4 + version: 1.9.4 lru-cache: specifier: ^10.0.0 version: 10.4.3 @@ -84,6 +87,9 @@ importers: '@testing-library/react-hooks': specifier: ^8.0.1 version: 8.0.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-test-renderer@19.2.5(react@18.3.1))(react@18.3.1) + '@types/leaflet': + specifier: ^1.9.21 + version: 1.9.21 '@types/node': specifier: ^24.0.0 version: 24.12.2 @@ -2711,6 +2717,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -2726,6 +2735,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/leaflet@1.9.21': + resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==} + '@types/loadable__component@5.13.10': resolution: {integrity: sha512-2/LjmgG1JcGPj7T3NViq7BB5cvOA0s63gL3Gv+FPULj2L+3ExWfsNQcsFPUIOoGsVUJeZxgNPf320JZDyxjtCQ==} @@ -4435,6 +4447,9 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + leaflet@1.9.4: + resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -9701,6 +9716,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/html-minifier-terser@6.1.0': {} '@types/istanbul-lib-coverage@2.0.6': {} @@ -9715,6 +9732,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/leaflet@1.9.21': + dependencies: + '@types/geojson': 7946.0.16 + '@types/loadable__component@5.13.10': dependencies: '@types/react': 18.3.28 @@ -11708,6 +11729,8 @@ snapshots: kind-of@6.0.3: {} + leaflet@1.9.4: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1