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.
18 KiB
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 variantROSSIYA(Latin) even on the Russian site. 🔴 Bug —BoardDetailsHeader'sOperatorLogois still passing locale="en" or the asset fallback is wrong. The fix that landed forFlightCarddidn'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 shows16:00 ⁻¹(with a small superscript day-change marker for the day-before start) plus15:15end 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 range19.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 6532with 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 A319per 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]zoom5; 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 usesaprlowercase weekday-relative form; React capitalisedApr. 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 usesClose Time, React usesEnd 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); ReactROSSIYA(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 scheduled17: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]/smokeroute — health probe, not present in Angular. - React
[lang]/popularroute 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'sdev:fullis dev-only.
Recommended priorities
P0 — content correctness on the Russian site:
- Fix
BoardDetailsHeaderto passuseLocale().languagetoOperatorLogoso RU pages getРОССИЯnotROSSIYA(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).





















