# 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 | |---|---| | ![](screenshots-v2/angular-onlineboard-start.png) | ![](screenshots-v2/react-onlineboard-start.png) | **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 | |---|---| | ![](screenshots-v2/angular-onlineboard-route.png) | ![](screenshots-v2/react-onlineboard-route.png) | **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 | |---|---| | ![](screenshots-v2/angular-onlineboard-departure.png) | ![](screenshots-v2/react-onlineboard-departure.png) | **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 | |---|---| | ![](screenshots-v2/angular-onlineboard-arrival.png) | ![](screenshots-v2/react-onlineboard-arrival.png) | **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 | |---|---| | ![](screenshots-v2/angular-flight-details.png) | ![](screenshots-v2/react-flight-details.png) | **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 | |---|---| | ![](screenshots-v2/angular-schedule-start.png) | ![](screenshots-v2/react-schedule-start.png) | **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 | |---|---| | ![](screenshots-v2/angular-schedule-route.png) | ![](screenshots-v2/react-schedule-route.png) | **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 | |---|---| | ![](screenshots-v2/angular-flights-map.png) | ![](screenshots-v2/react-flights-map.png) | **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 | |---|---| | ![](screenshots-v2/angular-en-onlineboard-route.png) | ![](screenshots-v2/react-en-onlineboard-route.png) | **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 | |---|---| | ![](screenshots-v2/angular-mobile-onlineboard-start.png) | ![](screenshots-v2/react-mobile-onlineboard-start.png) | **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 | |---|---| | ![](screenshots-v2/angular-mobile-flight-details.png) | ![](screenshots-v2/react-mobile-flight-details.png) | **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`).