Commit Graph

740 Commits

Author SHA1 Message Date
gnezim 06b1aba530 Revert map marker permanent label to city name (not IATA code)
Earlier §4.1.24.3 R24 fix (commit 0bb6bf2) set the permanent on-map
label to the IATA city code. That mis-read the TZ: §4.1.24.3 describes
the hover tooltip (всплывающая подсказка) as showing the airport
code, not the always-visible marker label. Angular reference + the
user-facing baseline render the permanent label as the localized
city name.

- FlightsMapStartPage: label = city.name (localized via dictionary).
- Updated two test assertions that had codified the previous IATA-
  based form.

Live retest: map now shows "Москва", "Санкт-Петербург", "Сочи", etc.
on its markers. 2044 tests pass, typecheck clean.
2026-04-22 02:56:45 +03:00
gnezim 26d116f18e Fix browser-runtime TZ-compliance gaps found during live smoke test
Three gaps found by navigating the running app with Playwright:

1. parseFlightUrlParams did not zero-pad flight numbers. Deep-link URLs
   like /onlineboard/flight/SU100-20260422 produced API calls with
   flightNumber=SU100 (3 digits) and backend replied 400. Per TZ
   4.1.2-R4, the canonical form is 4-digit zero-padded. Padding moved
   into the parser so every downstream consumer sees SU0100.
2. SEO.SCHEDULE.MAIN.TITLE was the long SEO variant
   ("Расписание прямых и стыковочных рейсов авиакомпании Аэрофлот").
   Per TZ Table 6 row 9 the target is the short form "Расписание";
   fixed ru + en locales.
3. SEO.SCHEDULE.SEARCH.TITLE was "Расписание рейсов {dep} - {arr}
   | Аэрофлот"; per TZ Table 6 row 10 the target is "Расписание по
   маршруту: {dep}-{arr}"; fixed ru + en locales.

Three existing url.test.ts cases asserted the unpadded form; updated
them to match TZ and annotated with rule ID. Full suite 2044 pass,
typecheck clean. Live retest confirms 0 console errors / 0 warnings
on start pages, results pages, details pages.
2026-04-22 02:48:48 +03:00
gnezim 2f386cbaf0 Mark P6 rules Done + project-complete summary in TZ audit spec
CI / ci (push) Failing after 35s
Deploy / build-and-deploy (push) Failing after 5s
2026-04-22 02:21:57 +03:00
gnezim e433c0dc13 Fix noUncheckedIndexedAccess errors in ErrorPage.test.tsx 2026-04-22 02:13:15 +03:00
gnezim 83a9edb44e §4.1.24: assertion tests for all 6 sub-subsection clusters
Adds 48 new rule-tagged tests across 7 test files covering:
- §4.1.24.1/.2: filter disabled states (R13/R14/R16), swap button (R11),
  no-collapse (R8), hint text (R19), IATA tooltip label (R24)
- §4.1.24.3: arc style (R25), rendering mode (R21/R27/R28/R29),
  domestic/intl/connecting filters (R32-R35), zoom tiers (R23)
- §4.1.24.4: click sequence first/second/third (R36/R37/R39/R41)
- §4.1.24.5: API endpoint contract, bit-string parsing (R44/R45)
- §4.1.24.6: CTA URL format, new-tab intent, date-omit (R46/R47/R48)

Total: 175 tests, all passing.
2026-04-22 02:09:06 +03:00
gnezim 41d229a197 §4.1.24.6 R48: omit date segment in SB URL when Дата рейса not set
TZ §4.1.24.6: "Если Дата рейса не известна, то переход в SB должен
выполнятся без даты." buildBuyTicketUrl now accepts date as
string | undefined; when undefined the route triple is {dep}.{arr}
instead of {dep}.{YYYYMMDD}.{arr}. FlightsMapStartPage passes
filterState.date directly (possibly undefined) instead of defaulting
to today.
2026-04-22 02:08:56 +03:00
gnezim 0bb6bf2032 §4.1.24.3 R24: map marker tooltip label = IATA code, not city name
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.
2026-04-22 02:08:49 +03:00
gnezim f5dfa14eab §4.1.24.1/.2: filter label hidden on mobile; date locked until departure set
- 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).
2026-04-22 02:08:43 +03:00
gnezim a94b01cee9 Audit 404 + 500 error pages per TZ §4.1.21
Gaps closed:
- noindex meta: ErrorPage now emits <meta name="robots" content="noindex,nofollow"> (R6)
- 500 refresh CTA: add PAGE500.REFRESH i18n key (9 locales) and a reload button (R5)
- SSR HTTP status: $.tsx converted to $/page.tsx+error.tsx; loader throws Response(404)
  so Modern.js emits the correct status code; same pattern for error/[code]/page.tsx (R8)
- Add error.tsx error-elements so the branded page renders after the loader throws

