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
Add assertion tests confirming both OnlineBoardStartPage and ScheduleStartPage
render the required info-section headings and content blocks per TZ specifications.
OnlineBoardStartPage (TZ Table 8):
- Heading: 'Что такое Онлайн-табло и что я могу в нем увидеть?'
- 4 info blocks: Актуальная информация, Информация об услугах, Купить билет, Расписание
ScheduleStartPage (TZ Table 9):
- First heading: 'Как пользоваться расписанием?' + 4 info blocks
- Second heading: 'Возможности расписания' + 2 capabilities blocks
- Blocks 5-6 (Купить билет, Расписание) now rendered under capabilities heading
Tests added:
- OnlineBoardStartPage: 6 new assertions (4.1.6-R-info-heading through 4.1.6-R-popular)
- ScheduleStartPage: 8 new assertions (4.1.7-R-info-heading-1 through 4.1.7-R-popular)
Board kinds (Arrival, Departure, Route, FlightNumber): buildOnlineBoardPrefillState
now emits date=today in every case; OnlineBoardStartPage wires it through to
OnlineBoardFilter via initialDate.
Schedule one-way (Route/Schedule): click handler now includes dateFrom/dateTo
= current ISO week (Mon-Sun) in the transient prefill written to sessionStorage.
Schedule round-trip (RouteWithBack/Schedule): additionally includes
returnDateFrom/returnDateTo = next ISO week.
SchedulePrefillState extended with the four new optional date fields;
yyyymmddToDate helper added to ScheduleStartPage; currentWeekBounds /
nextWeekBounds helpers implement the TZ week-boundary logic.
Nine new §4.1.5-labeled tests (4 unit + 5 integration) added; existing
prefill-state tests updated to expect the new date fields. All 55 tests pass.
The final (success) return branch was passing a hardcoded 1-item breadcrumb
array instead of the computed `breadcrumbs` variable, so the leaf crumb built
from ?request= was silently dropped for loaded flights. Loading/error/empty
branches were already correct. Adds 3 unit tests to lock the wiring.
Per TZ §4.1.4 Table 7 rows 6–8, the details page now builds a 3-item
breadcrumb trail [Home, Онлайн-Табло, <leaf>] where <leaf> is mode-aware:
- flight: "Номер рейса: SU-1234" (hyphen per TZ)
- departure: "Вылет: {station}"
- arrival: "Прилет: {station}"
- route: "Маршрут: {dep}-{arr}"
The leaf crumb is a clickable link back to the source search page with
time-range preserved in the URL. Share-link entries (no ?request=) get
only [Home, Онлайн-Табло] with no leaf.
Breadcrumbs component updated to allow last-item links (was suppressed),
since TZ explicitly requires the leaf to be navigatable.
CONFLICT (deferred to Task 16): TZ row 6 says departure/arrival leaf
should show both dep+arr cities; parentRequest only carries one station,
so only that station is shown. Departure/arrival search returns flights
to many arrival cities — "both cities" makes no sense at search-mode level.
- Add formatDateForTitle helper: returns today/tomorrow labels or dd.MM.yyyy
- Switch all search page title builders to use formatDateForTitle; descriptions keep dd.MM.yyyy
- FLIGHT-DETAILS title now uses routeCities (no date) per TZ rows 6-8; adds TITLE-NO-ROUTE fallback for SSR when cities not yet loaded
- buildFlightDetailsSeoFromId accepts optional cityNames param
- Update ru/en i18n TITLE strings to TZ Table 6 format; add TITLE-NO-ROUTE to all 9 locales
- Tests: 32 cases covering today/tomorrow/arbitrary-date branches and routeCities logic
Shared single-purpose helper: returns redirect path when a parsed yyyymmdd
date falls outside the [-1, +14] day window, null otherwise. Six unit tests
cover both bounds, the in-window case, and locale propagation.