# 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`).