Pre-existing (already compliant): URL preservation, root link, all 9 locales, support link.
Tests: 34 new assertions cover R4–R8 (noindex, root link, refresh, i18n keys, loader status).
2026-04-22 01:56:53 +03:00
gnezim 5286049420 Audit OpenGraph + canonical + hreflang per TZ 4.1.19/20 (assertion tests)
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.
2026-04-22 01:48:00 +03:00
gnezim 944015d658 Add JSON-LD microdata builders per TZ 4.1.19 + CLAUDE.md #6
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.
2026-04-22 01:45:32 +03:00
gnezim 4904ba31c9 Audit caching behavior per TZ 4.1.18 (all 3 rules backend-responsibility)
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).
2026-04-22 01:40:42 +03:00
gnezim 187977a39f Close C5 TZ 4.1.1-R22 typo conflict (Flight-Map placeholder uses ДД.ММ.ГГГГ) 2026-04-22 01:37:16 +03:00
gnezim fcf48348d5 Populate rule rows for P6 subsections 4.1.18/19/20/21/24 in TZ audit spec 2026-04-22 01:35:53 +03:00
gnezim f961a1d394 Add P6 implementation plan: SEO + errors + cache + flight map 2026-04-22 01:28:38 +03:00
gnezim 21f7bcc57c Mark P5 (flight cards + timeline + icons + Уточняется) rules Done in TZ audit spec
- §4.1.15.1–.11: ~28 Done + ~70 Implemented across structure, mini-list,
  day-tabs, direct flight, multi-segment, intermediate landing, timeline
  time/status, previous-flight, meals, services
- §4.1.16.1–.8: ~16 Done + ~37 Implemented; 4 TBD(backlog): three-date-group
  mini-list UI (R10/R11/R14/R15) and full §4.1.16.3.1 nav algorithm (R28–R31)
- §4.1.17-R4 Implemented (per-type badge independence fixed 63fc606)
- §4.1.22-R1–R10 Done (operatorIcon.ts + 35-carrier table + 7-range fallback)
- §4.1.23-R1–R6 Done (tbd.ts helper + orange .tbd-text + 9 locales)
- Conflicts C9/C10/C11 registered and resolved
- Coverage: 381 Done + ~308 Implemented + 7 Partial + ~143 TBD ≈ 853 total
- Merge log row added for P5 (27fd4ac..67504aa)
2026-04-22 01:22:59 +03:00
gnezim 67504aa4d6 Fix P5 Task 13 typecheck errors (exactOptionalPropertyTypes, union narrowing, unused props) 2026-04-22 01:07:25 +03:00
gnezim 896dea9297 Audit details page structure + mini-list + day tabs per TZ 4.1.15.1-3 + 4.1.16.1-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)
2026-04-22 00:59:59 +03:00
gnezim 0485a3b0ac Lock in execution-days algorithm per TZ 4.1.16.8 (assertion tests)
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
2026-04-22 00:47:27 +03:00
gnezim c0c2d7d748 Audit intermediate landing + transfer per TZ 4.1.15.6 + 4.1.16.7
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.
2026-04-22 00:44:07 +03:00
gnezim c49a2a8525 Audit connecting flight details per TZ §4.1.16.6
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.
2026-04-22 00:39:23 +03:00
gnezim 7fcb844b82 Audit multi-segment flight details per TZ 4.1.15.5 + 4.1.16.5
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.
2026-04-22 00:32:06 +03:00
gnezim 21b6c90d0f Audit direct flight details per TZ §4.1.15.4/.10/.11
Gaps found and closed:
- Departure gate (§4.1.15.4): rendered in boarding accordion row body,
  sourced from leg.departure.gate (Angular parity: flight-details-boarding L21)
- Departure transfer type / dispatch (§4.1.15.4): rendered in boarding row body
  from leg.departure.dispatch (Angular parity: flight-details-boarding L17)
- Aircraft tail number (§4.1.15.4): rendered in AircraftPanel from
  aircraft.registration field; AIRPLANE.TAIL-NUMBER added to all 9 locales
- BoardingPanel accepts optional departure prop for gate/dispatch display;
  legacy hidden panel keeps existing testids without duplication
- §4.1.15.10 meals + §4.1.15.11 services already implemented (assertion tests
  already cover icon rendering, links, fallback icon)

