TZ §4.1.24.3 line 3098: "всплывающая подсказка с кодом аэропорта".
The marker `label` now uses `city.code` (IATA city code) instead of the
human-readable city name. On hover, Leaflet's tooltip shows the code.
- Hide `.flights-map-filter-header` on mobile via `@include screen.mobile`
so the "Найдите свой маршрут" label is absent on phone (R7).
- Disable the PrimeReact Calendar and DayQuickPick when `Город вылета` is
empty; date picker must not be selectable without a departure city (R16).
- Add `disabled?` prop to DayQuickPick so callers can block the quick-day
buttons on mobile (R16 mobile quick-day parity).
All three feature seo builders already emitted the full OG set (title,
description, url, image, type, locale, site_name), canonical with no
query params, and hreflang for all 9 locales + x-default. No builder
gaps found. Added explicit §4.1.19/20 requirement-ID test cases to each
seo.test.ts so the contractual coverage is machine-verifiable.
WebSite JSON-LD now emitted on Online Board and Schedule start pages
via buildWebsiteJsonLd in the shared jsonLdBuilders module. Flight Map
already had WebPage JSON-LD; Online Board/Schedule search and details
pages already rendered Flight/ItemList JSON-LD directly in components.
Client makes no stale-serving assumptions: every Online-Board and Schedule
search triggers a fresh upstream fetch; dictionaries are loaded once per
session. Cache-Control TTLs (≤1 min board, ≤10 min schedule) and ETag/304
for dictionary endpoints are set by the backend HTTP layer. CachedApiClient
/ ServerLruCache infrastructure exists in src/shared/api/ for future SSR
caching if needed. All three 4.1.18 rules marked Out-of-scope (backend) in
the spec; coverage counters updated (13 → 16 out-of-scope, TBD −3).
- FlightsMiniListItem: SVO/VKO airport names rendered as role=link spans that
open external sites in a new tab (R19, R20) — avoids <a> nesting inside Link
- ScheduleDetailsPage: wire FlightsMiniList into contentLeft and DayTabs into
stickyContent (schedule window [-1, +330]) per §4.1.16.1 R2/R3 and §4.1.16.3 R22
- Add navigation handler for schedule day-tab clicks (simple date URL swap;
full §4.1.16.3.1 re-search algorithm is deferred)
- Tests: 72 tests across four files covering R12/R13/R16/R17/R22 (mini-list),
R23/R27/R28 (day-tabs), R3/R5/R6/R7 (page structure), R2/R4/R22 (schedule)
Add targeted assertions covering every rendering rule in §4.1.16.8:
- DaysOfWeekStrip: Mon/Wed/Fri, weekend-only, single-day patterns;
explicit bit-index contract (0=Mon … 6=Sun)
- weekDateRange: Mon–Sun ISO week, 6-day span invariant, leading-zero
dd.MM.yyyy format, Sat input resolves to same week as Mon
- FlightSchedule: daysOfWeek.flight (not .current) drives active days;
accordion collapses on click; week note anchored to dep-local date
Three concrete gaps fixed:
1. TransferBar (Online-Board §4.1.15.6): duration now uses actual/estimated
UTC times when viewType=Onlineboard instead of always scheduled UTC.
Adds isIntermediateLanding prop (default true) so the label can switch
between "Промежуточная посадка" and "Пересадка" based on flight-number
identity rather than being hardcoded. StationChange now always rendered
(not only when separated) so city/airport/terminal are always shown.
2. ScheduleFlightBody (§4.1.16.7): transferDuration previously computed
ground time from .local strings ("HH:MM:SS" without timezone), making
new Date() result timezone-dependent and often NaN. Switched to .utc
(ISO 8601 with Z suffix) for a correct, deterministic diff.
Tests: 53 pass (8 TransferBar + 32+3 ScheduleFlightBody + 10 computeTransferTime).
New test cases: isIntermediateLanding=false label, StationChange always-on,
--separated class, UTC-based 90-min duration, label distinction per TZ.
Three fixes:
- Transfer box: use IATA cityCode (not display text) for city-level
station change detection (TZ §4.1.16.6 rule 12), catching cases where
city codes differ even if airport codes are the same.
- Transfer box: add terminal-change case — same airport but different
arrival/departure terminals now renders both codes separated by →
(TZ §4.1.16.6 rule 14).
- ScheduleDetailsPage title: show all connecting flight numbers in the
page <h1> and title string (TZ §4.1.16.6 Table 60 header rule 1+5).
Also fixes a pre-existing flaky test in ScheduleFlightBody: todayUtc()
now always returns UTC noon of today to avoid day-boundary races.
Gap found and fixed: Timeline route bar (Маршрут section) was rendering
departure/arrival times without day-change badges. TZ §4.1.15.5 rows 3
and 9 require +X/-X indicators whenever a leg crosses midnight.
Added TimeCell component to Timeline that emits the badge when
dayChange != 0, with priority to actual times when canChange=true
(Online Board) and fallback to scheduled (Schedule). Added 9 new
assertion tests covering: no badge when 0, +1/+2/-1 on arrival, badge
on departure, actual-takes-priority, and multi-badge (3 badges when 3
of 4 time cells carry non-zero day offsets).
All other multi-segment rules (routeChanged/returnToAirport from any
leg, codesharing in header, StationChange detection, TransferBar,
per-leg LegRoute with its own arrival day-change badge, ScheduleFlightBody
per-leg TimeGroup) were verified as already implemented. Per-segment
collapse/expand accordion (rows 7 of §4.1.15.5) deferred to Task 13.
Angular rule: show the previous-flight identifier as a clickable link
opening the prior flight's details in a new browser tab, gated on the
flight's scheduled departure being > today − 2 days old. Outside that
window it falls back to plain text to avoid stale cross-links.
Threads locale + departureDateLocal from OnlineBoardDetailsPage through
FlightLegs → FlightDetailsAccordion → AircraftPanel. URL is built with
the existing buildFlightUrlParams helper using previousFlight.localDate,
matching Angular's dateToSearchBy = new Date(prevFlight.localDate).
Two gaps: Delayed fell into center--progress (blue) instead of
orange; Sent was excluded from the isInFlight branch despite the
Angular FlightStatusLegacy.inFlight contract. Fixed both and added
8 per-status assertions covering all 8 FlightStatus enum values.
Creates timelineTime.ts with computeTimelineCalc (R94–R97: total/elapsed/
remaining minutes + aircraft position %) and formatTimelineDuration (R98:
omit zero leading units — «45мин.» not «0ч. 45мин.»).
Wires into OnlineBoardDetailsPage: arrival time now uses actual > estimated
> scheduled priority (R94), and В пути / До прилета labels use the new
formatter. 24 unit tests cover all branches.
R4 gap fixed: TimeGroup now accepts scheduledDayChange + actualDayChange props
separately so each time type renders its own independent badge. FlightCard
updated to pass them independently (scheduled vs actual/estimated); expanded
row time block also now shows per-type badges.
R5 tooltip fixed: dayChangeBadgeTooltip() uses string-based date extraction
(no TZ reprojection via new Date()) — avoids viewer-TZ shift for SSR and
cross-TZ correctness. Returns "День" for ±1, DD.MM.YYYY for ±2+.
New shared helper dayChange.ts exports computeDayChange(), dayChangeBadgeTooltip(),
formatDayChangeBadge(). 27 unit tests cover +0/+1/+2/-1/-2, null, malformed
input, month/year boundaries, and per-time-type independence (R4).
R1–R3, R6 confirmed correct (API supplies dayChange per ITimesSet; badge
adjacent to time; hidden when 0). R8 (mobile tooltip suppression) deferred.
Extracts the 35-carrier logo path table from OperatorLogo into a shared
pure module (src/shared/operatorIcon.ts) so the mapping can be tested and
reused independently. Adds the 7-range SU flight-number fallback that the
TZ requires when OperatingBy is null — SU5000-5399 shows Pobeda (DP),
SU5400-5799 shows Aurora (HZ), SU6000-6999 shows Rossiya (FV), and the
3000-4999 / 5800-5999 bands explicitly render no logo.
63 table-driven tests lock in every range boundary and carrier entry.
FlightCard and ScheduleFlightBody both apply the range resolution before
falling back to the flight's own carrier code.
Gate Buy button to the TZ §4.1.14.4.4 window: visible only when departure
UTC is > 2 hours ahead AND < 330 days ahead; first leg governs for multi/
connecting. Gate Status button (§4.1.14.4.5) to same-day departure only,
based on UTC calendar date. Add separate Details button (§4.1.14.4.6) that
is always visible when an onStatus handler is provided. Add SCSS for the
new details-btn outline style. Add 25-test ScheduleFlightBody.test.tsx
covering structure, transfer-box labels, buy gate, and status gate.
WeekTabs (§4.1.14.1):
- Fix active range: derive weeks from scheduleWindowBounds() [-1,+330 days]
instead of hardcoded WEEKS_AFTER=30 (≈210 days, less than required 330).
- Fix auto-scroll: sync page via useEffect when selectedMonday prop changes
so navigating to a different week always reveals its tab.
- Add fill-to-7: pad last page with disabled placeholder tabs when the
final active week does not end a complete group of 7; disable next arrow.
Collapsed row (§4.1.14.3): already implemented — add lock-in tests for
Tables 36–40 (direct / multi-leg / connecting) covering flight number,
operator logos (round for multi-leg per commit 3ae59da), dep/arr times,
day-change chips, duration column, expand chevron, and DayGroupedFlightList
day-grouping + column headers.
Gap audit against §4.1.13.4.3 (Tables 29/30) found that the inline
boarding/deboarding row in FlightCard's default expanded body was
missing three attributes:
- departure.gate / arrival.gate (boarding gate number)
- departure.dispatch (трап/автобус transfer type)
- arrival.bagBelt (baggage belt, deboarding only)
Add all three as conditional fields in the transition block, guarded
by the existing isArrival flag so departure shows gate+dispatch and
arrival shows gate+bagBelt. Add DETAILS.DISPATCH i18n label (ru + en).
Add 16 assertion tests covering time rows, transition status/times,
gate, dispatch, bagBelt, and the share/details buttons.
Deferred (DONE_WITH_CONCERNS):
- Check-in counter number: API type has checkingStatus string but no
counter number field; requires backend extension.
- Aircraft tail number: field (aircraft.registration) exists in types
but is only shown in the details-page AircraftPanel, not in the
FlightCard expanded body; deferred to details-page parity task.
- Code-share chips in expanded segment body: currently merged into the
collapsed header number column via _childFlightIds; per-segment
expanded display deferred to multi-leg task.
Departure/route/flight-number modes sort by scheduled departure time;
arrival mode sorts by scheduled arrival time (last leg for MultiLeg).
Day ordering (yesterday < today < tomorrow) emerges from absolute ISO
timestamps — no bespoke bucketing needed. Flights missing a timestamp
are pushed to the end. 18 unit tests lock the contract in.
- Fix daysAfter: 7→14 in OnlineBoardSearchPage (TZ active range is today-1 to today+14)
- Add inactive padding tabs on the last page when it has fewer than 7 slots; right-arrow stays disabled on last page regardless (TZ §4.1.13.1)
- Add aria-current="date" to active DayTabButton for accessible highlight (TZ requires visual highlight + screen-reader signal)
- Add auto-scroll via scrollIntoView when selectedDate changes externally (URL-driven day navigation)
- Convert DayTabButton to forwardRef to support the activeBtnRef scroll anchor
- 9 new TZ-labelled tests locking in all the above behaviors
- New findNearestFlightIndex helper (scrollToCurrentTime.ts) with 5 unit tests
- FlightList: lock scroll-to-nearest behind a ref so live SignalR updates
don't yank the viewport back to the auto-selected flight after the user
has manually scrolled elsewhere
- OnlineBoardSearchPage integration tests: verify today/future/past tab
selection logic via findClosestFlightId (the id-based variant already
wired to FlightList.initialCurrentFlightId)
- AbortController wired through ApiClient → api functions → hooks so a
new search immediately aborts the previous in-flight request (§4.1.12)
- cancel() exposed from useOnlineBoard / useScheduleSearch; Escape key
triggers it while the loader is showing (§4.1.12)
- «Отменить поиск» button rendered during loading; hides when idle (§4.1.12)
- data-searching attribute on search pages disables filter/tabs/breadcrumbs
via pointer-events:none CSS while a search is running (§4.1.10/11)
- Submit buttons disabled for 30 s after each search (hardcoded, per TZ
§4.1.10/11: «не должно выноситься в конфигурационный файл»)
- Per-status error messages: BOARD.ERROR-TIMEOUT / ERROR-4XX / ERROR-5XX
replace the generic LOAD-FAILED-MESSAGE (§4.1.10.1/11.1)
- Error messages added to all 9 locales
- 8 new tests: 3 for AbortController wiring, 5 for error banners + cancel
button visibility
TZ §4.1.9.5 requires session-scoped history ("в рамках одной сессии").
Migrate useSearchHistory from localStorage to sessionStorage so history
clears on tab close / page reload.
Add schema-validated get/set/deleteNs helpers to sessionStore in storage.ts
so the hook stays under no-restricted-globals constraints.
Fix hover style in SearchHistory.scss: TZ specifies голубая подложка with
white text/icon on hover — replace the near-white tint with the full blue.
Add TZ §4.1.9.5 assertion tests: session storage target, dedup + bump-to-top,
most-recent-first ordering, item types, empty initial state.
Add keyboard navigation (ArrowDown/Up + Enter to commit highlighted item),
Escape closes the popup without committing, role=dialog + aria-activedescendant
for a11y, and city-highlighted visual feedback. All §4.1.9.2 structural rules
(grouping, RU/CIS-first, MOW-first, alpha ordering, scrollable panel, selected
highlight) confirmed by assertion tests. 14 new assertion tests added across
CityPickerPopup.test.tsx and CityAutocomplete.test.tsx.
Two gaps filled vs the Angular reference:
1. EN→RU keyboard layout translit fallback in searchCities (TZ §4.1.9.1:
retry query converted from EN layout, e.g. "vjc" → "мос" → Москва).
2. ESC key cancels manual entry and restores the last committed value's
display (TZ §4.1.9.1 / mirrors Angular focusOut behaviour on Escape).
All other §4.1.9.1 rules (case-insensitive search, substring match, city+
airports grouping, 3-letter code lookup, top-10 cap, alpha sort, no auto-
submit on typing, exact-match auto-commit) were already present; assertion
tests lock them in.
- OB flight-number: X was always visible; now conditionally rendered only
when the field has a value (hides when empty)
- OB flight-date and route-date: add X button next to calendar icon,
clears date state and hides itself when empty
- Schedule outbound and return date-range calendars: same inline X pattern
- CSS: .calendar-input-wrapper + .calendar-clear-btn added to both SCSS
files (absolute-positioned left of the calendar icon)
- CityAutocomplete: already correct (CSS show/hide via has-value class)
- 21 new tests across OnlineBoardFilter, ScheduleFilter, CityAutocomplete
(aria-label, visibility toggling, click-to-clear); all 640 pass