Replace the inline 'Invalid parameters' fallbacks and the framework's
default '404' text with the existing Aeroflot 404 screen. Unknown
locale, malformed flight/route/station params, and unmatched URLs
(including bad paths like onlineboard//route/...) now all land on the
same ErrorPage component.
Angular's schedule renders 7 day pills ("20 пн … 26 вс") spanning the
active week, not weekly date ranges. Match that: tabs now render the
seven days of selectedMonday's week with day-num + weekday-abbr stack.
Prev/next arrows shift by full weeks. Day clicks scroll to the
matching day group in DayGroupedFlightList for the schedule UX.
Schedule list day accordion stays collapsed by default but auto-
opens the day matching today's date when it's in the visible week
window — mirrors Angular's p-accordion default-active behaviour
where today's flights are visible without a click. The user can
still collapse it; we never re-open after that for the same date.
Visual-diff URLs were hardcoded to a past date with the wrong React
URL format (Angular path-style /AAQ/16042026 instead of React's
single-segment /AAQ-20260420-00002400). Switch to dynamic
yyyyMMdd of today for onlineboard pages and Mon→Sun of the current
week for schedule. Schedule-route diff dropped from ~91% to ~28%
on desktop after these two fixes.
Match Angular's p-accordion default-collapsed state on the schedule
results page. State now tracks expanded days (default empty)
instead of collapsed days (default empty), so the initial render
shows day headers only and the user clicks to reveal flights.
Schedule details page used to show only a one-line FlightCard and
stop. Reuse ScheduleFlightBody so each flight in the chain renders
the same per-leg layout the schedule results page uses (route
summary, leg cards, transit pill, share/Купить/Детали рейса
actions). Add a `Вернуться к Расписанию` back link to the header.
While here, fix the SEO title key — buildScheduleDetailsSeo was
calling SEO.SCHEDULE.DETAILS.TITLE with `flights={...}`, but the
i18n bundle only defines SEO.SCHEDULE.FLIGHT-DETAILS.TITLE with
`flightNumber={...}`. The unresolved key was leaking into the
document title as "SEO.SCHEDULE.DETAILS.TITLE".
Round-trip schedules used to render outbound and inbound lists
stacked. Mirror Angular's schedule-direction-switch: keep both
fetches running, but render only the active direction's list and add
a button group to the sticky header that swaps which one is shown.
WeekTabs track the active direction's week independently, and tab
navigation updates whichever direction is currently active.
Each schedule + onlineboard search now records itself into the
existing useSearchHistory localStorage hook, with a structured
params payload (departure/arrival/dates/flightNumber). The
SearchHistory sidebar renders the rich Angular layout: clock or
plane icon, optional sub-title (e.g. "Расписание рейсов, в одну
сторону"), city pair, and date range, with inbound dates appended
for round-trip searches.
Schedule flight cards now expand into the rich Angular layout instead
of the online-board time/transition rows. Mirrors connecting-flight-
body / multi-flight-body: horizontal timeline summary, per-leg card
with section number + flight number + operator + aircraft + dep/arr
times + leg duration + stations, transfer-inline-extended pill
between legs (Пересадка, ground time, transit city), and the actions
row (share, Купить, Детали рейса).
Wired via a renderExpandedBody render prop on FlightCard/FlightList so
ui/flights doesn't need to know about schedule-specific bodies.
Replace OnlineBoardFilter on schedule pages with a dedicated
ScheduleFilter that matches Angular's schedule-filter:
- Город вылета / Город прилета with swap arrows
- 'Показать расписание на' date range picker
- 'Время вылета' time slider
- 'Только прямые рейсы' checkbox (sets connections=0)
- 'Показать обратные рейсы' checkbox
- 'Показать расписание' submit button (blue, full-width)
The OnlineBoardFilter accordion (Номер рейса + Маршрут tabs) is no
longer rendered on schedule pages — Angular only ships flight-number
search on the online-board side.
Add sort arrows on ВЫЛЕТ / ВРЕМЯ В ПУТИ / ПРИЛЕТ headers — clicking
toggles ascending/descending order; clicking again clears the sort.
Day groups (Понедельник 20 апреля, etc.) are now collapsible via the
header chevron — matches Angular's p-accordion structure where each
day is an accordionTab. Default state expanded.
- Connecting (multi-leg via transit) flights are now folded into a
synthetic MultiLeg shape with combined flight numbers (SU 6188,
SU 6233) and per-leg airline logos, matching Angular's
schedule-list-flight-header.
- Schedule grid now uses Angular's 8-column layout
(80/120/100/240/100/100/240/16). The middle status icon is
replaced by a duration column with the blue clock icon and
'3ч. 48мин.' / '4h 19m' formatting.
- Multi-leg airline logos use the round badge variant (separate
round.png assets) so two carriers fit side-by-side without overlap.
- Action buttons removed from collapsed rows — Angular only shows
flight-actions in the expanded body. Added chevron column for
every schedule card and made schedule cards expandable by default.
- Removed 'Туда: MOW → KUF' subhead from outbound section, matching
Angular's bare flight list under the column header.
ScheduleStartPage now starts dateFrom/dateTo as null so the input
shows the `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ` placeholder Angular ships
instead of pre-filling with the current week. The submit handler
defaults to current-week range when the user submits without
picking dates, preserving the legacy "find this week" UX.
Same pattern as the onlineboard date fix.
- ScheduleSearchPage: H1 now reads `Расписание по маршруту: …`
using SCHEDULE.SCHEDULE-BY-ROUTE — the existing onlineboard
variant was leaking through. Matches Angular's schedule-search
title-bar verbatim.
- DayGroupedFlightList: render a non-sortable column header bar
above the list with `Рейс / Авиакомпания, борт / Вылет / Время
в пути / Прилет`. Mirrors Angular's schedule-list-flight-header
column row. Sort arrows still TBD.
- New i18n keys: SCHEDULE.COL-FLIGHT, COL-AIRLINE, COL-DEPARTURE,
COL-DURATION, COL-ARRIVAL (RU + EN both filled).
Schedule-route mismatch now 11.99% (was 12.47% pre-heading fix).
- flights-map: default departure to Москва (MOW) when geolocation
doesn't yield a city. Mirrors Angular which seeds the orange
marker on Moscow regardless of geo permission. Hook now has two
effects — a synchronous MOW fallback that fires once dictionaries
load, and the existing geo callback that may upgrade to a closer
city when permission is granted.
- Schedule: introduce DayGroupedFlightList. Buckets the flat result
list by scheduled-departure date and renders each group under a
`Воскресенье 19 Апреля`-style header (Intl-driven, weekday +
genitive month). Single-day result skips the grouping noise.
- Schedule: introduce WeekTabs. Replaces the daily DayTabs in the
schedule sticky-content with Monday-anchored 7-day windows like
`13 апр - 19 апр`, matching Angular's week-tabs component.
handleWeekChange recomputes both dateFrom (Monday) and dateTo
(Sunday) when the tab changes.
- Schedule: aircraft model now visible in the collapsed FlightCard
row when `direction === "schedule"` (Sukhoi SuperJet 100 / Airbus
A321 etc., per Angular's operator-logo-and-model column).
- FlightCard / FlightList: extend `direction` union with `"schedule"`.
Tests updated: useGeolocationDefault tests now assert the MOW
fallback fires when permission is denied / API missing / arrival
already set (was previously expected to no-op).
- URL surface now matches Angular: `/ru-ru/`, `/en-us/`, `/zh-cn/`, …
(BCP-47). Bare short codes still work — the [lang]/layout auto-
promotes them with a replace navigation. Internally everything that
needs the short language (i18n file lookup, API path segment,
Accept-Language header, dictionary `title[lang]` key, Intl
formatters) reads it through the new `useLocale()` hook, which
returns both `locale` (BCP-47) and `language` (short).
- ApiClient.locale is now mutable and is updated from the [lang]
layout whenever the URL locale changes — was hard-coded to "ru" in
the root layout before, so backend responses for /en/... still came
back in Russian. Cities / airports / flight statuses now arrive in
the active language.
- All 21 empty EN translation keys filled in (AIRPLANE.*, BOARD.
PREVIOUS-FLIGHT, SCHEDULE.FILE-NAME, SEO.SCHEDULE.*, SEO.FLIGHTS-
MAP.*, SHARED.FLIGHT-TRANSFER-PLURAL-*, SHARED.WEEK_FORMAT-WRONG)
so /en-us renders without falling back to raw keys.
- Added BOARD.LOAD-FAILED-TITLE / -MESSAGE keys (RU + EN) and removed
the three hardcoded Russian error strings from the search-page
error card.
- FlightStatus now reads `FLIGHT-STATUSES.{Status}` from i18n instead
of hardcoding the Russian labels.
- FlightCard's OperatorLogo now picks the en/ru carrier-logo variant
from `useLocale().language` instead of always passing "ru" — the
Aeroflot/Rossiya logos display in the active language where
variants exist.
- registerPrimeLocales(): all 9 supported languages get a PrimeReact
`addLocale` entry at module load (RU + EN hand-curated, others built
from Intl). Calendar/AutoComplete widgets switch with the URL.
- ErrorBoundary catches outside the i18n provider, so it now ships
its own minimal localised string table keyed off the URL locale —
no more "Something went wrong" leaking on the Russian site.
- Hreflang URLs now emit BCP-47 (`/en-us/...`) while `hreflang="en"`
stays the short Google-friendly form.
- Datetime helpers accept either short or BCP-47 locale (`isRussianLocale`)
so callers can pass through whatever the route hands them.
- Drop the React-only standalone /popular route (and its e2e
smoketest). Angular returns 404 for /ru-ru/popular; popular
requests are surfaced inline on onlineboard/schedule start pages
via PopularRequestsPanel (which stays). Matching the URL surface
is a contractual requirement for the MF remote.
- Replace ?tab/?departure/?arrival/?return query-string prefill on
the onlineboard and schedule start pages with a sessionStorage
transient slot. Mirrors Angular's OnlineBoardFiltersStateService /
ScheduleFiltersStateService cross-page singletons: URLs stay
clean of query strings, the start-page form still seeds itself
from a popular-request click, and a fresh page reload (which
bypasses the in-memory state in Angular) lands on a pristine form.
Same-page popular clicks remount the filter via key bump so the
useState initializers pick up the new prefill.
1. Route heading uses airport name when a code maps only to an airport
(SVO → 'Шереметьево') but prefers the city when the code is a city
too (LED → 'Санкт-Петербург', not 'Пулково'). Angular does the
same. Apply the new lookup order in both the onlineboard and
schedule search pages.
2. Append ', Сегодня' (or 'DD.MM.YYYY' for other dates) to the board
search heading, matching Angular.
3. Render the '+1' day-change marker on FlightCard even when only
scheduled times are known. Previously the fallback pulled the value
from `actualBlockOff/On.dayChange`, which is undefined for
scheduled-only flights — so overnight flights like SU 6805
(23:30 → 00:55 +1) showed no indicator. Read
`scheduledDeparture/Arrival.dayChange.value` when the actual block
time is missing.
4. Localize the PrimeReact Calendar widget: register a Russian locale
in [lang]/layout.tsx and set the active one on every locale change,
so 'Choose Date' reads 'Выбрать дату' and month/day names localize.
For a single-leg flight the FlightCard summary already shows
SVO → ALA with times; the extra 'SVO→ALA / 00:05 - 06:30' line
below was redundant noise. Render the per-leg row only when the
route has multiple legs (transfer case).
Upstream /schedule/details returns 400 when dates are sent as
yyyy-MM-dd; it wants the full ISO datetime (yyyy-MM-ddT00:00:00), same
as Angular's ApiFormatterService.formatDate output. Update the date
helper in ScheduleDetailsPage to append T00:00:00.
Verified with curl: request now returns 200 with the full flight
payload for SU1942 on 2026-04-18.
Upstream returned HTTP 400 for empty departure/arrival values. Angular
derives them from per-leg data when missing; easier fix here is to
simply not send them — the backend accepts the request without those
query params.
Schedule results previously had nothing in the left rail. Angular fills
it with the route filter + search history. Reuse the OnlineBoardFilter
component pre-populated with current URL params plus SearchHistory
below it, so the layout matches the board pages.
Angular's ScheduleApiService.getFlights uses GET with query params
and a half-open date interval (sends dateTo = requested end + 1 day).
React sent POST with a JSON body matching the params verbatim, which
returned HTTP 500 from the backend.
searchSchedule now:
- GETs flights/1/{lang}/schedule with ?departure=...&arrival=...
- extends dateTo by one day
- coerces connections to '0'/'1' and drops empty timeFrom/timeTo
Verified the call returns 200 with real flight data for
SVO→LED 2026-04-18..2026-04-25. Updated the api tests accordingly.
- ScheduleDetailsPage flight status: rendered raw
`flight.status` (English enum 'Scheduled'); now routes through
FLIGHT-STATUSES.* keys for localized Russian labels.
- FlightsMiniListItem status icon aria-label: same fix, so screen
readers say 'Запланирован' instead of 'Scheduled'.
Batch of fixes identified by the comparison audit:
Schedule search page (ScheduleSearchPage):
- Resolve IATA codes to city/airport names, so the H1 reads
'Маршрут: Шереметьево - Санкт-Петербург' instead of 'SVO - LED'.
- Breadcrumb trail now includes the human-friendly route as its
last entry.
Details page (OnlineBoardDetailsPage):
- Hide the 'Перелет N' leg header for single-leg flights (Angular
parity — that label is only meaningful for multi-leg routes).
- Translate the leg status through FLIGHT-STATUSES.* instead of
emitting the raw enum ('Cancelled' → 'Отменен', etc.).
- Humanize leg and total flying time through formatDuration so the
page reads '1ч 25м' rather than '01:25:00'.
Details meal panel (MealPanel):
- Use the same FOOD.* translation keys as Angular, so labels become
'Эконом класс / Комфорт класс / Бизнес класс / Специальное
питание'.
- Add the Special-meal icon + link (was stubbed out previously).
Accessibility:
- Route the English aria-labels through new SHARED.A11Y-* keys in
DayTabs pagination, FlightListSkeleton, ScrollUpButton and
PrintButton.
Breadcrumbs:
- Render the 'Главная' crumb as a link even when it's the only /
last item (it was dropping to plain text on start pages). Angular
always links it to aeroflot.ru.
Tests updated to assert the new translated labels and duration
formatting; 1258 tests passing.
The schedule-details view rendered bare content on the blue background
with no PageLayout, no PageTabs, no breadcrumbs, no white card — so
the flight-number heading, leg list and skeletons were invisible
against the page background. Wrap the component in PageLayout with
the schedule tab + breadcrumb + 'Информация о рейсе: SU 6805' title,
and put each branch inside a <section class='frame'> (error,
not-found, empty, success) so it sits on the same white card as the
board pages.
Also fix the same React key warning the board page had: the leg map
used leg.index as the key, which is undefined for Direct flights.
Fall back to the array index.
The two filter toggles ('Только прямые рейсы' / 'Показать обратные рейсы')
were rendering as the browser default 16px native checkboxes, which look
inconsistent across browsers and thin against the rest of the page.
Add an appearance:none + custom box with a blue tick mark when checked,
matching Angular's filter card.
Modern.js routes catch-all paths via a file literally named \`\$.tsx\`.
The name is framework-mandated but cryptic at a glance. Move the real
component to src/features/schedule/ScheduleDetailsCatchAllRoute.tsx
and make \$.tsx a 12-line re-export, so every grep/import/stack-frame
shows the readable name and the \$.tsx file stays just a framework
shim.
Previously the /schedule/route results page rendered everything on the
dark-blue background and dumped the raw 382-char bitmask from the
/days endpoint straight into the DOM. Changes:
- Wrap the page in PageLayout with PageTabs, breadcrumb and an H1
matching Angular ('Маршрут: SVO - LED').
- Swap the inline calendar loop for the shared <DayTabs> component
(weekday + day + month labels, paging arrows).
- Replace the broken comma-split parser in getScheduleCalendarDays
with the same bitmask-to-dates conversion the board endpoint uses,
so the calendar now yields real yyyy-MM-dd strings.
- Frame the results in <section class='frame'> so they sit on a white
card (matches the board pages).
- Translate the 'Invalid …' parameter errors on every route page to
SHARED.INVALID-PARAMS ('Неверные параметры URL.') and wire t() into
the two route files that still lacked useTranslation.
- Schedule round-trip page: 'Outbound' / 'Return' section headings now
use SCHEDULE.OUTBOUND / SCHEDULE.RETURN keys ('Туда' / 'Обратно' in
Russian).
- SignalR connection badge: 'Live' / 'Reconnecting…' / 'Offline' now
route through SHARED.CONNECTION-* keys ('Онлайн' / 'Соединение…' /
'Нет связи' in Russian). Applied on both board search and details
pages.
- Bag-belt label on leg stations switched to the existing
DETAILS.BAG_BELT key (SHARED variant doesn't exist).
- Integration tests updated to match the new badge keys under mocked t.
- FlightList empty-state, Operated-by label, details/schedule error
and not-found messages now route through i18n instead of hardcoded
English. Added BOARD.FLIGHT-NOT-FOUND, BOARD.LOAD-FAILED,
BOARD.OPERATED-BY, SHARED.RETRY to all nine locales.
- FlightStatus label now picks up the same colour as the plane icon
(red for Cancelled, green for Arrived/Landed, blue for In Flight,
orange for Delayed) — matches Angular's flight-status text treatment
so 'Отменен' reads at a glance.
- Tests updated to expect the translation keys under the mocked `t`.
Add buildSchedulePopularRequestQueryParams to convert Route/RouteWithBack
popular requests into URL search params. ScheduleStartPage now reads
departure/arrival/return from query params to initialize form state, and
the popular request click handler navigates with appropriate params for
both Schedule and Onlineboard request types.
Online board filter: use Angular sprite SVG for swap button, add dropdown
chevron to city AutoComplete inputs.
Schedule filter: add swap button between cities, replace two separate
date fields with single range Calendar matching Angular. Fix button text
to "Показать расписание", date label to "Показать расписание на".
Add dropdown chevrons to city inputs.
Null dates broke form submission — the handlers bail early when date
is null. Restore initialization to today/+7 days as before. The
Calendar placeholder prop still provides the format hint when cleared.
Error page: add search input bar, align flex/spacing to Angular SCSS mixins,
match button display and illustration flex.
Online board filter: default to "route" tab expanded (Angular defaults to
route, not flight number).
Start pages: remove extra breadcrumb items — Angular start pages show only
"Главная", not the page title.
PageLayout: hide FeedbackButton — Angular gates it behind
FEEDBACK_BUTTON_AVAILABLE feature flag (off by default).
Schedule: add SearchHistory below filter, time selector fields (timeFrom/timeTo)
for outbound and return flights, breadcrumbs, and bottom description section.
Flights Map: add FILTER_INFO message in filter panel, disabled states on
toggles when no departure/arrival selected (matching Angular logic), breadcrumbs,
and replace plain "Loading..." text with animated spinner matching Angular
loader-sheet component.