- storage.ts: add sessionStore wrapper (getRaw/setRaw/delete/clear) so
transientPrefill + ScheduleStartPage tests don't trip the
no-restricted-globals rule.
- transientPrefill.ts + ScheduleStartPage.test.tsx: use sessionStore.
- closestFlight.ts: hoist bracket-index key so no newline-before-[ ASI.
- Test files: hoist typeof import(...) into named type alias with
type-only namespace import.
- Drop unused imports: FlightCard (Link, languageToLocale),
OnlineBoardDetailsPage (operatingCarrier),
ScheduleSearchPage (FlightList, inline import() types),
PageLayout (FeedbackButton).
- Drop react-hooks/exhaustive-deps disable comments for a rule not
registered in eslint.config.js.
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.
- 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 8px margin below the day-tabs strip so the sticky card touches
the results frame, matching Angular's layout.
Add page-layout__scroll-overlay — a fixed 25px dark-blue strip across
the top of the viewport — so flight rows scrolling past the 20px-sticky
date row don't peek through the gap above it.
Realign DayTabs range to match Angular's boardSearchFrom=1 /
boardSearchTo=7 defaults (today-1 through today+7). Previous 2/14
window left today off-center in the 7-tab page.
Rebuild DayTabs to mirror Angular's flat single-line tab strip: one label
per tab ('17 апр.' siblings, '19 апреля' active), 48px tall, 7 tabs per
page, brand blue label on light-blue background with white active cell.
Single Intl.DateTimeFormat call keeps Russian months in genitive case.
Drop the 60px sticky-content offset to 20px so the date strip aligns
with the left filter column (both already sticky at top:20px).
Correct SEO.FLIGHTS_MAP translation key to SEO.FLIGHTS-MAP.MAIN — the
underscored path never existed in the locale files, so the browser tab
title on /{lang}/flights-map fell back to the raw key.
Angular renders the breadcrumb trail on its own row above the H1 title.
React had them in the same flex row with justify-content:space-between,
which squeezed the breadcrumb column and forced 'Главная / Онлайн-Табло'
to wrap onto two lines. Switch the header-right container to column
layout so breadcrumbs and title stack vertically regardless of width.
Six more aria-labels that still read in English:
- CityAutocomplete clear button ('clear')
- CityAutocomplete picker trigger ('open regional picker')
- Breadcrumbs nav landmark ('breadcrumb')
- Timeline prev/next buttons ('Previous legs' / 'Next legs')
Routed through new SHARED.A11Y-* keys (translated into all nine
locales). This is screen-reader-only text but part of the parity
budget.
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.
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).
Replace 4-radio-button filter with PrimeNG-style accordion (2 tabs: Flight Number, Route).
Add swap button between departure/arrival in route filter, clear button on flight number input,
time selector in route filter, flight number validation with error tooltip.
Add SearchHistory component below filter, Breadcrumbs in page header, FeedbackButton stub,
ScrollUpButton for scroll-to-top. SeoHead already wired on start page route.
All tests updated to match new accordion structure.
PageTabs now reads the FEATURE_FLIGHTS_MAP flag directly via useFeatureFlag
instead of relying on a prop default, matching the Angular page-tabs pattern.
FlightsMapFilter uses PrimeReact AutoComplete and Calendar instead of plain
HTML inputs, with i18n labels. MapCanvas init effect uses refs to avoid
React exhaustive-deps warnings. Root layout imports leaflet CSS and
PrimeReact theme globally. Env schema accepts NODE_ENV "test" for vitest.
Enable flights-map tab by default (showFlightsMap=true) to match Angular
production config where flightsMap feature flag is true. The other three
items (tile icons, body background, popular-requests panel) were already
ported identically in the React SCSS.
Add blue background/hover styles to the search button matching Angular's
.color.blue button pattern. Fix page title width to calc(100% - 120px)
matching Angular layout. Conditionally render sticky-content wrapper
to avoid empty DOM nodes.
Port the Angular page-layout wrapper and flights-page-tabs navigation
to React, preserving identical DOM structure and CSS class names so
global SCSS styles apply without modification.