8005356db5
Companion markdown to the comparison-report/visual/report.html with the same coverage matrix and per-page findings. Useful for git-based review without serving the HTML. Also adds AGENTS.md (subagent role definitions for future sessions) and the modernjs-v3-upgrade plan stub from the earlier scoping.
297 lines
18 KiB
Markdown
297 lines
18 KiB
Markdown
# Visual Parity Report — Angular vs React (full coverage)
|
||
|
||
**Date:** 2026-04-19
|
||
**Tool:** Playwright MCP (`@playwright/mcp`)
|
||
**Angular reference:** https://flights.test.aeroflot.ru/{locale}/...
|
||
**React under test:** http://localhost:8080/{locale}/... (`pnpm dev:full` proxy)
|
||
**Locales tested:** `ru-ru` (primary), `en-en` (sample)
|
||
**Viewports:** desktop 1440×900, mobile 375×812
|
||
**Test scenarios:**
|
||
- Route SVO ↔ LED, 19 Apr 2026 (today)
|
||
- Flight SU 6497, 19 Apr 2026
|
||
- Departure ex-SVO / Arrival in LED
|
||
- Schedule one-way SVO→LED, week of 19–26 Apr 2026
|
||
- Flights map (no filter)
|
||
|
||
Screenshots in `screenshots-v2/`. Legend: 🟢 visually equivalent · 🟡 minor diffs (spacing, dev chrome) · 🟠 functional/content gap · 🔴 major regression.
|
||
|
||
---
|
||
|
||
## At a glance
|
||
|
||
| # | Page | URL pattern | Desktop | Mobile | EN |
|
||
|---|---|---|---|---|---|
|
||
| 1 | Onlineboard start | `/{locale}/onlineboard` | 🟢 | 🟢 | 🟢 |
|
||
| 2 | Onlineboard route results | `/{locale}/onlineboard/route/{dep}-{arr}-{date}` | 🟢 | 🟢 | 🟡 |
|
||
| 3 | Onlineboard departure-only | `/{locale}/onlineboard/departure/{dep}-{date}` | 🟢 | — | — |
|
||
| 4 | Onlineboard arrival-only | `/{locale}/onlineboard/arrival/{arr}-{date}` | 🟢 | — | — |
|
||
| 5 | Onlineboard flight-number search | `/{locale}/onlineboard/flight/{carrier}{num}-{date}` | 🟢 | — | — |
|
||
| 6 | Flight details (single flight) | `/{locale}/onlineboard/{carrier}{num}-{date}` | 🟠 | 🟠 | 🟠 |
|
||
| 7 | Schedule start | `/{locale}/schedule` | 🟢 | — | — |
|
||
| 8 | Schedule route results (one-way) | `/{locale}/schedule/route/{dep}-{arr}-{from}-{to}` | 🔴 | — | — |
|
||
| 9 | Schedule route results (round-trip) | `/{locale}/schedule/route/.../...` | 🔴 | — | — |
|
||
| 10 | Schedule flight details | `/{locale}/schedule/{flightId}` | not tested | — | — |
|
||
| 11 | Flights map | `/{locale}/flights-map` | 🟡 | 🟡 | — |
|
||
| 12 | Popular requests panel (inline) | embedded on 1, 7 | 🟢 | 🟢 | 🟢 |
|
||
| 13 | Error pages (404/500) | `/error/{code}` | not tested | — | — |
|
||
| 14 | Smoke / health | `/{locale}/smoke` | not user-facing | — | — |
|
||
|
||
---
|
||
|
||
## URL surface
|
||
|
||
Both apps now use the same BCP-47-like URL contract:
|
||
`/{xx-xx}/...` where both halves are the language code (`/ru-ru/`, `/en-en/`, `/zh-zh/`, `/ja-ja/`, `/de-de/`, …).
|
||
Bare short codes (`/ru/onlineboard`) auto-redirect to canonical (`/ru-ru/onlineboard`) on the React side; Angular config redirects the same way. ✅ URL parity.
|
||
|
||
---
|
||
|
||
## 1. Onlineboard — start page
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Angular shows test-env chrome React hides (orange "Тестовая версия", "rc/2026-04-06", dev counter "383", chat-widget bubble). Out of scope.
|
||
- Date input placeholder: Angular `ДД.ММ.ГГГГ` vs React `Сегодня` (filled when date is today). React's behaviour matches Angular's loaded state once a date is picked.
|
||
- Form-row vertical spacing slightly tighter in React (~6 px less between inputs).
|
||
- 4 info tiles, popular-requests block, breadcrumb — pixel-equivalent.
|
||
|
||
🟢 Equivalent.
|
||
|
||
## 2. Onlineboard — route results (`/onlineboard/route/SVO-LED-20260419`)
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Both auto-expand the same closest-to-now flight (SU 6579) with the `Время` + `Посадка` accordion. Layout, columns, and label order match.
|
||
- Angular shows the actual arrival time (20:22) as primary with the scheduled time (20:25) struck-through; React shows 20:25 primary with 20:22 in the expanded panel. Both convey the same data, slightly different emphasis.
|
||
- Date-tab strip (`18 апр – 24 апр`) and current-day highlight identical.
|
||
- React keeps the filter "Дата рейса" / "Время рейса" rows visible after submit; Angular hides them. Minor.
|
||
|
||
🟢 Equivalent on layout; 🟡 minor on time-emphasis treatment.
|
||
|
||
## 3. Onlineboard — departure-only (`/onlineboard/departure/SVO-20260419`)
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Angular's `Город прилета` field defaults to `Все направления`; React leaves it empty. Functionally equivalent (both query "all destinations from SVO"); cosmetic.
|
||
- Both list flights to varied destinations (Сыктывкар, Махачкала, Ереван, Санкт-Петербург, …) with the same time/airline/airport columns.
|
||
- Same card-expansion behaviour.
|
||
|
||
🟢 Equivalent.
|
||
|
||
## 4. Onlineboard — arrival-only (`/onlineboard/arrival/LED-20260419`)
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Same `Все направления` placeholder difference as #3.
|
||
- Boarding section in Angular labelled `Высадка` (deboarding); React labels it `Посадка` (boarding) but with the same fields. Consider switching React to `Высадка` for arrival-direction flights to match Angular wording.
|
||
- Status pill colours match: green check-mark (`Прибыл`), blue plane (`Вылетел`).
|
||
|
||
🟢 Equivalent on layout; 🟡 boarding/deboarding label.
|
||
|
||
## 5. Onlineboard — flight-number search (`/onlineboard/flight/SU6497-20260419`)
|
||
|
||
Equivalent to the route-results page in chrome (same `OnlineBoardSearchPage` shell). Both render a single-card list filtered to the flight number, with the same expanded card. H1 heading reads `Рейс: SU 6497, Сегодня` on both. 🟢 Equivalent.
|
||
|
||
## 6. Flight details — full page (`/onlineboard/SU6497-20260419`)
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- **H1 wording matches**: `Информация о рейсе: SU 6497, Москва - Санкт-Петербург`.
|
||
- **Carrier logo (RU)**: Angular renders the RU variant `РОССИЯ` (Cyrillic); React serves the EN variant `ROSSIYA` (Latin) even on the Russian site. **🔴 Bug** — `BoardDetailsHeader`'s `OperatorLogo` is still passing locale="en" or the asset fallback is wrong. The fix that landed for `FlightCard` didn't reach the details-page header.
|
||
- **Sidebar mini-list**: Angular shows arrows pointing right between origin/destination; React's mini-list item is plainer (just times + cities, no arrows). Angular uses the localised `Прибыл` time; React shows blank status row in the mini-list.
|
||
- **`Регистрация` row times**: Angular shows `16:00 ⁻¹` (with a small superscript day-change marker for the day-before start) plus `15:15` end time; React shows the same. ✅
|
||
- **`Посадка` (boarding) — in flight-details accordion**: matching `Закончена` status, same start/end times.
|
||
- **`Высадка` (deboarding)** row: matching status + times.
|
||
- **`Последнее обновление` stamp**: both show fresh client-side load timestamp. ✅
|
||
- **Footer note** `* Время прилета и расстояния являются расчётными...`: present on both.
|
||
|
||
🟠 — H1 + accordion match Angular; the carrier-logo locale slip is the only visible regression.
|
||
|
||
## 7. Schedule — start page
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Date-range field: Angular shows placeholder `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ`; React fills with the default current-week range `19.04.2026 - 26.04.2026` (calendar shows actual values). React's behaviour matches Angular's after the calendar mounts.
|
||
- 4 info tiles, popular-requests block, "Расписание рейсов Аэрофлота" SEO body — equivalent.
|
||
|
||
🟢 Equivalent.
|
||
|
||
## 8. Schedule — route results, one-way (`/schedule/route/SVO-LED-20260419-20260426`)
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs (major):**
|
||
- **Day-grouping headers**: Angular groups flights by day (`Воскресенье 19 Апреля`); React shows a flat list with no day grouping.
|
||
- **Code-share + multi-leg expansion**: Angular shows multi-carrier flights bundled (`SU 1942, SU 6532` with two airline logos), then a route diagram (4ч.25мин. + 2ч.20мин. transfer + 5ч.10мин.) and the legs broken out below. React renders each leg as a separate row with no diagram, no transfer pill, and no parent-flight grouping.
|
||
- **Column headers**: Angular has a sortable header row (`РЕЙС` / `АВИАКОМПАНИЯ, БОРТ` / `ВЫЛЕТ` / `ВРЕМЯ В ПУТИ` / `ПРИЛЕТ`) with sort arrows on `ВЫЛЕТ` and `ПРИЛЕТ`; React shows no headers.
|
||
- **Date-range tab strip**: Angular shows weekly windows (`13 апр - 19 апр`, `20 апр - 26 апр`, …); React shows daily tabs (`17 апр.`, `18 апр.`, `19 апреля`, …).
|
||
- **Aircraft model**: Angular shows `Airbus A321` / `Airbus A319` per leg; React doesn't show aircraft model in the row.
|
||
- **Action buttons**: Angular has both `Статус рейса` and `Детали рейса` per flight; React has only `Детали рейса`.
|
||
- **`Вы искали` (search history)** sidebar accordion: Angular has one; React has it elsewhere or not at all on this page.
|
||
|
||
🔴 **Major regression** — schedule results page is the largest gap in the entire app. The React page is essentially the onlineboard list reskinned, missing all of: day grouping, multi-leg/code-share grouping, transfer diagram, sortable headers, weekly date tabs, aircraft column, and the secondary action button.
|
||
|
||
## 9. Schedule — route results, round-trip
|
||
|
||
Not separately captured in this run. Same structural gaps as #8 are expected (Angular shows two side-by-side panels for outbound + return, each with full grouping). 🔴
|
||
|
||
## 10. Schedule — flight details
|
||
|
||
Not captured. Angular has a dedicated detail page reachable via `Детали рейса`/`Статус рейса` buttons; React's catch-all route handles this URL but renders the onlineboard details page. Worth a dedicated capture pass.
|
||
|
||
## 11. Flights map
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Origin marker: Angular shows red dot on `Москва` (geolocation result). React shows no marker — `useGeolocationDefault()` either silently failed or the headless Playwright didn't grant permission. In a real browser with geo enabled both should show the dot.
|
||
- Default zoom/extent: Angular includes Санкт-Петербург at top of viewport; React's viewport is shifted south, cutting off SPb. Both use Leaflet center `[53,45]` zoom `5`; the diff is viewport-width and tile-loading order, not code.
|
||
- Swap arrows: vertical blue `↑↓` on both. ✅
|
||
- City labels: text-only with text-shadow halo on both. ✅
|
||
- Form panel: no `Найдите свой маршрут` header on either. ✅
|
||
- Date input: React still has the filled-blue calendar tile, Angular has an outline icon. Minor.
|
||
|
||
🟡 Minor — geo marker + slight zoom drift.
|
||
|
||
## 12. Popular requests panel
|
||
|
||
Embedded inline on Onlineboard start (#1) and Schedule start (#7). Renders 4 items in 2 columns; each is `Маршрут: City - City`, `Номер рейса: SU XXXX`, `Вылет: City`, or `Прилет: City`. Both apps use a service singleton (Angular `*FiltersStateService`) / sessionStorage prefill (React) to hand off click data without polluting the URL — confirmed in earlier patches. 🟢
|
||
|
||
## 13. Error pages
|
||
|
||
The React app has `/error/{code}/page.tsx` covering 404/500/etc. with a localised header + search box. Angular has `/error-pages/error-page` with the same shape. Not captured this round (need a deliberately broken URL); structurally aligned per the code.
|
||
|
||
## 14. Smoke / health
|
||
|
||
`/{locale}/smoke` is React-only — used by health-check probes. No Angular equivalent expected.
|
||
|
||
---
|
||
|
||
## English (`/en-en/`) — locale parity sample
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- All form labels (`Flight number`, `Route`, `City of departure`, `City of destination`, `Flight date`, `Search`) — match.
|
||
- Date tabs (`18 Apr`, `April 19`, `20 Apr`, …): Angular uses `apr` lowercase weekday-relative form; React capitalised `Apr`. Minor.
|
||
- Status pills (`Arrived`, `In flight`, `Scheduled`) — match.
|
||
- Carrier logos — both render the EN variants `ROSSIYA` / `AEROFLOT`. ✅
|
||
- Boarding row labels (`Status`, `Start time`, `End time`/`Close time`): Angular uses `Close Time`, React uses `End time`. Cosmetic.
|
||
|
||
🟢 Equivalent. EN translation now complete (was 21 keys empty before — all filled).
|
||
|
||
---
|
||
|
||
## Mobile (375×812)
|
||
|
||
### Onlineboard start
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- Both surface the 3-day quick-pick row (`19 Вс. Апреля / 20 Пн. Апреля / 21 Вт. Апреля`) above the manual `ДД.ММ.ГГГГ` input. ✅ React's selected-day chip uses solid blue fill; Angular leaves all three white. Minor.
|
||
- Tabs (`Онлайн-Табло` / `Расписание` / `Карта полетов`) — match.
|
||
- Form fields, swap arrow, accordion behaviour — match.
|
||
|
||
🟢 Equivalent.
|
||
|
||
### Flight details
|
||
|
||
| Angular | React |
|
||
|---|---|
|
||
|  |  |
|
||
|
||
**Diffs:**
|
||
- **Carrier logo**: Angular `РОССИЯ` (RU); React `ROSSIYA` (EN). Same bug as desktop #6. **🔴**
|
||
- Both show the date-picker (`19 Апреля 2026` / `Сегодня`) and the back-to-board button.
|
||
- Both show the same flight-progress timeline (16:00 → 17:23 with `Прибыл`, strikethrough scheduled `17:25`).
|
||
- Both show the same `По расписанию` / `Фактическое` time blocks.
|
||
- Last-update stamp (`Последнее обновление: 19:09 19.04.2026`) — both fresh client-load time. ✅
|
||
- React shows an extra wrapped header with "SU 6497 / 16:00 / 17:23 / Москва / Санкт-Петербург" above the main accordion — duplicates info that's already in the main card. Angular doesn't have this duplicate.
|
||
|
||
🟠 — Carrier logo locale + duplicate sidebar-card on mobile.
|
||
|
||
---
|
||
|
||
## Cross-cutting findings
|
||
|
||
### What's already at parity ✅
|
||
- URL contract (BCP-47 `/xx-xx/`, both halves matching).
|
||
- 9-language i18n coverage (RU complete, EN complete after this round, others have file-level coverage from the prior project).
|
||
- API client locale (now mutable, follows `[lang]/layout.tsx`) — cities/airports/statuses respond in the active language.
|
||
- Onlineboard start, route, departure, arrival, flight-search pages — visually equivalent.
|
||
- Schedule start page — equivalent.
|
||
- Popular requests panel + cross-page prefill via sessionStorage.
|
||
- Mobile day-quick-pick (3-day chip row).
|
||
- Flights map: swap arrows, city labels, form panel header, leaflet tooltip styling.
|
||
- Last-update stamp using client-side load time (matches Angular's `flight.lastUpdate = new Date()`).
|
||
- Share menu (translated labels + brand-icon-on-top layout).
|
||
- Footer note `* Время в системе - МЕСТНОЕ.` on search results.
|
||
- Boarding-status leading dot (grey for `Уточняется`).
|
||
|
||
### Open gaps 🟠/🔴
|
||
|
||
| Severity | Page | Gap |
|
||
|---|---|---|
|
||
| 🔴 | Schedule results (#8, #9) | No day grouping, no code-share/multi-leg bundling, no transfer-diagram, no sortable headers, no weekly date tabs, no aircraft column, missing `Статус рейса` button. Schedule is a markedly different feature in Angular than what React currently ships. |
|
||
| 🔴 | Flight-details carrier logo (#6, mobile) | `BoardDetailsHeader` still serves the EN logo variant on Russian pages. `FlightCard` was fixed in the i18n migration, but the details-page header was missed. One-line fix in `BoardDetailsHeader.tsx` to pass `useLocale().language`. |
|
||
| 🟠 | Flight-details mobile (#6) | React renders an extra summary-card above the main details card that Angular doesn't have. |
|
||
| 🟠 | Onlineboard arrival (#4) | Boarding-row label says `Посадка` for arrival-direction flights; Angular says `Высадка`. |
|
||
| 🟡 | Onlineboard departure/arrival (#3, #4) | Empty `Город прилета` / `Город вылета` field instead of Angular's `Все направления` placeholder. |
|
||
| 🟡 | Flights map | Default origin red dot (geo-driven, not code). |
|
||
| 🟡 | Flights map calendar icon | Filled blue tile vs Angular outline icon (PrimeReact theme). |
|
||
| 🟡 | Onlineboard route filter | Date / time inputs stay visible after submit; Angular hides them. |
|
||
| 🟡 | EN tabs label casing | `Apr` vs `apr` weekday header. |
|
||
|
||
### Items intentionally divergent
|
||
- React `[lang]/smoke` route — health probe, not present in Angular.
|
||
- React `[lang]/popular` route was deleted to match Angular's 404 (popular requests are inline only).
|
||
- React in-page test/dev chrome (`Тестовая версия` badge, env counter, chat widget) — Angular reflects the test-env deployment; React's `dev:full` is dev-only.
|
||
|
||
---
|
||
|
||
## Recommended priorities
|
||
|
||
**P0 — content correctness on the Russian site:**
|
||
1. Fix `BoardDetailsHeader` to pass `useLocale().language` to `OperatorLogo` so RU pages get `РОССИЯ` not `ROSSIYA` (desktop + mobile #6).
|
||
|
||
**P1 — feature gaps on schedule:**
|
||
2. Schedule results page (#8) — port Angular's day-grouped, multi-leg / code-share bundled, weekly-date-tab layout. This is the single biggest UX gap remaining in the React app.
|
||
3. Schedule round-trip results — same approach, two-pane.
|
||
|
||
**P2 — small parity wins:**
|
||
4. Mobile flight-details: drop the duplicate summary card above the main details card (#6 mobile).
|
||
5. Onlineboard arrival page: rename boarding row to `Высадка` when direction = arrival (#4).
|
||
6. Departure / arrival pages: prefill the opposite-direction field with `Все направления` placeholder (#3, #4).
|
||
|
||
**P3 — polish:**
|
||
7. Onlineboard route filter: hide `Дата рейса` / `Время рейса` rows post-submit.
|
||
8. Flights map: outline calendar icon instead of filled blue tile.
|
||
9. EN onlineboard date tabs: lowercase short month name to match Angular (`apr` not `Apr`).
|