- 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.
Typing a full city name (or airport name) and clicking search without
picking a dropdown row previously did nothing: the parent-held city
code stayed empty and the submit handler silently short-circuited.
Exact case-insensitive name matches now resolve to the owning city
code immediately, so the Schedule and OnlineBoard start pages can act
on keyboard-only input. Partial text still requires a dropdown pick.
Expanding a connecting-flight row on /schedule/route used to swap the small
round airline badges in the header for the wide rectangular logo, which
overflowed the operator column and overlapped the departure time. The
header now always renders the round variant on schedule pages, regardless
of the expansion state.