Tests added: 4 in BoardingPanel, 2 in AircraftPanel, 3 in FlightDetailsAccordion
2026-04-22 00:26:39 +03:00
gnezim 1740af682c Render previous-flight chip as link per TZ §4.1.15.9
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).
2026-04-22 00:17:43 +03:00
gnezim e33c8c4b24 Audit timeline status display per TZ 4.1.15.8
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.
2026-04-22 00:09:49 +03:00
gnezim 877cd87162 Add timeline time-calculation algorithm per TZ §4.1.15.7
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.
2026-04-22 00:06:21 +03:00
gnezim 63fc6060f2 Verify day-change algorithm per TZ 4.1.17 (per-time-type badges, query-date baseline)
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.
2026-04-22 00:01:30 +03:00
gnezim 5d31f4389e Add operator icon mapping helper per TZ §4.1.22
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.
2026-04-21 23:54:00 +03:00
gnezim b43c341fcb Add "Уточняется" fallback helper + orange styling per TZ 4.1.23 2026-04-21 23:49:10 +03:00
gnezim df83a587c2 Populate rule rows for P5 subsections 4.1.15/16/17/22/23 in TZ audit spec 2026-04-21 23:45:57 +03:00
gnezim 27fd4ac601 Add P5 implementation plan: flight cards + timeline + aircraft icons + Уточняется 2026-04-21 23:36:46 +03:00
gnezim 8c8022780f Mark P4 (results lists) rules Done in TZ audit spec
32 rules Done + ~142 Implemented across §4.1.13/14. 4 Partial (multi-seg
status-switching, check-in counter, airport-as-link on segment dep/arr).
7 TBD(P5) deferred (airport-as-link OB+Schedule collapsed/expanded,
online check-in button). C8 registered and resolved (week-tabs active
range +210→+330 days). Coverage: 317 Done, ~191 Implemented, 6 Partial,
~138 TBD.
2026-04-21 23:35:11 +03:00
gnezim f6def717b5 Verify sticky behavior + scroll-up button per TZ Table 22
PageLayout.test: assert header/breadcrumbs/title stay outside sticky
wrapper, filter column carries __column-left (CSS sticky on desktop),
stickyContent (day-tabs) gets __sticky-content wrapper, overlay only
when stickyContent present.

ScrollUpButton.test: assert button hidden below 300px threshold, visible
above it, disappears on scroll-back, correct aria-label/type, listener
cleaned up on unmount.

All 27 tests pass; no implementation changes needed — React matches
Angular reference behavior exactly.
2026-04-21 23:21:37 +03:00
gnezim 4290c819bb Audit Schedule expanded rows per TZ 4.1.14.4 (multi-segment + connecting)
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.
2026-04-21 23:18:16 +03:00
gnezim 6f67c06786 Audit Schedule week-tabs + collapsed row per TZ 4.1.14.1 + 4.1.14.3
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.
2026-04-21 23:11:32 +03:00
gnezim 9f6623786f Audit Online-Board expanded row per TZ 4.1.13.4
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.
2026-04-21 23:02:55 +03:00
gnezim 3b5ae9af85 Audit Online-Board collapsed row per TZ 4.1.13.3 Tables 23-27 2026-04-21 22:55:49 +03:00
gnezim 8b0d559df9 Implement Online-Board flight-list default sort per TZ §4.1.13.2
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.
2026-04-21 22:48:39 +03:00
gnezim 439624244d Audit DayTabs behavior per TZ 4.1.13.1 (7-day window, paging, padding, active range)
- 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
2026-04-21 22:45:08 +03:00
gnezim 38a512004f Add scroll-to-current-time + auto-expand-nearest on Online-Board today's tab per TZ 4.1.13
- 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)
2026-04-21 22:39:33 +03:00
gnezim 4fd1b054a4 Populate rule rows for P4 subsections 4.1.13/14 in TZ audit spec 2026-04-21 22:34:32 +03:00
gnezim 890d575e88 Add P4 implementation plan: results lists (Online-Board + Schedule) 2026-04-21 22:25:06 +03:00
gnezim 793637ffc3 Mark P3 (filter + validation + history + search execution) rules Done in TZ audit spec 2026-04-21 22:21:09 +03:00
gnezim a5c64a2270 Search execution, cancellation, and error handling per TZ §4.1.10/11/12
- 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
2026-04-21 22:08:11 +03:00
gnezim 2b0a7ecbe7 Audit «Вы искали» search history per TZ 4.1.9.5
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.
2026-04-21 21:55:30 +03:00
gnezim c509131649 Tighten filter validation per TZ 4.1.9.3 + 4.1.9.4 2026-04-21 21:50:46 +03:00
gnezim d173159018 Audit CityPickerPopup dictionary-picker behavior per TZ 4.1.9.2
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.
2026-04-21 21:44:43 +03:00
gnezim dc3ee10ae8 Audit CityAutocomplete manual-entry behavior per TZ 4.1.9.1
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.
2026-04-21 21:40:23 +03:00
gnezim 66518a6f0c Enforce 1h minimum gap on time-range slider per TZ 4.1.9 Tables 12/14 2026-04-21 21:36:07 +03:00
gnezim 83951d4292 Add clear-button (X) to filter fields per TZ 4.1.9 Tables 11/12/14
- 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
2026-04-21 20:05:53 +03:00