http://flights-ui.devwebzavod.ru/ru/flights-map was still hitting the
same-origin tile path after adding the k8s env: Modern.js renders the
<Suspense> fallback on the server (i18n isn't preloaded), so the route
component that reads getEnv() never actually runs during SSR. The page
hydrates client-side, where process.env is Rspack's empty stub and
MAP_TILE_URL is never set — getEnv() falls back to the default.
Move the value into window.__ENV__ instead:
- modern.config.ts: inline a <script> into html.tags that sets
window.__ENV__ = { MAP_TILE_URL: <value> } at SSR-server startup.
The snippet is authored once at server boot, so the HTML template
baked into dist/standalone/html/main/index.html always carries the
pod's tile URL.
- src/env/index.ts: merge window.__ENV__ on top of process.env so the
browser prefers the injected value (process.env only has NODE_ENV
after Rspack's polyfill).
- Dockerfile.react: accept MAP_TILE_URL as a build ARG and expose it
as ENV before `pnpm build:standalone`, so Modern.js picks it up when
building the HTML template. k8s env still flows into the Node SSR
process; the build-arg path guarantees correctness even when the
runtime env is stripped.
- deployment/build-docker.sh: forward MAP_TILE_URL through as a
build-arg (default keeps the same-origin path). CI on the
devwebzavod cluster can export MAP_TILE_URL=https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg
before running build-docker.sh and the resulting image will serve
tiles from the upstream the real Aeroflot ingress terminates.
The flights-front deploy repo ships k8s manifests at deployment/k8s/,
a sibling of Aeroflot.Flights.Front/. Previously the sync script only
copied the app source, so any env change landed on the k8s side had
to be hand-edited in the deploy repo and was never reflected back.
- Bring deployment/k8s/flights-ui.yaml into this repo (with the new
MAP_TILE_URL env pointing at flights.test.aeroflot.ru) so the
cluster config lives next to the code that reads it.
- sync-to-flights-front.sh resolves the deploy-repo root from the
target path and mirrors this repo's deployment/ directory there,
mkdir'ing and copying contents without wiping unrelated files.
- Bump step numbering (1/6..6/6) and the summary now lists the synced
deployment files in addition to the app files.
The flights-map tile URL was hardcoded as the same-origin path
'/map/api/tile/{z}/{x}/{y}.jpeg' (matching Angular's environment.ts).
On deployments where the ingress routes /map/api/** to the upstream
tile service (prod, flights.test.aeroflot.ru) this works. On
deployments without that rule (e.g. flights-ui.devwebzavod.ru) the
Modern.js SSR catch-all answers every tile URL with the SPA index
page, so Leaflet renders the marker + controls but never paints the
raster layer.
Expose the URL through MAP_TILE_URL env with the same-origin path as
the default, read it on the server route (where process.env is
available), and pass the resolved URL to FlightsMapStartPage as a
prop so the client bundle uses whatever the operator configured.
Prod and same-origin deployments stay unchanged; dev clusters can
point at an absolute URL like https://flights.test.aeroflot.ru/map/api/tile/...
instead.
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.
Angular keeps the 'Расписание рейса' collapse chevron on the right of the
header and styles the header like the Детали рейса row above it. React
was rendering the PrimeReact chevron on the LEFT with its own pill style.
Swap to the same lightweight accordion markup the details block uses so
both collapses look identical.
Angular's details breadcrumb trail is just 'Главная / Онлайн-Табло'
(BOARD.TITLE with capital Т) — the flight number itself is NOT a
breadcrumb entry. React was using the lowercase 'Онлайн-табло'
translation and appending 'SU 6272'. Align both the leaf text and the
list depth with Angular.
Angular's captioned-time-group renders 'UTC {{ utc }}', producing
'UTC +03:00' on screen. React was emitting 'UTC+03:00' without the
separator, making the time details read slightly differently. Insert a
U+00A0 non-breaking space between 'UTC' and the signed offset so the
time-table values ('15:30 UTC +03:00 18.04.2026') line up with Angular.
- formatDuration(locale='ru') now emits 'Xч. Xмин.' (and 'Xд. Xч. Xмин.')
with trailing dots, matching Angular's DurationPipe + SHARED.SHORT-HOUR
translations. Every 'В пути', 'До прилета', and 'Время в пути' label on
the details page now reads identically to Angular.
- FlightSchedule shows the SCHEDULED duration (dep→arr from the timestamps)
instead of the actual flyingTime the API reports, so the Расписание рейса
row reads '1ч. 30мин.' for a 15:30→17:00 schedule even after an early
landing. The Вылет / Прилет columns also surface the 'UTC+HH:MM' offset
below each time, matching the Angular layout.
- Relabel the meal row 'Питание на борту' (SHARED.FOOD) instead of the
shorter 'Питание' (DETAILS.MEAL) Angular stopped using.
- Replace AircraftPanel's vertical label/value table with a horizontal
strip of (Название | Количество мест | Эконом | Комфорт | Бизнес |
Предыдущий рейс) cells to match flight-details-airplane layout.
- Render the '* Время в системе - МЕСТНОЕ.' note inline after the last
visible transition row (Регистрация/Посадка/Высадка) inside the
Детали рейса accordion, dropping the separate footer-notes block —
Angular anchors the note exactly there.
- Rework FlightSchedule body into a 3-column grid (Вылет по расписанию |
Прилет по расписанию | Время в пути) and humanize flyingTime '1:19' →
'1ч 19м' so the value reads consistently with the rest of the page.
Details page calls useOnlineBoard to populate the sibling mini-list,
passing empty-string params when the URL has no ?request=... context.
The empty params were reaching the backend as dateFrom=&dateTo=, which
returns HTTP 400 and surfaces as an error in the browser console.
Short-circuit the effect so we just emit an empty result when either
range bound is missing — same no-fetch behavior, no console noise.
- Drop the visible 'Общее время в пути: Xч Xм' row above the flight
schedule block — Angular keeps the total duration inside the
FlightSchedule accordion, not as a separate caption. Mark the
existing div visually-hidden so testids keep resolving.
- Redraw the three transition icons so Регистрация looks like a person
with an ID badge (Angular's #service), Посадка reads as an ascending
escalator with a passenger (#board), and Высадка mirrors it going
down (#deboard). The previous placeholders were too abstract to read
at a glance.
Angular stamps a small '-1' (or '+1') next to the time whenever the
transition start/end falls on a different calendar day than the leg
itself (e.g. registration opening the day before a 00:05 departure).
Read start.dayChange.value and end.dayChange.value on each transition
and render the offset as a superscript next to the time. Keeps the
blue accent color used elsewhere in the row for date lines.
Angular anchors the status label ('Прибыл') above the right end of the
progress bar and parks the green plane icon at 100%. React was keeping
both centered even after the flight landed; move the plane marker to
the bar's end (100%) for finished as well as in-flight, and make the
status text flex-end so it lines up with the arrival column.
- Restructure BoardDetailsHeader so the Share icon sits top-right next to
the flight-number badge, and 'Последнее обновление' sits on its own row
below, right-aligned, matching Angular's flight-details header layout.
- FlightEvents badges only render when changeRoute/reroute are actually
set, avoiding an empty row on normal flights.
- Hide the leg.flyingTime under the route-status bar once the flight is
Arrived/Landed/Cancelled — Angular leaves that slot blank in those
states since the in-flight 'В пути / До прилета' split no longer
applies.
- Drop the duplicate FlightCard summary between the header and the route
strip — Angular's details page shows the route strip directly under the
board-details-header, with no 'SU 6272 Russia 15:30/15:22 ...' row.
- Keep the mini-list sidebar visible even when allFlights has only one
entry; fall back to rendering the current flight as a single item,
matching Angular's flights-details-list-flight behavior.
- Accordion now renders flat rows (icon + caption/status on the left,
Время начала / Время окончания columns on the right) under a single
collapse toggle, matching Angular's flight-details-wrapper layout.
- Aircraft row moves the model title into the row subtitle and drops the
duplicate 'Борт' property, so the row reads 'Борт / Sukhoi SuperJet 100'.
- Route strip grows a green in-flight state with a plane marker on the
progress bar plus 'В пути Xч Xм' / 'До прилета Xч Xм' durations derived
from actual-departure and scheduled-arrival.
- Mini-list sidebar now fetches sibling flights from the departure station
parsed from the '?request=onlineboard-departure-LED-...' URL param, and
the item layout gains city + airport labels with formatted time/date
columns (replacing raw ISO timestamps and IATA codes).
- Tests and mocks updated: add useSearchParams / useOnlineBoard mocks,
relocate aircraft-title assertions to the accordion level, and expect
city names on mini-list items.
- Move the '* Время прилета...' note from the footer to right after the
LegRoute, matching Angular's position between the route strip and the
Детали рейса accordion.
- Add inline SVG icons for each FlightDetailsAccordion row
(Регистрация, Посадка, Высадка, Воздушное судно, Питание, Услуги) in
blue to mirror Angular's sprite-based icons.
Replace the vertically-stacked station blocks with Angular's
route-strip + time-table layout:
- Top row: big time + city + airport/terminal on both sides, with
the status label and a progress bar in the middle. Scheduled time
shows as a small strike-through line under the actual when delayed.
Arrival time picks up the '+1' day-change marker when the flight
crosses midnight.
- Bottom row: 'По расписанию' + 'Фактическое / Ожидаемое' detail
rows for both departure and arrival, with UTC offsets and dates
styled exactly like the Angular design.
The progress bar colors switch between blue (in-flight), green
(finished/arrived) and red (cancelled). The status text localizes
via FLIGHT-STATUSES.*.
Integration tests switched from asserting IATA codes to asserting
the city names, which now render in the promoted row (matches
Angular and the audit feedback).
The API returns flight.flightId.date as 'yyyy-MM-dd' (dashed). Our URL
builder pasted it verbatim, producing /onlineboard/SU6162-2026-04-18
which the route parser (expecting yyyyMMdd) rejected. Normalise the
date to compact form inside buildFlightUrlParams so the URL always
matches the route pattern, regardless of whether the caller passes a
compact or dashed date.
Clicking a row on the board search results page now toggles an inline
details panel instead of immediately navigating away. The layout
matches Angular's board-flight-header:
- Aircraft model ('Sukhoi SuperJet 100') appears below the flight
number when expanded.
- 'Время' detail row: По расписанию / Фактическое times with UTC
offsets for both the departure and the arrival sides.
- 'Посадка' detail row: boarding status (через the
BOARDING-STATUSES.* keys), start and end times.
- 'Детали рейса' button (blue) in the bottom-right navigates to the
full details page.
- Active rows get a blue left border + light-blue background.
- Chevron icon on the right rotates on expand.
Wire-up: FlightCard has two new props (expandable, onViewDetails).
FlightList automatically passes expandable=true when a click handler
is provided. Added SHARED.BOARDING-START / SHARED.BOARDING-END keys
across all nine locales for the time captions.
The arrival-city popup called t('FLIGHTS-MAP.BUY-TICKET') but the key
in every locale file is FLIGHTS-MAP.BUY_TICKET_BTN (it was renamed
earlier). Map popups rendered the raw key 'FLIGHTS-MAP.BUY-TICKET'
instead of 'Купить билет'. Point the call at the existing key.
With empty availableDates every day button rendered as [disabled],
leaving the user unable to navigate between days while the /days API
loads (or for routes where the calendar endpoint hasn't been wired
yet). Treat an empty availableDates array as 'unknown' — don't disable
anything, matching Angular's behaviour where tabs are tappable until
the upstream tells us a specific day has no flights.
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.
formatTime runs new Date(iso).getHours() which reprojects the
timestamp through the browser's local timezone. For a flight arriving
at 06:30 in Almaty (GMT+5) a viewer in Moscow saw '04:30'. Switch the
TimeGroup component to formatLocalTime which reads the wall-clock
directly out of the offset-aware ISO string, matching the rest of the
details/timetable views.
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.
The badge already conveys the operating carrier via the airline logo
(e.g. РОССИЯ for FV-operated Aeroflot flights), and code-share flights
get an additional 'KL 123, AF 456...' line below the flight number
from DetailsHeaderBadge. The separate 'Выполняет рейс: FV' line
duplicated that information.
Keep the div in the DOM with a visually-hidden class so tests that
target the data-testid still find it. Also add the .visually-hidden
utility class to layout.scss.
Test updated to assert the slot still exists but is hidden.
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.
The upstream /schedule endpoint regularly returns 7MB+ payloads and
takes 6-10s to complete. The 5s default was aborting those fetches
mid-body, cascading into a retry loop that showed ERR_ABORTED in
DevTools and 'Failed to load data' on the UI — even though the backend
eventually answered with HTTP 200. Matches Angular's default.
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'.
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.
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.
Two bugs prevented the popular-requests click from filling the filter:
1. OnlineBoardFilter seeded its fields from initial* props via
useState(...), which only runs once. When a user clicked a popular
request the parent pushed ?departure=SVO&arrival=LED into the URL
and re-rendered with new initial* props, but the sidebar fields
kept their previous empty values. Add an effect that diffs the
initial* props against a ref and pushes the changes into local
state, matching Angular's ngOnChanges behaviour.
2. CityAutocomplete's selectedCity only looked the value up in
cityByCode. Airport codes like SVO aren't cities, so the header
code label stayed blank. Fall back to airportByCode → city_code so
the top-right code renders as 'MOW' when the input shows
'Шереметьево'.
End-to-end behaviour now matches Angular: clicking
'Маршрут: Шереметьево - Санкт-Петербург' on the start page updates
the URL, populates 'Шереметьево' / 'Санкт-Петербург' in the inputs,
shows 'MOW' / 'LED' codes in the labels.
useCityName was a 'phase 2 stub' that returned the IATA code
unchanged, so the popular-requests panel read 'Маршрут: SVO - LED'
instead of 'Маршрут: Шереметьево - Санкт-Петербург'. Rewire the hook
to read the current locale and look up cityByCode / airportByCode
from the loaded dictionaries, falling back to the code only while
dictionaries are loading or for unknown codes (matches Angular's
DictionariesService.getCityOrAirport).
Tests expanded to cover the city/airport resolutions and the
loading / unknown fallback paths (5 total, 1258 suite green).
- Add the 'Найдите свой маршрут' heading (uses the existing
FLIGHTS-MAP.ROUTE key, previously unused).
- Reorder the fields to Angular's order: cities → info → toggles →
date (date was previously stuck in the middle).
- Replace the three native checkboxes with styled pill toggles +
sliding knobs — matches p-inputswitch used in the Angular filter.
- Add 'ДД.ММ.ГГГГ' placeholder to the date input so the empty state
reads the same as Angular.
Several components still read `flight.operatingBy.carrier` directly.
The API actually returns `{scheduled, actual}` — I kept `.carrier` on
IOperatingBy as a deprecated alias during the refactor, so nothing
fails typechecking, but at runtime the lookup is always undefined,
which silently disabled the Registration, Flight-status and
Operated-by UIs (the Reg button did nothing on click, the status
visibility check always returned false).
Swap the affected sites — RegistrationButton, FlightStatusButton, the
canRegister / canViewFlightStatus visibility helpers, and the
'Operated by' block in OnlineBoardDetailsPage — to use the
operatingCarrier() helper that reads actual/scheduled/legacy in turn.
/ru rendered a blank page because no index route existed under
src/routes/\[lang\]/. Angular's app-routing.module.ts redirects any
empty top-level path to /onlineboard; mirror that in React by adding
a tiny <Navigate replace> index route.
- Flights-map empty/error states: 'Failed to load routes. Please try
again.' and 'No directions found.' now use existing translation keys
(BOARD.LOAD-FAILED + FLIGHTS-MAP.NO_DIRECTIONS_INFO).
- Flights-map feature-flag fallback '404 - Page Not Found / This
feature is currently unavailable.' reduced to the translated
PAGE404.HEADER string.
- Suspense fallbacks on /onlineboard, /schedule, /flights-map and
/popular route pages now render the new SHARED.LOADING ('Загрузка…')
instead of hardcoded 'Loading...'.
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`.
Render ISO timestamps as "23:30 UTC+03:00 17.04.2026" instead of the
raw "2026-04-17T23:30:00+03:00" that the API returns. Applies to the
per-leg station blocks (new LegStation helper) plus the
Registration / Boarding / Deboarding panels.
formatLocalTime now preserves the wall-clock in the source string
(previously `new Date(...)` would shift it to the runtime's locale).
Added formatUtcOffset (UTC±HH:mm) and formatDayMonthYear (DD.MM.YYYY)
helpers next to it.
Also ship the Angular footer notes on the details page (estimated
times / local-time disclaimer) and added BOARD.LOCAL-TIME-NOTE,
BOARD.ESTIMATED-TIME-NOTE keys to all nine locales.
Search page:
- Title and breadcrumb now read the station dictionaries and render the
human-friendly route heading (e.g. 'Маршрут: Шереметьево - Пулково')
for route/departure/arrival/flight search URLs, mirroring Angular.
Details page:
- Main H1 becomes 'Информация о рейсе: SU 6805, Москва - Санкт-Петербург'
(carrier + flight number + origin/destination cities), not a bare
flight number.
- Add 'Детали рейса' section header above the accordion to match
Angular's flight-details-wrapper layout.
- Promote the airline block in BoardDetailsHeader: drop the legacy
OperatorLogo copy with broken asset paths and hand off to the shared
<OperatorLogo> under src/ui/flights. Render it with the
'авиакомпания' caption beside the enlarged flight number.
- Replace hardcoded English 'Leg' / 'Total flying time' / 'Aircraft:'
with i18n keys, added to all nine locale files.
Test harness:
- Add vi.mock for useDictionaries in the three suites that render
OnlineBoardSearchPage (the new heading helper calls the hook and
crashed without ApiClientProvider). 1256 tests passing.