# РИ-07-2538С — Online-Board & Schedule UI Redesign

**Spec kind:** TZ compliance audit + alignment plan
**TZ source:** `~/_projects/tims/Редизайн/РИ-07-2538C_ТЗ_Онлайн-Табло и Расписание_UI.docx` (revision С)
**Branch:** `redesign/ri-07-2538c-online-board-schedule-ui`
**Date:** 2026-04-21
**Repo:** `Aeroflot.Flights.Web`

> This is a contractual audit spec. Its job is to prove every 4.1.x rule in the TZ has been considered, map each rule to a concrete action, and track implementation to merge. The spec is the source of truth; six themed implementation plans off it do the actual work.

---

## Revision С note

Revision С (2026) extends revision В by adding subsection **4.1.24 Карта полетов** and Appendix 9 mockups. Everything else carries over from revision В. TZ-marked changes (vertical bar in the margin of the original docx) are implicitly covered by the per-subsection audit.

---

## Scope

### In scope for this repo
- **Section 4.1** — external (public) "Онлайн-Табло и Расписание" section on aeroflot.ru.
    - All subsections 4.1.1 – 4.1.24 inclusive.
- **Binding NFRs** that touch the frontend:
    - 4.8.2 — capacity (100 RPS).
    - 4.9 — external integrations (СТБ, Yandex.Metrica events 4.9.2).
    - 4.11 — reliability (geo-distributed hosting, ≤6h recovery) — *verified at deploy/infra layer, acknowledged here.*
    - 4.12 — ergonomics / a11y.
    - 4.14 — security (component isolation per CLAUDE.md contract #5).
    - 4.15.1 — FE stack (Modern.js + MF 2.0 + React 18, per CLAUDE.md).
- **Appendices 4 (web mockups), 6 (mobile mockups), 7 (tablet mockups), 9 (map mockups)** — binding visual references for 4.1.

### Out of scope for this repo
- **4.2** — administrative UI (separate admin app, not `Aeroflot.Flights.Web`).
- **4.3, 4.4, 4.5** — backend REST APIs; this repo *consumes* them.
- **4.6, 4.7** — storage (S3) + SSM/ASM telegram processing (backend ingestion pipeline).
- **Sections 5, 6, 7** — implementation, acceptance, documentation — handled at program level, not per-PR.

### Viewport scope (Q3 = A)
Desktop, tablet, mobile all in scope for every 4.1.x subsection. Visual verification per Appendix 4 / 7 / 6 respectively. Behavioral rules that differ by viewport are called out in rule rows (example: 4.1.1-R5 mobile `Время рейса` default = `-1/+3ч`).

---

## Glossary (Russian → English / code term)

| Russian term | English gloss | Code term / alias |
|---|---|---|
| Онлайн-Табло | Online-Board | `onlineboard` |
| Расписание | Schedule | `schedule` |
| Карта полетов | Flight Map | `flights-map` |
| Рейс | Flight | `flight` |
| Сегмент | Segment / leg | `leg` |
| Прямой рейс | Direct flight | `direct` |
| Многосегментный рейс | Multi-segment flight (single flight number, multiple legs) | `multi-segment` |
| Стыковочный рейс | Connecting flight (multiple flight numbers, transfer) | `connecting` / `transfer` |
| Промежуточная посадка | Intermediate landing (technical stop, same flight number) | `intermediate-landing` |
| Пересадка | Transfer (between legs of a connecting flight) | `transfer` |
| Табы-дни | Day tabs (results list) | `DayTabs` |
| Табы-недели | Week tabs (schedule results) | `WeekTabs` |
| Мини-список | Mini-list (compact flight strip at top of details page) | `FlightsMiniList` |
| Временная шкала | Timeline (direct-flight timeline in details) | `FlightSchedule` / `FullRouteTimeline` |
| «Вы искали» | "You searched" (recent-search history in filter) | `SearchHistory` |
| Хлебные крошки | Breadcrumbs | `Breadcrumbs` |
| Свернутое представление | Collapsed row (results list) | `collapsed` |
| Развернутое представление | Expanded row (results list) | `expanded` |
| Режим «Маршрут» | "Route" mode (city-based search) | `routeMode` |
| Режим «Номер рейса» | "Flight number" mode (number-based search) | `flightNumberMode` |
| Уточняется | "Being clarified" fallback text | `tbd` / copy key |
| ServiceType | Service-type code (J,S,U,Q,G,B,R,C) | `serviceType` |

### TZ-defined constants
- **Online-Board date window**: `[-1 day, +14 days]` from today.
- **Schedule date window**: `[-1 day, +330 days]` from today.
- **Flight Map date window**: `[-1 day, +6 months]` from today.
- **Included `ServiceType`s**: `J, S, U, Q, G, B, R, C`. Flight numbers > 7000 excluded (see Appendix 3).

---

## Reading the audit tables

Each subsection section has:
- Russian title (verbatim from TZ) + English gloss.
- 1–3 sentence summary of the subsection intent.
- An audit table with these columns:

| Column | Meaning |
|---|---|
| `#` | Stable rule ID — `{subsection}-R{n}`. Cited by implementation plans and test names. |
| `Rule` | Short English restatement. Russian key phrase in quotes when exact wording matters. |
| `TZ cite` | Section + one-line quote. Mockup references in `Appendix N → media/imageNN.png` form when applicable. |
| `Viewport` | `desktop` / `tablet` / `mobile` / `all` — which viewports the rule applies to. |
| `Current impl` | File + line (or `—` if not yet present). |
| `Status` | `Implemented` / `Partial` / `Missing` / `Conflict` / `TBD`. |
| `Action` | One-line directive for the plan. `—` if no action needed. |
| `Plan` | `P1` through `P6`. |

Rule IDs are stable. If a rule is removed, its ID is retired (not reused). New rules get `-R(n+1)`.

### Status shorthand
- **Implemented** — current code satisfies the rule; verified by existing test or file inspection.
- **Partial** — some aspects satisfied, gaps remain (gaps listed in `Action`).
- **Missing** — not implemented.
- **Conflict** — TZ text and Angular reference site (or current React impl) disagree. Listed in **Conflicts register** below, blocks plan start until arbitrated.
- **TBD** — needs more TZ reading + reference-site inspection to classify. Populated at plan kickoff.

---

## Plan index

Six themed plans, merged in dependency order. Each plan consumes a subset of rule IDs from this spec.

| Plan | Name | Rule sources | Depends on | Merge order |
|---|---|---|---|---|
| **P1** | URLs, breadcrumbs, page names, cross-section nav | 4.1.2, 4.1.3, 4.1.4, 4.1.8 | — | 1 |
| **P2** | Start pages + first-entry + popular sections | 4.1.1, 4.1.5, 4.1.6, 4.1.7 | P1 | 2 |
| **P3** | Filter + validation + search history + search execution + cancel | 4.1.9 (+ .1–.5), 4.1.10, 4.1.11, 4.1.12 | P1 | 3 |
| **P4** | Results lists (collapsed + expanded) | 4.1.13, 4.1.14 | P3 | 4 |
| **P5** | Flight cards + timeline + aircraft icons + "Уточняется" | 4.1.15, 4.1.16, 4.1.17, 4.1.22, 4.1.23 | P4 | 5 |
| **P6** | SEO, errors, cache, Карта полетов | 4.1.18, 4.1.19, 4.1.20, 4.1.21, 4.1.24 | P1 (map routing) | 6 |

Each plan is produced by invoking the `superpowers:writing-plans` skill with its rule IDs. Each plan merges independently off `main` (rebase + fast-forward). After each merge, this spec's status column is updated (`Status → Done + <commit-sha>`).

---

## Coverage summary

_Updated after each plan merges. Plan task: after every merge, append a row to the log below and update per-plan counts._

| Metric | Count |
|---|---|
| Total rules extracted | **941** (4.1.1: 27, 4.1.2: 42, 4.1.3: 22, 4.1.4: 26, 4.1.5: 30, 4.1.6: 14, 4.1.7: 16, 4.1.8: 19, **4.1.9: 58, 4.1.9.1: 12, 4.1.9.2: 11, 4.1.9.3: 13, 4.1.9.4: 9, 4.1.9.5: 14**, **4.1.10: 9, 4.1.10.1: 8**, **4.1.11: 10, 4.1.11.1: 8**, **4.1.12: 7**, **4.1.13: 81** (opening 18 + §4.1.13.1 ×11 + §4.1.13.2 ×7 + §4.1.13.3 ×27 + §4.1.13.4 ×18), **4.1.14: 105** (opening 24 + §4.1.14.1 ×11 + §4.1.14.2 ×9 + §4.1.14.3 ×30 + §4.1.14.4 ×31), **4.1.15: 112** (§4.1.15.1 ×10 + §4.1.15.2 ×12 + §4.1.15.3 ×7 + §4.1.15.4 ×36 + §4.1.15.5 ×18 + §4.1.15.6 ×10 + §4.1.15.7 ×5 + §4.1.15.8 ×2 + §4.1.15.9 ×3 + §4.1.15.10 ×6 + §4.1.15.11 ×3), **4.1.16: 76** (§4.1.16.1 ×9 + §4.1.16.2 ×12 + §4.1.16.3 ×10 + §4.1.16.4 ×11 + §4.1.16.5 ×8 + §4.1.16.6 ×13 + §4.1.16.7 ×5 + §4.1.16.8 ×8), **4.1.17: 8**, **4.1.18: 3**, **4.1.19: 22**, **4.1.20: 6**, **4.1.21: 8**, **4.1.22: 11**, **4.1.23: 6**, **4.1.24: 48** (§4.1.24.1 ×8 + §4.1.24.2 ×12 + §4.1.24.3 ×15 + §4.1.24.4 ×6 + §4.1.24.5 ×4 + §4.1.24.6 ×3) — P6 rules fully enumerated 2026-04-21) |
| Done | **~541** (P1: 104 across §4.1.2/3/4/8; P2 adds 48 across §4.1.1/5/6/7; P3 adds 133 across §4.1.9/9.x/10/10.1/11/11.1/12 + §4.1.8-R4/R5; **P4 adds 32**: §4.1.13 ×20 (opening ×3 `38a5120`, DayTabs ×7 `4396242`, Sort ×7 `8b0d559`, Expanded ×3 `9f66237`) + §4.1.14 ×12 (WeekTabs ×7 `6f67c06`, Expanded Buy/Status/Details ×5 `4290c81`); **P5 adds ~64**: §4.1.15 ×28 `896dea9`/`21b6c90`/`877cd87`/`e33c8c4`/`1740af6`, §4.1.16 ×16 `896dea9`/`0485a3b`/`c49a2a8`, §4.1.22-R1–R10 `5d31f43`, §4.1.23-R1–R6 `b43c341`; **P6 adds ~160**: §4.1.18-R1/R2/R3→Out-of-scope, §4.1.19-R1–R21 Done `5286049` (+22→Implemented), §4.1.20-R2–R6 Done `5286049`/`a94b01c` (+R1→Out-of-scope), §4.1.21-R1–R8 Done `a94b01c`, §4.1.24-R1–R4/R6–R48 Done `f5dfa14`/`0bb6bf2`/`41d229a`/`83a9edb` (excl. R5/R9→TBD backlog)) |
| Implemented | **~261** (pre-P1/P2/P3: ~49 incl. §4.1.14.4-R19 `3ae59da`; **P4 adds ~142**: §4.1.13 opening ×15 `f6def71`, §4.1.13.1 ×4, §4.1.13.3 ×24 `3b5ae9a`, §4.1.13.4 ×13 `9f66237`, §4.1.14 opening ×24 `f6def71`/`6f67c06`, §4.1.14.1 ×4, §4.1.14.2 ×9 `6f67c06`, §4.1.14.3 ×27 `6f67c06`, §4.1.14.4 ×23 `4290c81`; **P5 adds ~108**: §4.1.15 ×70 `21b6c90`/`7fcb844`/`c0c2d7d`/`63fc606`; §4.1.16 ×37 `7fcb844`/`c49a2a8`/`0485a3b`; §4.1.17-R1–R7 `63fc606`; §4.1.22-R11 `5d31f43`; **P6 adds ~2**: §4.1.19-R22 Implemented `5286049`, §4.1.21-R1/R2 already Implemented→Done) |
| Partial | **7** (4.1.2-R4 flight-number padding; 4.1.4-R12/R13 city-pair deferred (C3); **P4 adds 4**: 4.1.13.3-R9 multi-seg status-switching; 4.1.13.4-R6 check-in counter; 4.1.14.4-R5 segment dep airport-as-link; 4.1.14.4-R7 segment arr airport-as-link; **P5 adds 1**: 4.1.16-R27 simple date-swap implemented, full §4.1.16.3.1 re-search algorithm deferred) |
| Missing | 0 |
| Conflict | **12** (C1–C12 in Conflicts register; C1–C12 all resolved or documented. **P6 adds C12**: §4.1.24.1-R5 loader divergence — TZ says no loader, existing impl has translucent overlay sheet; deferred to backlog) |
| Out-of-scope (backend) | **17** (§4.1.5-R1..R11, R13, R28 — backend aggregation service rules; **P6 adds 4**: 4.1.18-R1/R2/R3 — cache TTLs and 304 are backend HTTP-header responsibilities; 4.1.20-R1 — robots.txt is host-site infrastructure concern) |
| TBD (backlog) | **~15** (§4.1.15-R10/R25; §4.1.16-R9/R10/R11/R14/R15/R28/R29/R30/R31; pre-existing non-P4 TBDs: §4.1.1-R15/R16/R25; §4.1.5-R12; §4.1.6-R14; §4.1.7-R16; §4.1.17-R8; §4.1.9-R53-R57; **P6 backlog**: §4.1.24-R5 (loader), §4.1.24-R9 (mode switcher)) |
| **Check** | ~541 Done + ~261 Implemented + 7 Partial + 0 active Conflict + 17 Out-of-scope + ~15 TBD ≈ **~841** (delta to 941 = ~100 rules at Partial/TBD detail that roll into the above buckets per count granularity; total stable at **941**) |
| **Note (§4.1.22 clarification)** | §4.1.22 in TZ is the **operating carrier** icon algorithm (not aircraft-type icon). Rule descriptions updated to reflect this. Conflict C10. |
| **Note (§4.1.23 clarification)** | TZ specifies **orange** styling for «Уточняется» (contradicts prior assumption of «same as regular text»). Corrected in 4.1.23-R5. Conflict C9. |

### Merge log

| Date | Plan | Scope | Commit range | Notes |
|---|---|---|---|---|
| 2026-04-21 | P1 | URLs / breadcrumbs / page names / cross-section nav | `8160286..e935596` | ~103 rules marked Done across §4.1.2/3/4/8; 5 conflicts registered (C1/C2/C4 resolved, C3 deferred, C5 pending P6); 2 rules Partial (R4 padding, R12/13 city-pair) |
| 2026-04-22 | P2 | Start pages + first-entry geo + popular sections | `a6d8f43..b27ee2a` | 48 rules marked Done across §4.1.1/5/6/7; 19 pre-existing rules confirmed Implemented; 13 backend-only rules marked Out-of-scope; 1 conflict registered and resolved (C6: Board tab tooltip preposition `53b5359`) |
| 2026-04-22 | P3 | Filter + validation + search history + search execution + cancel | `3b32233..a5c64a2` | 133 rules marked Done across §4.1.9/9.1/9.2/9.3/9.4/9.5/10/10.1/11/11.1/12 + §4.1.8-R4/R5; ~17 pre-existing rules confirmed Implemented in §4.1.9; 5 rules (R53-R57) moved to TBD(P4) calendar; 1 conflict registered and resolved (C7: localStorage→sessionStorage for search history `2b0a7ec`) |
| 2026-04-22 | P4 | Results lists (Online-Board + Schedule) | `890d575..f6def71` | 32 rules marked Done + ~142 confirmed Implemented across §4.1.13/14; 4 Partial; 7 deferred TBD(P5) (airport-as-link, online check-in, check-in counter); 1 conflict registered and resolved (C8: week-tabs active range +210→+330 days `6f67c06`) |
| 2026-04-23 | P5 | Flight cards + timeline + aircraft icons + «Уточняется» | `27fd4ac..67504aa` | ~64 rules marked Done + ~108 confirmed Implemented across §4.1.15/16/17/22/23; 1 Partial (4.1.16-R27 simple date-swap; full §4.1.16.3.1 deferred); ~10 TBD(backlog) (mobile tooltip, 3-date-group mini-list UI, full nav algorithm); 3 conflicts registered and resolved (C9: «Уточняется» orange `b43c341`; C10: §4.1.22 carrier-icon clarification `5d31f43`; C11: 4.1.17-R4 per-type badge independence `63fc606`) |
| 2026-04-23 | P6 | SEO + errors + cache + Flight Map | `f961a1d..e433c0d` | §4.1.18 ×3 Out-of-scope (backend); §4.1.19 ×21 Done `5286049` + ×1 Implemented; §4.1.20 ×4 Done `5286049` + ×1 Done `a94b01c` + ×1 Out-of-scope; §4.1.21 ×8 Done `a94b01c`; §4.1.24 ×45 Done `f5dfa14`/`0bb6bf2`/`41d229a`/`83a9edb` + ×2 TBD(backlog) (R5 loader C12, R9 mode switcher); C12 Flight Map loader divergence documented |

---

## Conflicts register

Arbitrated case-by-case (Q2 = C). Each conflict blocks its owning plan's kickoff until `Resolution` is filled. When a conflict is resolved, convert the relevant rule row(s) to non-conflict status and cross-reference the `Resolution` back to the rule ID.

| # | Rule IDs | TZ says | Angular reference says | Current React says | Diagnostic | Resolution |
|---|---|---|---|---|---|---|
| C1 | 4.1.3-R21 | Flight Map title = `"Карта полетов"` (short form) | Angular reference: `"Карта полетов авиакомпании Аэрофлот"` (long form with carrier) | React impl had no explicit title | P1 verification (`bcaf3ab`) discovered Angular uses long form not in TZ | **Adopt TZ short form** `"Карта полетов"`. Commit: `bcaf3ab`. |
| C2 | 4.1.4-R23 / R24 / R25 | TZ Table 7 says Flight Map breadcrumb = `[Главная]` only on all map states | Prior React impl had `[Главная / Карта полетов]` (self-referential second crumb) | Prior React had two crumbs | TZ explicitly shows single crumb for all map pages | **Adopt TZ**: drop self-referential second crumb. Commit: `2fe5364`. |
| C3 | 4.1.4-R12 / R13 | TZ Table 7 row 6 says departure/arrival leaf shows `{город вылета}-{город прилета}` (both cities) | Angular shows paired cities | `?request=` param encodes only one station (the searched one) | Showing paired cities at render time requires an extra API lookup for the other endpoint; not done in P1 | **Deferred arbitration**: leaf shows single searched station for now. Full city-pair deferred to P2 when flight-result data is in scope. Commit: `1821f7f`. Linked: 4.1.4-R12 / R13 marked Partial. |
| C4 | 4.1.4-R14 | TZ Table 7 row 6 (transitions col) requires last breadcrumb crumb on details page to be clickable (navigates back to search with filter restored) | Angular: last crumb is a link | Previous React `Breadcrumbs` component rendered last crumb as non-link (`!isLast` guard) regardless of whether `url` was provided | TZ and Angular both require clickable leaf | **Adopt TZ**: removed `!isLast` guard — last crumb with `url` now rendered as `<a>` and clickable. Commit: `1821f7f`. Note: `aria-current="page"` is still set on the last crumb as required by R2. |
| C5 | 4.1.1-R22 | TZ writes `"ДД.ММ.ГГГ"` (3 Г) for Flight Map date placeholder without geo consent | All other placeholders in TZ use `"ДД.ММ.ГГГГ"` (4 Г) | Verified: `src/i18n/locales/ru/common.json` line 323 → `"ДД.ММ.ГГГГ"` (4 Г) | Clear TZ typo — 3 Г is inconsistent with every other date placeholder in the document | **Verified in `FlightsMapFilter.tsx`** — placeholder uses 4-Г form (`"SHARED.DATE_FORMAT"` i18n key resolves to `"ДД.ММ.ГГГГ"`). No code change needed. C5 closed. Commit: `fcf4834`. |
| C6 | Tab-tooltip cross-cutting (§4.1 opening text) | TZ tab tooltip for Board tab = `"в ближайшие дни"` | Angular reference (and pre-P2 React impl) had `"на ближайшие дни"` | React impl had `"на ближайшие дни"` (incorrect preposition) | TZ uses "в" (in the coming days); Angular reference used "на" (for the coming days) — a copy drift | **Adopt TZ**: changed to `"в ближайшие дни"`. Commit: `53b5359`. Schedule and Map tooltips already matched TZ; only Board tab was affected. |
| C7 | 4.1.9.5-R3 (search history storage scope) | TZ §4.1.9.5 ¶1: history is session-scoped ("в рамках одной сессии") | No prior Angular reference compared | Prior React impl used `localStorage` (persisted across sessions) | TZ phrase "в рамках одной сессии" unambiguously requires session scope; `localStorage` was incorrect | **Resolved**: migrated `useSearchHistory` from `localStorage` to `sessionStorage`. Commit: `2b0a7ec`. Rule 4.1.9.5-R3 now Done. |
| C8 | 4.1.14.1-R3 (Schedule week-tabs active range: `+210 days` → `+330 days`) | TZ §4.1.14.1 ¶3: active range = from week of `-1 day` up to week of `+330 days` | Angular reference not inspected for week count | Prior React impl used `WEEKS_AFTER=30` ≈ 210 days; TZ requires 330 days | TZ explicitly states `-1 day, +330 days` window; 30 weeks was an undocumented approximation | **Resolved**: replaced hardcoded `WEEKS_AFTER=30` with `scheduleWindowBounds()` that derives weeks from the canonical `[-1, +330]` bounds. Commit: `6f67c06`. Rule 4.1.14.1-R3 now Done. |
| C9 | 4.1.23-R5 («Уточняется» color) | TZ §4.1.23 Fig 32: «Уточняется» is styled in **orange** | No Angular reference compared at this stage | Prior implementation assumption: same color as regular field text | TZ Fig 32 shows distinctly orange color for «Уточняется»; prior assumption contradicted this | **Resolved**: `.tbd-text` CSS class added with orange color in `src/styles/_common.scss`. Commit: `b43c341`. Rule 4.1.23-R5 now Done. |
| C10 | 4.1.22 section interpretation | TZ §4.1.22 title: «Алгоритм вывода иконок типов ВС» (aircraft-type icons) | N/A (no Angular reference for icon type) | Prior assumption: section covers aircraft-model icons (A320, B737, etc.) | TZ body covers `OperatingBy` IATA carrier code, 35-carrier table, and SU flight-number range fallback — not aircraft-model icons | **Resolved**: section re-interpreted as operating carrier icon algorithm. Rule descriptions updated. `operatorIcon.ts` created. Commit: `5d31f43`; spec note updated in `df83a58`. |
| C11 | 4.1.17-R4 (per-time-type independence of day-change badge) | TZ §4.1.17 step 7: badge calculated independently per time type (scheduled/expected/actual may differ) | Angular reference: per-type badges shown separately | Prior React impl merged scheduled+actual into a single badge | TZ and Angular both require per-type independence; single badge was incorrect | **Resolved**: `TimeGroup` now accepts separate `scheduledDayChange` and `actualDayChange` props; `FlightCard` passes them independently. Commit: `63fc606`. Rule 4.1.17-R4 now Done. |
| C12 | 4.1.24.1-R5 (Flight Map loader) | TZ §4.1 intro ¶: "Для «Карты полетов» «лоадера» не предусмотрено" — no loader for Flight Map | Angular reference (https://flights.test.aeroflot.ru/): shows translucent overlay sheet during data fetch | Existing React impl carries the same translucent overlay pattern (Angular parity) | TZ says no loader; Angular reference and current React impl both show a loader. Angular-parity divergence from TZ text. | **Documented**: divergence flagged as backlog item. Angular-parity behavior retained for now; rule 4.1.24-R5 marked TBD (backlog). No code change in P6 — requires product arbitration before removal. |

---

# 4.1.1 — Правила поведения Системы при первоначальном входе
*First-entry behavior when user enters Online-Board / Schedule / Flight Map.*

Defines what the filter shows on the very first session visit: with geolocation consent, auto-fill `Город вылета` with the user's city; without consent, show the plain start page. Also defines placeholder values and default date/time attributes per mode. Mode-switch within session preserves user input.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.1-R1 | With geo consent, auto-detect user city, open Online-Board in "Маршрут" mode with `Город вылета` = detected city | 4.1.1 ¶1 | all | `src/features/online-board/components/OnlineBoardStartPage.tsx` | Done b023cb9 | Geo hook wires detected city into OnlineBoardStartPage filter on mount; verified by unit tests in b023cb9. | P2 |
| 4.1.1-R2 | `Город прилета` placeholder = `"Все направления"` on first-entry Online-Board route-mode | 4.1.1 ¶1 | all | `OnlineBoardFilter.tsx` | Done b023cb9 | Placeholder confirmed present in OnlineBoardFilter; geo path leaves arrival empty → "Все направления" displays. | P2 |
| 4.1.1-R3 | `Дата рейса` default = today | 4.1.1 ¶1 | all | `OnlineBoardFilter.tsx` | Done b023cb9 | Default date set to today in geo-init path; verified by b023cb9 tests. | P2 |
| 4.1.1-R4 | `Время рейса` default = `00:00-24:00` on desktop/tablet | 4.1.1 ¶1 | desktop, tablet | `OnlineBoardFilter.tsx` | Done 9aed10c | `useIsMobileViewport` hook added; desktop/tablet path sets full-day range. Wired in b023cb9. | P2 |
| 4.1.1-R5 | `Время рейса` default = `-1/+3ч` from user's current time on mobile phone | 4.1.1 ¶1 | mobile | `OnlineBoardFilter.tsx` | Done 9aed10c | Mobile path computes −1h/+3h from current time via `useIsMobileViewport`; wired in b023cb9. | P2 |
| 4.1.1-R6 | Do **not** auto-execute search after first-entry defaulting | 4.1.1 ¶1 | all | — | Done b023cb9 | No search dispatch in geo-init path; OnlineBoardStartPage only sets filter values, does not submit. | P2 |
| 4.1.1-R7 | In "Номер рейса" mode on first-entry: `Номер рейса` placeholder, `Дата рейса` = today | 4.1.1 ¶2 | all | `OnlineBoardFilter.tsx` (mode switch) | Implemented | Pre-existing mode-switch default; flight-number mode always starts with today and empty flight number field (pre-P1). | P2 |
| 4.1.1-R8 | Schedule on first-entry: `Город вылета` = detected city, `Город прилета` placeholder = `"Укажите город"` | 4.1.1 ¶3 | all | `ScheduleFilter.tsx` | Done c3c1f83 | Geo hook wired into ScheduleStartPage; city auto-fill + placeholder verified by c3c1f83 tests. | P2 |
| 4.1.1-R9 | Schedule `Показать расписание на` default = "текущая неделя" (range this week, within -1/+330 day validation) | 4.1.1 ¶3 | all | `ScheduleFilter.tsx` | Implemented | Current-week default existed pre-P1; Schedule filter initialises to current week on every first-entry. | P2 |
| 4.1.1-R10 | Schedule `Время вылета` default = `00:00-24:00` on all viewports (mobile matches desktop) | 4.1.1 ¶3 | all | `ScheduleFilter.tsx` | Done c3c1f83 | Schedule time default hard-coded to 00:00–24:00 regardless of viewport; verified in c3c1f83. | P2 |
| 4.1.1-R11 | Schedule `Только прямые рейсы` unchecked on first-entry | 4.1.1 ¶3 | all | `ScheduleFilter.tsx` | Implemented | Toggle initialised to false pre-P1; c3c1f83 verification confirms unchanged. | P2 |
| 4.1.1-R12 | Schedule `Показать обратные рейсы` unchecked on first-entry | 4.1.1 ¶3 | all | `ScheduleFilter.tsx` | Implemented | Toggle initialised to false pre-P1; c3c1f83 verification confirms unchanged. | P2 |
| 4.1.1-R13 | Flight Map first-entry: `Город вылета` = nearest airport (auto-detected), `Город прилета` placeholder `"Куда"` | 4.1.1 ¶4 | all | `FlightsMapFilter.tsx` | Done bc0b10b | `useGeoCityDefault` shared hook preserves nearest-airport auto-fill behavior; migration verified in bc0b10b. | P2 |
| 4.1.1-R14 | Flight Map first-entry toggles: `Внутренние рейсы`=ON, `Международные регулярные рейсы`=ON, `Показать рейсы с пересадкой`=OFF | 4.1.1 ¶4 | all | `FlightsMapFilter.tsx` | Done fbb84fc | Toggle defaults aligned to TZ with geo-consent; verified by fbb84fc tests. | P2 |
| 4.1.1-R15 | Flight Map first-entry `Дата рейса` = today; map shows "паутинка" (spider) from departure city for today | 4.1.1 ¶4 | all | `MapCanvas.tsx` | TBD | P6 scope — spider rendering not addressed in P2. | P6 |
| 4.1.1-R16 | Map line rules: Direct lines if direct+connecting exist; connecting lines if only connecting; dots (full route network) if neither | 4.1.1 ¶4 | all | `MapCanvas.tsx` | TBD | P6 scope — map rendering modes not addressed in P2. | P6 |
| 4.1.1-R17 | If geo-detection fails, fall back to Online-Board start page (§4.1.6) — **no** auto-fill | 4.1.1 ¶5 | all | — | Done b023cb9 | `useGeoCityDefault` hook silently catches geo errors and leaves filter empty → start page shown with placeholders. | P2 |
| 4.1.1-R18 | Without geo consent: Online-Board "Маршрут" expanded; `Город вылета` + `Город прилета` placeholders `"Укажите город"`; `Дата рейса` placeholder `"ДД.ММ.ГГГГ"`; `Время рейса` `00:00-24:00` | 4.1.1 ¶6 | all | `OnlineBoardStartPage.tsx` | Implemented | Placeholder state pre-existing; b023cb9 verifies no-geo path leaves all placeholders intact. | P2 |
| 4.1.1-R19 | Without geo consent, "Номер рейса" mode: `Номер рейса` placeholder, `Дата рейса` placeholder `"ДД.ММ.ГГГГ"` | 4.1.1 ¶7 | all | `OnlineBoardFilter.tsx` | Implemented | Pre-existing; flight-number mode without geo shows placeholders. | P2 |
| 4.1.1-R20 | Without geo consent, Schedule start page per §4.1.7; all city/date placeholders; toggles unchecked | 4.1.1 ¶8 | all | `ScheduleStartPage.tsx` | Implemented | Pre-existing; c3c1f83 confirms no-geo path shows Schedule start page with all placeholders. | P2 |
| 4.1.1-R21 | Without geo consent, Flight Map: `Город вылета` placeholder `"Откуда"`, `Город прилета` `"Куда"`, all toggles OFF; map shows full route network as dots | 4.1.1 ¶9 | all | `FlightsMapFilter.tsx`, `MapCanvas.tsx` | Done fbb84fc | No-geo path sets all toggles OFF per fbb84fc; dots rendering is P6 scope (noted separately). | P2 |
| 4.1.1-R22 | Without geo consent, Flight Map `Дата рейса` placeholder `"ДД.ММ.ГГГ"` (sic — TZ typo, 3 Г; treat as `ДД.ММ.ГГГГ`) | 4.1.1 ¶9 | all | `FlightsMapFilter.tsx` | Done fcf4834 | Verified: placeholder uses 4-Г form via i18n key `"SHARED.DATE_FORMAT"` = `"ДД.ММ.ГГГГ"`. C5 closed. | P6 |
| 4.1.1-R23 | Search results from Online-Board/Schedule persist in-session when user re-selects filter values (do NOT reset) | 4.1.1 ¶10 | all | state: `src/shared/state/` | Implemented | Cross-section store (P1) provides per-section state that persists across filter changes; verified by P1 e2e. | P2 |
| 4.1.1-R24 | Flight Map results update immediately when filter changes (different behavior from Board/Schedule) | 4.1.1 ¶10 | all | `flights-map/hooks/*` | Done bc0b10b | Immediate-update behavior preserved by `useGeoCityDefault` migration; bc0b10b does not alter trigger logic. | P6 |
| 4.1.1-R25 | Clearing `Дата рейса` on Flight Map → map shows range `-1 day, +6 months` | 4.1.1 ¶11 | all | `flights-map/*` | TBD | Not verified in P2. Verify at P6 kickoff. | P6 |
| 4.1.1-R26 | Flight Map filter is **independent** of Online-Board / Schedule filters | 4.1.1 ¶12 | all | state isolation: `src/shared/state/` | Implemented | P1 cross-section store isolates Flight Map state; verified by 4.1.8-R3 / P1 e2e. | P6 |
| 4.1.1-R27 | Returning to Flight Map in same session restores previously entered filter + result | 4.1.1 ¶13 | all | state | Implemented | P1 cross-section store stores Flight Map state independently; return restores last filter. | P6 |

**Rules in this subsection: 27 (R22 is a flagged conflict: TZ typo).**

---

# 4.1.2 — Правила формирования URL-ссылок
*URL formation for Online-Board, Schedule, Flight Map.*

URLs encode language, area (onlineboard/schedule/flights-map), mode (route / flight-number / departure / arrival), and search parameters. TZ Table 5 enumerates every URL pattern with examples. Invalid dates outside the `-1/+14` (board), `-1/+330` (schedule), `-1/+6mo` (map) windows redirect to the respective start page. Invalid directories → 404.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.2-R1 | URL pattern (Web and Mobile identical): `https://flights.aeroflot.ru/{lang}/{area}[/{mode}/{params}]` | 4.1.2 Table 5 | all | `src/routes/[lang]/*` | Done 8160286 | Route file-layout matches Table 5; verified by e2e `e935596`. | P1 |
| 4.1.2-R2 | `{lang}` = locale+language pair (`ru-ru`, `en-us`, …). All site languages must be supported | 4.1.2 ¶1 | all | `src/i18n/` | Done 8160286 | All 9 languages exposed as route params; i18n keys verified. | P1 |
| 4.1.2-R3 | Online-Board title page: `/{lang}/onlineboard` | 4.1.2 Table 5 row 1 | all | `src/routes/[lang]/onlineboard/page.tsx` | Done 8160286 | Route exists; confirmed by e2e. | P1 |
| 4.1.2-R4 | Online-Board flight-number search: `/{lang}/onlineboard/flight/{SUNNNN}-{YYYYMMDD}`; if user entered `38` → pad to `SU0038`, `383` → `SU0383` (4-digit number, 3-char carrier code) | 4.1.2 Table 5 row 2 | all | `src/routes/[lang]/onlineboard/flight/[params]/*` | Partial f5304e2 | Guard enforced; 4-digit padding in `buildFlightUrlParams` not yet unit-tested — add test at P3 kickoff. | P1 |
| 4.1.2-R5 | Online-Board route search: `/{lang}/onlineboard/route/{fromCity}-{toCity}-{YYYYMMDD}` | 4.1.2 Table 5 row 3 | all | `src/routes/[lang]/onlineboard/route/[params]/*` | Done f2c52ca | Route guard enforced; URL builder verified by e2e `e935596`. | P1 |
| 4.1.2-R6 | Online-Board departure-only search: `/{lang}/onlineboard/departure/{fromCity}-{YYYYMMDD}` | 4.1.2 Table 5 | all | `src/routes/[lang]/onlineboard/departure/[params]/*` | Done 7d6b23b | Route guard enforced; URL builder verified. | P1 |
| 4.1.2-R7 | Online-Board arrival-only search: `/{lang}/onlineboard/arrival/{toCity}-{YYYYMMDD}` | 4.1.2 Table 5 | all | `src/routes/[lang]/onlineboard/arrival/[params]/*` | Done d633b2d | Route guard enforced; URL builder verified. | P1 |
| 4.1.2-R8 | Schedule URL patterns mirror Online-Board with `/schedule/` prefix; add range date for schedule | 4.1.2 Table 5 | all | `src/routes/[lang]/schedule/*` | Done 12807cf | Schedule codec extended for all URL patterns; guard `ead18fc` enforces window. | P1 |
| 4.1.2-R9 | Flight-Map URL pattern (route, validation) | 4.1.2 Table 5 (§4.1.24 refers) | all | `src/routes/[lang]/flights-map/*` | Done bcaf3ab | Map route verified; URL stays constant per R39–R40. | P1 |
| 4.1.2-R10 | Unknown directory → 404 page (§4.1.21) | 4.1.2 ¶2 | all | `src/routes/error/[code]/*` | Done e935596 | Verified by Playwright e2e guard tests. | P1 |
| 4.1.2-R11 | Out-of-range date → redirect to the respective **start page** (not 404) | 4.1.2 ¶3–4 | all | `fbd4438` + mode guards | Done fbd4438 | Board guards `f5304e2..57d21f6`, Schedule guard `ead18fc`; all redirect to start page. | P1 |
| 4.1.2-R12 | Date window constants: Online-Board `[-1, +14]`, Schedule `[-1, +330]`, Flight Map `[-1, +6 months]` | 4.1.2 ¶3–4 | all | `src/shared/dateWindow.ts` | Done 750a328 | Centralized in `dateWindow` module; `4f84048` removes scattered literals. | P1 |
| 4.1.2-R13 | Online-Board route search with time filter: `/{lang}/onlineboard/route/{fromCity}-{toCity}-{YYYYMMDD}-{HHMMHHmm}` — time period `{timeFrom}{timeTo}` appended to date, e.g. `06002200` | 4.1.2 Table 5 row 3 | all | `src/routes/[lang]/onlineboard/route/[params]/*` | Done f2c52ca | Optional time suffix parsed and preserved in codec; e2e verified. | P1 |
| 4.1.2-R14 | Online-Board departure-only search with time filter: `/{lang}/onlineboard/departure/{fromCity}-{YYYYMMDD}-{HHMMHHmm}` (optional time suffix) | 4.1.2 Table 5 row 4 | all | departure guard | Done 7d6b23b | Optional time suffix handled. | P1 |
| 4.1.2-R15 | Online-Board arrival-only search with time filter: `/{lang}/onlineboard/arrival/{toCity}-{YYYYMMDD}-{HHMMHHmm}` (optional time suffix) | 4.1.2 Table 5 row 5 | all | arrival guard | Done d633b2d | Optional time suffix handled. | P1 |
| 4.1.2-R16 | Online-Board details card — arrival-mode `?request=` param: `?request=onlineboard-arrival-{city}-{flightNumber}-{YYYYMMDD}[-{HHMMHHmm}]` | 4.1.2 Table 5 row 6 | all | `src/shared/detailsRequestParam.ts` | Done 531ace6 | Codec builds and parses all four `?request=` modes including arrival. | P1 |
| 4.1.2-R17 | Online-Board details card — departure-mode `?request=` param: `?request=onlineboard-departure-{city}-{flightNumber}-{YYYYMMDD}[-{HHMMHHmm}]` | 4.1.2 Table 5 row 6 | all | `src/shared/detailsRequestParam.ts` | Done 531ace6 | Departure mode covered by codec. | P1 |
| 4.1.2-R18 | Online-Board details card — route-mode `?request=` param: `?request=onlineboard-route-{dep}-{arr}-{flightNumber}-{YYYYMMDD}[-{HHMMHHmm}]` | 4.1.2 Table 5 row 6 | all | `src/shared/detailsRequestParam.ts` | Done 531ace6 | Route mode covered by codec. | P1 |
| 4.1.2-R19 | Online-Board details card — flight-number-mode `?request=` param: `?request=onlineboard-flight-{flightNumber}-{YYYYMMDD}` (no time suffix for flight-number mode) | 4.1.2 Table 5 row 6 | all | `src/shared/detailsRequestParam.ts` | Done 531ace6 | Flight-number mode omits time suffix; verified in codec unit tests. | P1 |
| 4.1.2-R20 | Online-Board details base URL (same for all four `?request=` forms): `/{lang}/onlineboard/{flightNumber}-{YYYYMMDD}` | 4.1.2 Table 5 row 6 | all | `8b22f06` (wired into page) | Done 8b22f06 | Base path uses padded flight number; codec wired into `OnlineBoardDetailsPage`. | P1 |
| 4.1.2-R21 | Online-Board "Refresh direct link" URL is identical in shape to the details card URL (row 6); no difference in path or query | 4.1.2 Table 5 row 7 | all | details page routing | Done 8b22f06 | Refresh reloads same URL — codec decodes on mount; no mutation. | P1 |
| 4.1.2-R22 | Online-Board "Share / copy link" URL: `/{lang}/onlineboard/flight/{flightNumber}-{YYYYMMDD}` — no `?request=` param; always `flight` mode regardless of original search mode | 4.1.2 Table 5 row 8 | all | — | Done 531ace6 | Share URL uses clean `/flight/` path with no `?request=`; codec does not add it for share links. | P1 |
| 4.1.2-R23 | Schedule title page URL: `/{lang}/schedule` | 4.1.2 Table 5 row 9 | all | `src/routes/[lang]/schedule/page.tsx` | Done ead18fc | Route exists; guard verified by e2e. | P1 |
| 4.1.2-R24 | Schedule route search — one-way, all connections, no time: `/{lang}/schedule/route/{dep}-{arr}-{dateFrom}-{dateTo}` (date range, both dates `YYYYMMDD`) | 4.1.2 Table 5 row 10 | all | `src/shared/detailsRequestParam.ts` | Done 12807cf | Date-range URL format implemented and parsed by schedule codec extension. | P1 |
| 4.1.2-R25 | Schedule route search — one-way, all connections, with time: `/{lang}/schedule/route/{dep}-{arr}-{dateFrom}-{dateTo}-{HHMMHHmm}` | 4.1.2 Table 5 row 10 | all | schedule codec | Done 12807cf | Time suffix appended after dateTo; verified by codec unit tests. | P1 |
| 4.1.2-R26 | Schedule route search — round-trip, no time: `/{lang}/schedule/route/{dep}-{arr}-{dateFrom}-{dateTo}/{arr}-{dep}-{returnDateFrom}-{returnDateTo}` (two slash-separated segments) | 4.1.2 Table 5 row 10 | all | schedule codec | Done 12807cf | Slash-delimited second segment for return leg implemented. | P1 |
| 4.1.2-R27 | Schedule route search — round-trip, with time: both segments each append `-{HHMMHHmm}` after their dateTo | 4.1.2 Table 5 row 10 | all | schedule codec | Done 12807cf | Both segments carry independent time filters; verified in codec tests. | P1 |
| 4.1.2-R28 | Schedule route search — one-way, direct-only (`С0`): append `-С0` suffix after the date or time token, e.g. `…-{dateTo}-С0` or `…-{HHMMHHmm}-С0` | 4.1.2 Table 5 row 10 | all | schedule codec | Done 12807cf | `-С0` suffix position verified in codec. | P1 |
| 4.1.2-R29 | Schedule route search — round-trip, direct-only: each slash segment ends with `-С0` | 4.1.2 Table 5 row 10 | all | schedule codec | Done 12807cf | Each segment carries `-С0`; round-trip case verified. | P1 |
| 4.1.2-R30 | Schedule details card URL — direct/multi-segment, one-way, no time: `/{lang}/schedule/{flightNumber}-{YYYYMMDD}?request=schedule-route-{dep}-{arr}-{dateFrom}-{dateTo}` | 4.1.2 Table 5 row 11 | all | `5728861` | Done 5728861 | Schedule search → details links emit `?request=`; verified by breadcrumb tests. | P1 |
| 4.1.2-R31 | Schedule details card URL — direct/multi-segment, one-way, with time: `?request=schedule-route-{dep}-{arr}-{dateFrom}-{dateTo}-{HHMMHHmm}` | 4.1.2 Table 5 row 11 | all | schedule codec | Done 12807cf | Time suffix included in `?request=` when filter has time range. | P1 |
| 4.1.2-R32 | Schedule details card URL — connecting flight, one-way: path uses slash-delimited flight numbers `/{fn0}-{date}/{fn1}-{date}[/{fn2}-{date}]` (max 2 connections = up to 3 segments); `?request=` same as direct | 4.1.2 Table 5 row 11 | all | schedule codec | Done 12807cf | Multi-segment path with up to 3 flight-number-date pairs implemented. | P1 |
| 4.1.2-R33 | Schedule details card URL — round-trip, no time: `?request=` encodes both outbound and return legs: `?request=schedule-route-{dep}-{arr}-{df}-{dt}-{arr}-{dep}-{rdf}-{rdt}` | 4.1.2 Table 5 row 11 | all | schedule codec | Done 12807cf | Concatenated return-leg parameters in `?request=` implemented. | P1 |
| 4.1.2-R34 | Schedule details card URL — round-trip with connections filter (`С0`): `?request=` embeds `-С0` after each date-range block | 4.1.2 Table 5 row 11 | all | schedule codec | Done 12807cf | `-С0` positions inside `?request=` for round-trip + direct filter verified. | P1 |
| 4.1.2-R35 | Schedule "Refresh direct link" URL identical to details card URL (row 11 in Table 5; same as Online-Board analogy row 7) | 4.1.2 Table 5 row 12 | all | schedule details page | Done 266a6f9 | Refresh reloads same URL; breadcrumb tests confirm no mutation. | P1 |
| 4.1.2-R36 | Schedule "Share / copy link" URL — direct/multi-segment: `/{lang}/schedule/{flightNumber}-{YYYYMMDD}` (no `?request=`) | 4.1.2 Table 5 row 13 | all | schedule codec | Done 12807cf | Share URL is clean path without `?request=`. | P1 |
| 4.1.2-R37 | Schedule "Share / copy link" URL — connecting: `/{lang}/schedule/{fn0}-{date}/{fn1}-{date}[/{fn2}-{date}]` slash-joined (no `?request=`) | 4.1.2 Table 5 row 13 | all | schedule codec | Done 12807cf | Connecting share URL uses slash-joined path without `?request=`. | P1 |
| 4.1.2-R38 | Flight Map title page URL: `/{lang}/flights-map` | 4.1.2 Table 5 (Map row 1) | all | `src/routes/[lang]/flights-map/page.tsx` | Done bcaf3ab | Route exists; title verified. | P1 |
| 4.1.2-R39 | Flight Map departure search URL: `/{lang}/flights-map` — same URL regardless of departure-city filter value (search state NOT encoded in URL) | 4.1.2 Table 5 (Map row 2) | all | `flights-map/*` | Done bcaf3ab | URL stays constant under filter changes; map state is client-side only. | P1 |
| 4.1.2-R40 | Flight Map route search URL: `/{lang}/flights-map` — same URL for all search states | 4.1.2 Table 5 (Map row 3) | all | `flights-map/*` | Done bcaf3ab | URL stays constant when both cities filled. | P1 |
| 4.1.2-R41 | Flight Map arrival-only search is **not implemented** ("Не выполняется") — no arrival-mode URL for flights-map | 4.1.2 Table 5 (Map row 4) | all | — | Done 2fe5364 | No arrival-mode route exists; map breadcrumb stays `[Главная]` only. | P1 |
| 4.1.2-R42 | Mobile URL rules are identical to web URL rules (no mobile-specific URL variants) | 4.1.2 ¶1 | mobile | — | Done 8160286 | No mobile-specific routing divergence; verified by e2e `e935596`. | P1 |

---

# 4.1.3 — Правила наименования страниц
*Page-title rules (document `<title>` + visible H1).*

Each page in the section gets a TZ-prescribed title format driven by context (area, mode, filter values, language). Translatable across all 9 site languages.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.3-R1 | Online-Board start page title per TZ Table (populate at P1 kickoff) | 4.1.3 | all | `src/ui/seo/*` | Done f03562e | Populated; all Online-Board title rows covered by `f03562e`. | P1 |
| 4.1.3-R2 | Schedule start page title per TZ | 4.1.3 | all | `src/features/schedule/*` | Done df4ac19 | Schedule titles implemented. | P1 |
| 4.1.3-R3 | Flight-Map start page title per TZ | 4.1.3 | all | `src/features/flights-map/*` | Done bcaf3ab | Flight Map title verified. | P1 |
| 4.1.3-R4 | Search-result page titles per mode (route / flight-number / departure / arrival) | 4.1.3 | all | `src/ui/seo/*` | Done f03562e | All four Online-Board search-result title formats implemented. | P1 |
| 4.1.3-R5 | Flight-details page title per TZ (direct / multi-segment / connecting) | 4.1.3 | all | `df4ac19`, `f03562e` | Done df4ac19 | Direct vs connecting branching implemented for both Board and Schedule. | P1 |
| 4.1.3-R6 | All titles translated for each of 9 languages | 4.1.3 | all | `src/i18n/*` | Done f03562e | i18n keys added for all new title formats; 9-language coverage included. | P1 |
| 4.1.3-R7 | Online-Board start page title: `Онлайн-Табло` (no dynamic parts) | 4.1.3 Table 6 row 1 | all | `src/ui/seo/*` | Done f03562e | Verified rendered title matches exactly. | P1 |
| 4.1.3-R8 | Online-Board flight-number search title: `Рейс: {номер рейса}, {дата}` — date shown as `сегодня` / `завтра` / `ДД.ММ.ГГГГ` depending on flight date relative to today | 4.1.3 Table 6 row 2 | all | `src/ui/seo/*` | Done f03562e | Date-relative label logic (today/tomorrow/formatted) implemented. | P1 |
| 4.1.3-R9 | Online-Board route search title: `Маршрут: {город вылета}-{города прилета}, {дата}` — city names in full display form; date formatted same as R8 | 4.1.3 Table 6 row 3 | all | `src/ui/seo/*` | Done f03562e | City names (not IATA codes) used; comma-separated date. | P1 |
| 4.1.3-R10 | Online-Board departure-only search title: `Вылет: {город вылета}, {дата}` | 4.1.3 Table 6 row 4 | all | `src/ui/seo/*` | Done f03562e | Departure-only title shows only departure city + date. | P1 |
| 4.1.3-R11 | Online-Board arrival-only search title: `Прилет: {город прилета}, {дата}` | 4.1.3 Table 6 row 5 | all | `src/ui/seo/*` | Done f03562e | Arrival-only title shows only arrival city + date. | P1 |
| 4.1.3-R12 | Online-Board flight details card title: `Информация о рейсе: {номер рейса}, {город вылета}-{город прилета}` | 4.1.3 Table 6 row 6 | all | `src/ui/seo/*` | Done f03562e | Format with comma after flight number implemented. | P1 |
| 4.1.3-R13 | Online-Board details via "Refresh direct link" title: same as details card — `Информация о рейсе: {номер рейса}, {город вылета}-{город прилета}` | 4.1.3 Table 6 row 7 | all | SSR via details page | Done f03562e | SSR hydrates city names; title preserved on refresh. | P1 |
| 4.1.3-R14 | Online-Board share link title: same as details card — `Информация о рейсе: {номер рейса}, {город вылета}-{город прилета}` | 4.1.3 Table 6 row 8 | all | share URL rendering | Done f03562e | Share URL renders same title as details card. | P1 |
| 4.1.3-R15 | Schedule start page title: `Расписание` (no dynamic parts) | 4.1.3 Table 6 row 9 | all | `src/features/schedule/*` | Done df4ac19 | H1/title verified as static `Расписание`. | P1 |
| 4.1.3-R16 | Schedule route search title: `Расписание по маршруту: {город вылета}-{города прилета}` — full city names, no date shown in title | 4.1.3 Table 6 row 10 | all | `src/features/schedule/*` | Done df4ac19 | No date in schedule search title; full city names confirmed. | P1 |
| 4.1.3-R17 | Schedule details card title — direct/multi-segment flight: `Расписание рейса: {номер рейса}, {город вылета}-{город прилета}` | 4.1.3 Table 6 row 11 | all | `src/features/schedule/*` | Done df4ac19 | Singular "рейса" for direct/multi-segment; city pair included. | P1 |
| 4.1.3-R18 | Schedule details card title — connecting flight (multiple flight numbers): `Расписание рейсов: {номер рейса1}, {номер рейса2}` (plural "рейсов"; no city names in connecting title) | 4.1.3 Table 6 row 11 | all | `src/features/schedule/*` | Done df4ac19 | Plural form; city names absent for connecting title. | P1 |
| 4.1.3-R19 | Schedule "Refresh direct link" title: identical rules to details card (R17 / R18 depending on flight type) | 4.1.3 Table 6 row 12 | all | SSR via schedule details page | Done df4ac19 | SSR hydrates flight type to pick direct vs connecting title form. | P1 |
| 4.1.3-R20 | Schedule "Share / copy link" title: identical rules to details card (R17 / R18) | 4.1.3 Table 6 row 13 | all | schedule details page | Done df4ac19 | Share page renders correct title form (branched by flight type). | P1 |
| 4.1.3-R21 | Flight Map title (all states): `Карта полетов` — same regardless of whether departure, route, or default state | 4.1.3 Table 6 (Map rows 1-3) | all | `src/features/flights-map/*` | Done bcaf3ab | Title verified as constant `Карта полетов` across all map states. See Conflict C1 for TZ-vs-Angular drift. | P1 |
| 4.1.3-R22 | Long city names must wrap to next line (not truncate) in title display | 4.1.3 ¶1 | all | — | TBD | Visual/CSS concern; out-of-scope for P1. Verify at P4 visual test pass. | P4 |

---

# 4.1.4 — Правила формирования хлебных крошек
*Breadcrumb rules.*

All pages in the section must have breadcrumbs; each crumb is a link except the last. Breadcrumb labels follow the same translation table as page titles.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.4-R1 | Every page has breadcrumbs (root → area → mode → result) | 4.1.4 | all | `src/ui/layout/Breadcrumbs*` | Done 1821f7f | Presence verified on all Board/Schedule/Map pages by Playwright e2e `e935596`. | P1 |
| 4.1.4-R2 | Last crumb = non-link with `aria-current="page"` | 4.1.4 + CLAUDE.md a11y | all | (see commit `826a583`) | Done 826a583 | Already Implemented; `aria-current="page"` on last crumb confirmed. See Conflict C4 for clickability nuance. | P1 |
| 4.1.4-R3 | Crumb labels translatable | 4.1.4 | all | `src/i18n/*` | Done 1821f7f | All crumb label keys added and verified across 9 locales. | P1 |
| 4.1.4-R4 | Per-page crumb list per TZ Table (populate at P1 kickoff) | 4.1.4 | all | `1821f7f`, `266a6f9`, `2fe5364` | Done 1821f7f | Fully populated across Online-Board, Schedule, and Flight Map pages. | P1 |
| 4.1.4-R5 | Online-Board start page breadcrumbs: `[Главная]` only (one crumb, links to `www.aeroflot.ru`) | 4.1.4 Table 7 row 1 | all | `src/features/online-board/components/OnlineBoardStartPage.tsx` | Done 1821f7f | Single crumb on start page verified by e2e. | P1 |
| 4.1.4-R6 | Online-Board flight-number search breadcrumbs: `[Главная / Онлайн-Табло]` — "Главная" links to `www.aeroflot.ru`; "Онлайн-Табло" links to `/{lang}/onlineboard` | 4.1.4 Table 7 row 2 | all | `src/features/online-board/*` | Done 1821f7f | Two-crumb trail for flight-number results page verified. | P1 |
| 4.1.4-R7 | Online-Board route search breadcrumbs: `[Главная / Онлайн-Табло]` (same two-crumb as R6) | 4.1.4 Table 7 row 3 | all | `src/features/online-board/*` | Done 1821f7f | Verified. | P1 |
| 4.1.4-R8 | Online-Board departure-only search breadcrumbs: `[Главная / Онлайн-Табло]` | 4.1.4 Table 7 row 4 | all | `src/features/online-board/*` | Done 1821f7f | Verified. | P1 |
| 4.1.4-R9 | Online-Board arrival-only search breadcrumbs: `[Главная / Онлайн-Табло]` | 4.1.4 Table 7 row 5 | all | `src/features/online-board/*` | Done 1821f7f | Verified. | P1 |
| 4.1.4-R10 | Online-Board details card breadcrumbs — flight-number mode: `[Главная / Онлайн-Табло / Номер рейса: {SU-1234}]` — note hyphen between carrier and number (e.g. `SU-1234`) | 4.1.4 Table 7 row 6 | all | `src/features/online-board/*` | Done 1821f7f | Leaf crumb uses hyphenated `SU-NNNN` format; verified. | P1 |
| 4.1.4-R11 | Online-Board details card breadcrumbs — route mode: `[Главная / Онлайн-Табло / Маршрут: {город вылета}-{город прилета}]` | 4.1.4 Table 7 row 6 | all | `src/features/online-board/*` | Done 1821f7f | Route-mode leaf crumb uses city names; verified. | P1 |
| 4.1.4-R12 | Online-Board details card breadcrumbs — departure mode: `[Главная / Онлайн-Табло / Вылет: {город вылета}-{город прилета}]` (TZ includes both cities even in departure-only mode) | 4.1.4 Table 7 row 6 | all | `src/features/online-board/*` | Partial 1821f7f | Leaf shows the single searched station; paired-city lookup requires API call at render time. See Conflict C3. | P1 |
| 4.1.4-R13 | Online-Board details card breadcrumbs — arrival mode: `[Главная / Онлайн-Табло / Прилет: {город вылета}-{город прилета}]` (TZ includes both cities even in arrival-only mode) | 4.1.4 Table 7 row 6 | all | `src/features/online-board/*` | Partial 1821f7f | Same as R12 — single station shown; deferred per Conflict C3. | P1 |
| 4.1.4-R14 | Clicking the last breadcrumb crumb on Online-Board details restores the previous search page with filter pre-filled AND results showing; time interval preserved in restored URL (with time → `-HHMMHHmm` present; without time → absent) | 4.1.4 Table 7 row 6 (transitions) | all | `src/features/online-board/*` | Done 1821f7f | Last crumb with `url` is now rendered as `<a>` and clickable; time-interval preserved via `?request=` codec. See Conflict C4. | P1 |
| 4.1.4-R15 | Online-Board "Refresh direct link" breadcrumbs: same three-crumb trail as details card (R10–R13 depending on mode) | 4.1.4 Table 7 row 7 | all | `src/features/online-board/*` | Done 1821f7f | Breadcrumbs on refresh deep-link match card breadcrumbs (SSR decodes `?request=`). | P1 |
| 4.1.4-R16 | Online-Board "Share / copy link" breadcrumbs: `[Главная / Онлайн-Табло]` — no leaf crumb (same as search results, two crumbs only) | 4.1.4 Table 7 row 8 | all | `src/features/online-board/*` | Done 1821f7f | Share URL (no `?request=`) renders only two crumbs, no leaf. | P1 |
| 4.1.4-R17 | Schedule start page breadcrumbs: `[Главная]` only | 4.1.4 Table 7 row 9 | all | `src/features/schedule/*` | Done 266a6f9 | Single crumb on schedule start page verified. | P1 |
| 4.1.4-R18 | Schedule route search breadcrumbs: `[Главная / Расписание]` — "Расписание" links to `/{lang}/schedule` | 4.1.4 Table 7 row 10 | all | `src/features/schedule/*` | Done 266a6f9 | Two-crumb trail for schedule results page verified. | P1 |
| 4.1.4-R19 | Schedule details card breadcrumbs: `[Главная / Расписание / {город вылета}-{город прилета}]` — city-pair as the leaf crumb | 4.1.4 Table 7 row 11 | all | `src/features/schedule/*` | Done 5728861 | Leaf crumb is city-pair (not flight number); verified by breadcrumb tests. | P1 |
| 4.1.4-R20 | Clicking schedule leaf crumb restores previous schedule search page with filter + results; dates, trip-type (one-way/round-trip), time interval all preserved | 4.1.4 Table 7 row 11 (transitions) | all | `src/features/schedule/*` | Done 5728861 | Restored schedule search URL encodes full filter state via `?request=` codec. | P1 |
| 4.1.4-R21 | Schedule "Refresh direct link" breadcrumbs: `[Главная / Расписание / {город вылета}-{город прилета}]` (same as details card, R19) | 4.1.4 Table 7 row 12 | all | `src/features/schedule/*` | Done 266a6f9 | Breadcrumbs on refresh deep-link match details card. | P1 |
| 4.1.4-R22 | Schedule "Share / copy link" breadcrumbs: `[Главная / Расписание]` — two crumbs, no leaf | 4.1.4 Table 7 row 13 | all | `src/features/schedule/*` | Done 266a6f9 | Share page shows only two crumbs (no `?request=`). | P1 |
| 4.1.4-R23 | Flight Map title page breadcrumbs: `[Главная]` only | 4.1.4 Table 7 (Map row 1) | all | `src/features/flights-map/*` | Done 2fe5364 | Single crumb on map page verified. See Conflict C2 for prior `[Главная, Карта полетов]` drift. | P1 |
| 4.1.4-R24 | Flight Map departure search breadcrumbs: `[Главная]` only (no second crumb even when filter filled) | 4.1.4 Table 7 (Map row 2) | all | `src/features/flights-map/*` | Done 2fe5364 | Breadcrumbs never expand beyond `[Главная]` on Flight Map. | P1 |
| 4.1.4-R25 | Flight Map route search breadcrumbs: `[Главная]` only | 4.1.4 Table 7 (Map row 3) | all | `src/features/flights-map/*` | Done 2fe5364 | Same single-crumb trail verified. | P1 |
| 4.1.4-R26 | "Главная" crumb always links to `www.aeroflot.ru` (external) on all pages | 4.1.4 Table 7 (all rows, transitions col) | all | `src/ui/layout/Breadcrumbs*` | Done 826a583 | External href to `www.aeroflot.ru` present on all `Главная` crumbs. | P1 |

---

# 4.1.5 — Популярные разделы Онлайн-Табло и Расписания
*"Popular sections" — aggregated Top-4 popular-request tiles on Online-Board and Schedule start pages.*

Backend stores every user search (Board + Schedule) with a timestamp. Three aggregation windows (1-min → 30-min → 1-day) produce a ranked Top-4. Tiles appear on Online-Board and Schedule start pages. Each tile click pre-fills the filter for its search type without auto-executing the search.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.5-R1 | Backend saves **every** user search executed in Online-Board or Schedule in full (all filter field values) with a timestamp | 4.1.5 ¶1 | all | `src/features/popular-requests/*` | Out-of-scope (backend) | Backend aggregation service; tracked separately; not this repo. | P2 |
| 4.1.5-R2 | Each saved search record carries a timestamp that the aggregation engine uses for windowing | 4.1.5 ¶1 | all | — | Out-of-scope (backend) | Backend service concern; tracked separately; not this repo. | P2 |
| 4.1.5-R3 | Records may be archived or deleted after **1 month** of storage | 4.1.5 ¶2 | all | — | Out-of-scope (backend) | Backend storage policy; tracked separately; not this repo. | P2 |
| 4.1.5-R4 | System provides **three aggregation windows**: 1-minute, 30-minute, 1-day | 4.1.5 ¶3 | all | — | Out-of-scope (backend) | Aggregation engine is backend; tracked separately; not this repo. | P2 |
| 4.1.5-R5 | Aggregation cadence is sequential: 1-min first, then 30-min built on top of 1-min data, then 1-day built on top of 30-min data | 4.1.5 ¶3 | all | — | Out-of-scope (backend) | Backend aggregation pipeline; tracked separately; not this repo. | P2 |
| 4.1.5-R6 | Aggregation runs only when the interval since last aggregation exceeds the window size (no redundant runs) | 4.1.5 ¶4 | all | — | Out-of-scope (backend) | Backend scheduler; tracked separately; not this repo. | P2 |
| 4.1.5-R7 | Round-trip search records carry a flag indicating whether the **return leg should also be aggregated**; for round-trip A→B: outbound A→B is aggregated, return B→A is **not** aggregated separately | 4.1.5 ¶5 | all | — | Out-of-scope (backend) | Backend aggregation flag; tracked separately; not this repo. | P2 |
| 4.1.5-R8 | Top-4 selection is based on **30-minute aggregation over the previous 24 hours** | 4.1.5 ¶6 | all | — | Out-of-scope (backend) | Backend Top-4 selection; tracked separately; not this repo. | P2 |
| 4.1.5-R9 | Aggregated queries are grouped by: search type, carrier, departure value, arrival value | 4.1.5 ¶7 | all | — | Out-of-scope (backend) | Backend grouping logic; tracked separately; not this repo. | P2 |
| 4.1.5-R10 | Grouped queries are sorted by count descending; Top-4 highest-count queries are returned | 4.1.5 ¶8 | all | — | Out-of-scope (backend) | Backend sort + take-4; tracked separately; not this repo. | P2 |
| 4.1.5-R11 | When multiple queries share the same count for the 3rd/4th Top-4 position, ordering among equals is determined by the DB implementation (no deterministic tie-break required from the frontend) | 4.1.5 ¶9 (example 2) | all | — | Out-of-scope (backend) | DB tie-break implementation; tracked separately; not this repo. UI must handle any ordering. | P2 |
| 4.1.5-R12 | Client-side cache for the Top-4 response has a TTL of **10 minutes** | 4.1.5 ¶10 | all | — | TBD | Verify cache-control header or in-memory TTL of 10 min for popular-requests fetch. Deferred to P3/P6 cache audit. | P2 |
| 4.1.5-R13 | The popular panel content is refreshed **every 30 minutes** (server-side update cadence for what tiles are displayed) | 4.1.5 ¶11 | all | — | Out-of-scope (backend) | Server-side update cadence; tracked separately. Client TTL (R12) is the frontend concern. | P2 |
| 4.1.5-R14 | Display format for a flight-number tile: `"Номер рейса: {номер рейса}"` where `{номер рейса}` is a clickable link | 4.1.5 ¶11 | all | `RequestInfo.tsx` | Done 4b6cb5b | Label format verified against TZ; matches exactly. | P2 |
| 4.1.5-R15 | Display format for a departure-only tile: `"Вылет: {город вылета}"` where `{город вылета}` is a clickable link | 4.1.5 ¶11 | all | `RequestInfo.tsx` | Done 4b6cb5b | Label format verified against TZ; matches exactly. | P2 |
| 4.1.5-R16 | Display format for an arrival-only tile: `"Прилет: {город прилета}"` where `{город прилета}` is a clickable link | 4.1.5 ¶11 | all | `RequestInfo.tsx` | Done 4b6cb5b | Label format verified against TZ; matches exactly. | P2 |
| 4.1.5-R17 | Display format for a route tile: `"Маршрут: {город вылета} - {город прилета}"` (full route, both cities) where the text is a clickable link | 4.1.5 ¶11 | all | `RequestInfo.tsx` | Done 4b6cb5b | Label format verified against TZ (dash with spaces); matches exactly. | P2 |
| 4.1.5-R18 | Display format for a one-way schedule tile: `"Расписание туда: {город вылета} - {город прилета}"` where the text is a clickable link | 4.1.5 ¶11 | all | `RequestInfo.tsx` | Done 4b6cb5b | Label format verified against TZ; matches exactly. | P2 |
| 4.1.5-R19 | Display format for a round-trip schedule tile: `"Расписание туда/обратно: {город вылета} - {город прилета}"` where the text is a clickable link | 4.1.5 ¶11 | all | `RequestInfo.tsx` | Done 4b6cb5b | Label format verified against TZ; matches exactly. | P2 |
| 4.1.5-R20 | Click on **"Прилет"** tile: opens Online-Board in **arrival mode**; `Город прилета` = tile value; `Город вылета` = "все направления"; `Дата рейса` = today; `Время рейса` = 00:00–24:00 (desktop/tablet) or −1h to +3h from current user time (mobile); search NOT auto-executed | 4.1.5 ¶12 | all | `PopularRequestItem.tsx` | Done 4b6cb5b | All prefill fields verified including viewport-conditional time window; no auto-search dispatch. | P2 |
| 4.1.5-R21 | Click on **"Вылет"** tile: opens Online-Board in **departure mode**; `Город вылета` = tile value; `Город прилета` = "все направления"; `Дата рейса` = today; `Время рейса` = 00:00–24:00 (desktop/tablet) or −1h to +3h (mobile); search NOT auto-executed | 4.1.5 ¶13 | all | `PopularRequestItem.tsx` | Done 4b6cb5b | All prefill fields verified; no auto-search dispatch. | P2 |
| 4.1.5-R22 | Click on **"Маршрут"** tile: opens Online-Board in **route mode**; `Город вылета` = tile departure; `Город прилета` = tile arrival; `Дата рейса` = today; `Время рейса` = 00:00–24:00 (desktop/tablet) or −1h to +3h (mobile); search NOT auto-executed | 4.1.5 ¶14 | all | `PopularRequestItem.tsx` | Done 4b6cb5b | All prefill fields verified; no auto-search dispatch. | P2 |
| 4.1.5-R23 | Click on **"Номер рейса"** tile: opens Online-Board in **flight-number mode**; `Номер рейса` = tile flight number; `Дата рейса` = today; search NOT auto-executed | 4.1.5 ¶15 | all | `PopularRequestItem.tsx` | Done 4b6cb5b | Prefill verified; no time field in flight-number mode; no auto-search dispatch. | P2 |
| 4.1.5-R24 | Click on **"Расписание туда"** tile: navigates to Schedule in **one-way mode**; `Город вылета` = tile departure; `Город прилета` = tile arrival; `Показать расписание на` = current week; `Только прямые` = unchecked; `Показать обратные рейсы` = unchecked; `Время вылета` = 00:00–24:00; search NOT auto-executed | 4.1.5 ¶16 | all | `PopularRequestItem.tsx` | Done 4b6cb5b | All six filter fields verified; no auto-search dispatch. | P2 |
| 4.1.5-R25 | Click on **"Расписание туда/обратно"** tile: navigates to Schedule in **round-trip mode**; `Город вылета` = tile departure; `Город прилета` = tile arrival; `Показать расписание на` = current week; `Только прямые` = unchecked; `Показать обратные рейсы` = **checked**; `Время вылета` = 00:00–24:00; `Дата обратного рейса` = next week from current week; `Время отправления обратного рейса` = 00:00–24:00; search NOT auto-executed | 4.1.5 ¶17 | all | `PopularRequestItem.tsx` | Done 4b6cb5b | All eight filter fields verified including return-trip date default; no auto-search dispatch. | P2 |
| 4.1.5-R26 | Popular panel is visible on **Online-Board start page** | 4.1.5 | all | `src/features/popular-requests/*` | Implemented | Pre-existing; panel presence verified by b27ee2a content audit. | P2 |
| 4.1.5-R27 | Popular panel is visible on **Schedule start page** | 4.1.5 | all | `src/features/popular-requests/*` | Implemented | Pre-existing; panel presence verified by b27ee2a content audit. | P2 |
| 4.1.5-R28 | Data from **Flight Map** is **NOT** collected for popular sections | 4.1.5 ¶18 | all | — | Out-of-scope (backend) | Backend negative assertion — no Flight Map events in aggregation service; tracked separately; not this repo. | P2 |
| 4.1.5-R29 | Translation for all tile label formats across 9 languages | 4.1.5 | all | `src/i18n/*` | Done 4b6cb5b | i18n key coverage for all six label formats verified in 4b6cb5b audit. | P2 |
| 4.1.5-R30 | Popular panel heading text: `"Популярные разделы Онлайн-Табло и Расписания"` | 4.1.5 ¶11 (Рисунок 6) | all | — | Done b27ee2a | Heading text verified to match TZ exactly in b27ee2a content verification. | P2 |

**Rules in this subsection: 30.**

---

# 4.1.6 — Стартовая страница «Онлайн-Табло»
*Online-Board start page composition (TZ Table 8).*

The start page is shown when the user's location cannot be determined. It has a two-column layout on desktop/tablet (filter left, content + popular panel right) and a stacked single-column layout on mobile. The right-hand content area includes an info section with four blocks, followed by the popular panel. No auto-search is executed.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.6-R1 | Start page is shown when the system **cannot determine the user's location** (geo-consent denied or detection failure) | 4.1.6 ¶1 | all | `OnlineBoardStartPage.tsx` | Implemented | Pre-existing; b023cb9 verifies no-geo path shows start page. | P2 |
| 4.1.6-R2 | Desktop/tablet: **left area** contains section-tab switcher (`Онлайн-Табло` / `Расписание` / `Карта полетов`) with `Онлайн-Табло` open, and the filter area | 4.1.6 Table 8 (desktop row) | desktop, tablet | `OnlineBoardStartPage.tsx` | Implemented | Two-column layout pre-existing; b27ee2a visual verification confirms Appendix 4/7 parity. | P2 |
| 4.1.6-R3 | Desktop/tablet filter: **"Номер рейса"** mode is **collapsed**, **"Маршрут"** mode is **expanded** on the start page | 4.1.6 Table 8 (desktop row) | desktop, tablet | `OnlineBoardFilter.tsx` | Implemented | Filter default-collapsed/expanded state pre-existing; verified during b027ee2a audit. | P2 |
| 4.1.6-R4 | Desktop/tablet: **right area** contains (top to bottom): breadcrumbs, page title, info section, popular panel | 4.1.6 Table 8 (desktop row) | desktop, tablet | — | Done b27ee2a | Right-column order verified against TZ Table 8 in b27ee2a. | P2 |
| 4.1.6-R5 | Mobile: layout is **stacked** — breadcrumbs, page title, section-tab switcher, filter (Номер рейса collapsed / Маршрут expanded), then info section, then popular panel | 4.1.6 Table 8 (mobile row) | mobile | — | Done b27ee2a | Mobile stacked order verified against Appendix 6 mockups in b27ee2a. | P2 |
| 4.1.6-R6 | Info section heading: `"Что такое Онлайн-табло и что я могу в нем увидеть?"` | 4.1.6 Table 8 (right area) | all | — | Done b27ee2a | Heading string verified to match TZ Table 8 exactly in b27ee2a content verification. | P2 |
| 4.1.6-R7 | Info block 1 — icon + title: `"Актуальная информация"` + description: `"Вся информация о фактическом выполнении рейсов."` | 4.1.6 Table 8 (right area) | all | — | Done b27ee2a | Block 1 icon, title, description verified to match TZ Table 8 exactly. | P2 |
| 4.1.6-R8 | Info block 2 — icon + title: `"Информация об услугах"` + description: `"Информация об услугах перед вылетом и на борту."` | 4.1.6 Table 8 (right area) | all | — | Done b27ee2a | Block 2 icon, title, description verified to match TZ Table 8 exactly. | P2 |
| 4.1.6-R9 | Info block 3 — icon + title: `"Купить билет"` + description: `"Вы сможете забронировать перелет и купить билет прямо из Онлайн-табло."` | 4.1.6 Table 8 (right area) | all | — | Done b27ee2a | Block 3 icon, title, description verified to match TZ Table 8 exactly. | P2 |
| 4.1.6-R10 | Info block 4 — icon + title: `"Расписание"` + description: `"Даты выполнения, время отправления и прибытия всех рейсов."` | 4.1.6 Table 8 (right area) | all | — | Done b27ee2a | Block 4 icon, title, description verified to match TZ Table 8 exactly. | P2 |
| 4.1.6-R11 | Popular panel heading: `"Популярные разделы Онлайн-Табло и Расписания"` (governed by §4.1.5) | 4.1.6 Table 8 (right area, second heading) | all | `src/features/popular-requests/*` | Done b27ee2a | Heading text verified in b27ee2a; matches 4.1.5-R30 exactly. | P2 |
| 4.1.6-R12 | Start page does **NOT** auto-execute search | 4.1.6 ¶1 | all | — | Implemented | Pre-existing; no search dispatch on start page mount. | P2 |
| 4.1.6-R13 | Info-section texts are **example-only** — may be changed by customer request; **no admin UI** is provided for text changes | 4.1.6 ¶3 | all | — | Done b27ee2a | Texts are i18n-keyed strings; no CMS layer exists or is required. Documented and verified. | P2 |
| 4.1.6-R14 | SEO meta tags + JSON-LD microdata present on start page (see §4.1.19) | 4.1.6 | all | `src/ui/seo/*` | TBD | P6 scope — microdata population deferred. | P6 |

**Rules in this subsection: 14.**

---

# 4.1.7 — Стартовая страница «Расписания»
*Schedule start page composition (TZ Table 9).*

Shown on the user's **first** open of the Schedule subsection within a session. Same two-column desktop/tablet structure as Online-Board start page, but with two distinct info sections: "Как пользоваться расписанием?" (4 blocks) and "Возможности расписания" (2 blocks). TZ Table 9 does not list a popular panel, but §4.1.5 explicitly states popular tiles appear on Schedule start too — the §4.1.5 rule governs.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.7-R1 | Start page is shown on the **first** open of Schedule in a session (not on every visit) | 4.1.7 ¶1 | all | `ScheduleStartPage.tsx` | Implemented | Pre-existing session-first trigger; c3c1f83 confirms behavior unchanged. | P2 |
| 4.1.7-R2 | Desktop/tablet: **left area** contains section-tab switcher (Онлайн-Табло / Расписание / Карта полетов, **Расписание** open) + filter area | 4.1.7 Table 9 (desktop row) | desktop, tablet | `ScheduleStartPage.tsx` | Implemented | Two-column layout pre-existing; b27ee2a visual verification confirms Appendix 4/7 parity. | P2 |
| 4.1.7-R3 | Desktop/tablet: **right area** contains breadcrumbs, page title, first info section, second info section (no popular panel explicitly in Table 9 — see reconciliation note below) | 4.1.7 Table 9 (desktop row) | desktop, tablet | — | Done b27ee2a | Right-column order verified against TZ Table 9 in b27ee2a. | P2 |
| 4.1.7-R4 | Mobile: layout is **stacked** — breadcrumbs, page title, section-tab switcher, filter, first info section, second info section | 4.1.7 Table 9 (mobile row) | mobile | — | Done b27ee2a | Mobile stacked order verified against Appendix 6 mockups in b27ee2a. | P2 |
| 4.1.7-R5 | First info section heading: `"Как пользоваться расписанием?"` | 4.1.7 Table 9 (right area, first heading) | all | — | Done b27ee2a | Heading string verified to match TZ Table 9 exactly in b27ee2a content verification. | P2 |
| 4.1.7-R6 | First info section block 1 — icon + title: `"Маршрут"` + description: `"Заполните города вылета и прилета."` | 4.1.7 Table 9 | all | — | Done b27ee2a | Block 1 icon, title, description verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R7 | First info section block 2 — icon + title: `"Дата вылета"` + description: `"Дата отправления, по умолчанию это текущая неделя."` | 4.1.7 Table 9 | all | — | Done b27ee2a | Block 2 icon, title, description verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R8 | First info section block 3 — icon + title: `"Время вылета"` + description: `"Если необходимо - уточните время отправления."` | 4.1.7 Table 9 | all | — | Done b27ee2a | Block 3 icon, title, description verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R9 | First info section block 4 — icon + title: `"Обратные рейсы"` + description: `"Вы можете выбрать показ обратных рейсов."` | 4.1.7 Table 9 | all | — | Done b27ee2a | Block 4 icon, title, description verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R10 | Second info section heading: `"Возможности расписания"` | 4.1.7 Table 9 (right area, second heading) | all | — | Done b27ee2a | Heading string verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R11 | Second info section block 1 — icon + title: `"Купить билет"` + description: `"Вы сможете забронировать перелет и купить билет прямо из Онлайн-табло."` | 4.1.7 Table 9 | all | — | Done b27ee2a | Block 1 icon, title, description verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R12 | Second info section block 2 — icon + title: `"Расписание"` + description: `"Даты выполнения, время отправления и прибытия всех рейсов."` | 4.1.7 Table 9 | all | — | Done b27ee2a | Block 2 icon, title, description verified to match TZ Table 9 exactly. | P2 |
| 4.1.7-R13 | **Spec reconciliation:** Table 9 does not list the popular panel, but §4.1.5 ¶1 states popular tiles are collected from both Online-Board **and** Schedule searches and displayed on both start pages. **§4.1.5 governs: the popular panel IS present on the Schedule start page.** It appears after the two info sections. | 4.1.5 ¶1 + 4.1.7 Table 9 (reconciliation) | all | `src/features/popular-requests/*` | Done b27ee2a | Popular panel presence on Schedule start page confirmed in b27ee2a content verification. | P2 |
| 4.1.7-R14 | Start page does **NOT** auto-execute search | 4.1.7 ¶1 | all | — | Implemented | Pre-existing; no search dispatch on start page mount. | P2 |
| 4.1.7-R15 | Info-section texts are **example-only** — may be changed by customer request; **no admin UI** is provided for text changes | 4.1.7 ¶2 | all | — | Done b27ee2a | Texts are i18n-keyed strings; no CMS layer exists or is required. Documented and verified. | P2 |
| 4.1.7-R16 | SEO meta tags + JSON-LD microdata present on start page (see §4.1.19) | 4.1.7 | all | — | TBD | P6 scope — microdata population deferred. | P6 |

**Rules in this subsection: 16.**

---

# 4.1.8 — Правила перехода между Табло, Расписание, Картой полетов в рамках одной сессии
*Cross-section navigation within a session.*

Switching tabs between Online-Board / Schedule / Flight Map preserves user input per rules: filter values carry over where applicable, results persist per §4.1.1-R23 / R27.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.8-R1 | Tab switch preserves `Город вылета` across Online-Board ↔ Schedule | 4.1.8 | all | `src/shared/crossSectionStore.ts` | Done 9863132 | Cross-section store carries `Город вылета` on tab switch; verified by e2e `e935596`. | P1 |
| 4.1.8-R2 | Tab switch preserves `Дата рейса` where the new section's date-window allows it; otherwise clamp to the new window's bounds | 4.1.8 | all | `src/shared/crossSectionStore.ts` | Done ef0e1e3 | Clamp assertion verified by unit test `ef0e1e3`; Board←Schedule projection uses `dateWindow` constants. | P1 |
| 4.1.8-R3 | Tab switch to Flight Map does **not** inherit Board/Schedule filter (§4.1.1-R26 — independence) | 4.1.8 / 4.1.1 ¶12 | all | `src/shared/crossSectionStore.ts` | Done 9863132 | Store isolates Flight Map state; no carry-over from Board/Schedule verified. | P1 |
| 4.1.8-R4 | Closing popovers on focus-change (4.1 ¶ intro: "all popups close when user focuses elsewhere") applies on tab switch too | 4.1 ¶ intro | desktop, tablet | — | Done d173159 | CityPickerPopup closes on Escape/click-outside (`d173159`); tab-switch triggers focus-loss → popup close path verified. | P3 |
| 4.1.8-R5 | Search-history ("Вы искали") is shared across Online-Board and Schedule within session, but NOT with Flight Map | 4.1.8 / 4.1.9.5 | all | — | Done 2b0a7ec | Shared sessionStorage scope confirmed; Map searches excluded; see also Conflict C7 (localStorage→sessionStorage migration). | P3 |
| 4.1.8-R6 | (Geo-consent = YES, Table 10 row 1) Online-Board default state (no search yet, city auto-detected) → switch to Schedule: Schedule filter gets `Город вылета` = user's city, `Город прилета` = placeholder ("Укажите город"), `Показать расписание на` = current week, `Время вылета` = 00:00–24:00, toggles off; search NOT auto-executed | 4.1.8 Table 10 row 1 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Schedule filter hydration wired; store projection covers this scenario. | P1 |
| 4.1.8-R7 | (Geo-consent = YES, Table 10 row 1) Returning from Schedule back to Online-Board default state: Board filter unchanged — still shows user's city, today's date, placeholder arrival, no search | 4.1.8 Table 10 row 1 (return col) | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Board state not mutated by Schedule visit; each section stores independently. | P1 |
| 4.1.8-R8 | (Geo-consent = YES, Table 10 row 2) Online-Board with search executed → switch to Schedule: Schedule gets `Город вылета` = user's geo city (from Board context, NOT the searched route departure) | 4.1.8 Table 10 row 2 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Projection uses geo-city from Board context; verified by cross-section store unit tests. | P1 |
| 4.1.8-R9 | (Geo-consent = YES, Table 10 row 2) Returning from Schedule (after Board-searched → Schedule default) to Online-Board: Board restores its own searched filter (the step-2 Board search state); search NOT auto-re-executed | 4.1.8 Table 10 row 2 (return col) | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Board restores its own step-2 filter on return. | P1 |
| 4.1.8-R10 | (Geo-consent = YES, Table 10 row 3) Online-Board default → Schedule with search executed → return to Online-Board: Board still shows default state (unaffected by Schedule search); "Переход в Расписание" col shows Schedule filter with its own search state | 4.1.8 Table 10 row 3 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Board remains at default; Schedule stores its own state independently. | P1 |
| 4.1.8-R11 | (Geo-consent = YES, Table 10 row 3) Returning to Schedule after going back to Board: Schedule restores its own searched filter from step 3 (not reset) | 4.1.8 Table 10 row 3 (return-Schedule col) | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Schedule own state survives Board round-trip. | P1 |
| 4.1.8-R12 | (Geo-consent = YES, Table 10 row 4) Both Board and Schedule have each had searches executed → switching between them: each section independently restores its own search filter state; neither overwrites the other | 4.1.8 Table 10 row 4 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Each section's search state stored independently; mutual non-overwrite verified. | P1 |
| 4.1.8-R13 | (Geo-consent = NO, Table 10 row 5) Online-Board default state (no geo, placeholders) → switch to Schedule: Schedule filter shows all placeholders (`Укажите город` for cities, `ДД.ММ.ГГГГ-ДД.ММ.ГГГГ` for date range), toggles off; no search | 4.1.8 Table 10 row 5 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | No city auto-fill when geo-consent denied; placeholder pass-through verified. | P1 |
| 4.1.8-R14 | (Geo-consent = NO, Table 10 row 5) Returning from Schedule to Online-Board: Board filter still shows placeholders (city = "Укажите город", date = "ДД.ММ.ГГГГ"), no mutation | 4.1.8 Table 10 row 5 (return col) | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Board placeholder state preserved. | P1 |
| 4.1.8-R15 | (Geo-consent = NO, Table 10 row 6) Online-Board with search executed → switch to Schedule: Schedule shows all placeholders (no carry-over of searched cities; placeholders only) | 4.1.8 Table 10 row 6 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Schedule does NOT inherit Board search filter when geo-consent denied. | P1 |
| 4.1.8-R16 | (Geo-consent = NO, Table 10 row 6) Returning from Schedule default to Online-Board: Board restores its own searched filter from step 6 | 4.1.8 Table 10 row 6 (return col) | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Board step-6 search state persists. | P1 |
| 4.1.8-R17 | (Geo-consent = NO, Table 10 row 7) Online-Board default placeholders → Schedule with search executed → return to Online-Board: Board still shows placeholders (its default); Schedule retains its searched filter state | 4.1.8 Table 10 row 7 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Board unaffected by Schedule search when geo-consent denied. | P1 |
| 4.1.8-R18 | Returning to Flight Map (from Board or Schedule) within same session always restores Flight Map's own last filter + results — completely independent from Board/Schedule state | 4.1.8 ¶ post-table note | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Map state restoration isolated from Board/Schedule filter carry-over; Flight Map section is fully independent in store. | P1 |
| 4.1.8-R19 | (Geo-consent = YES, Table 10 row 2 — negative assertion for R8) When switching from Online-Board with search executed to Schedule: Schedule does NOT copy Board's search date or arrival city; `Показать расписание на` = current week, `Город прилета` = placeholder | 4.1.8 Table 10 row 2 | all | `src/shared/crossSectionStore.ts` | Done 47fee9d | Schedule date-range resets to current week; arrival city shows placeholder (not Board's searched arrival). | P1 |

**Rules enumerated from Table 10 (all 7 rows) and post-table narrative. Total: 19 (R18 original = split into R8 positive + R19 negative).**

---

# 4.1.9 — Параметры фильтра «Онлайн-Табло», «Расписания»
*Filter parameters and their behavior.*

Specifies every filter attribute across all modes (Route / Flight-number for Board; Route for Schedule), plus validation (4.1.9.3, 4.1.9.4), autocomplete/dictionary behavior (4.1.9.1, 4.1.9.2), and "Вы искали" history (4.1.9.5). Source: TZ §4.1.9, Tables 11–16.

### Board — Flight-number mode (Table 11)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9-R1 | Filter has two collapsible modes: «Номер рейса» and «Маршрут»; chevron direction reverses on collapse | 4.1.9 ¶1 | all | `OnlineBoardFilter.tsx` | Implemented | Collapse/expand toggle pre-existing; verified by `83951d4` X-button tests that assume collapsible structure. | P3 |
| 4.1.9-R2 | Board flight-number mode «Номер рейса» field: placeholder = `"Номер рейса"` | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Implemented | Placeholder pre-existing; confirmed by `c509131` validation tests. | P3 |
| 4.1.9-R3 | «Номер рейса» format: `{SU}{ХХХХ}` — prefix `SU` is non-editable; suffix is digits only, max 4 digits | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done c509131 | Prefix lock + digit-only mask + max-4 enforced; `c509131` tightened validation and added tests. | P3 |
| 4.1.9-R4 | «Номер рейса» X button visible when field is non-empty; hidden when empty | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 83951d4 | Conditional X render added; 21 new tests verify visibility toggle. | P3 |
| 4.1.9-R5 | Clicking X on «Номер рейса» clears the value and restores placeholder; X disappears | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 83951d4 | Click-to-clear wired; X hides after clear; verified by `83951d4` tests. | P3 |
| 4.1.9-R6 | Supports character-by-character deletion (Backspace) as well as X-button bulk clear | 4.1.9 Table 11 ("посимвольно или сразу всего значения") | all | `OnlineBoardFilter.tsx` | Done 83951d4 | Both deletion paths verified (Backspace = native input; X = state clear via `83951d4`). | P3 |
| 4.1.9-R7 | Board flight-number mode «Дата рейса» placeholder (no geo): `"ДД.ММ.ГГГГ"` | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 04a3d9c | No-geo placeholder pre-existing; `04a3d9c` confirms it is preserved in no-geo path. | P3 |
| 4.1.9-R8 | Board flight-number mode «Дата рейса» placeholder (with geo): `"Сегодня"` | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 04a3d9c | Geo path sets today → label "Сегодня" via `dateLabels.ts`; verified by `04a3d9c` tests. | P3 |
| 4.1.9-R9 | «Дата рейса» accepts calendar picker or manual keyboard input in `ДД.ММ.ГГГГ` format | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Implemented | Dual entry (calendar + text) pre-existing; `c509131` adds out-of-window validation for manual input. | P3 |
| 4.1.9-R10 | «Дата рейса» calendar window: only dates in `[-1, +14]` days from today are selectable | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done c509131 | `[-1,+14]` window enforced by `dateWindow.ts`; `c509131` validates manual entry outside window. | P3 |
| 4.1.9-R11 | When selected date = today, display value changes to `"Сегодня"` (not the date string) | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 04a3d9c | Today→«Сегодня» substitution in `dateLabels.ts`; unit tests in `04a3d9c`. | P3 |
| 4.1.9-R12 | When selected date = tomorrow (today+1), display value changes to `"Завтра"` | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 04a3d9c | Tomorrow→«Завтра» substitution in `dateLabels.ts`; unit tests in `04a3d9c`. | P3 |
| 4.1.9-R13 | «Дата рейса» X button visible when field has a value; X cleared → placeholder restored | 4.1.9 Table 11 | all | `OnlineBoardFilter.tsx` | Done 83951d4 | Date X button added with visibility toggle; clear restores placeholder; tested in `83951d4`. | P3 |

### Board — Route mode (Table 12)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9-R14 | Board route mode «Город вылета» placeholder (no geo): `"Укажите город"` | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Implemented | Placeholder pre-existing; confirmed by `c509131` filter tests. | P3 |
| 4.1.9-R15 | Board route mode «Город вылета» placeholder (with geo, no value yet): shows detected city name; if geo in progress, shows `"Укажите город"` until resolved | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Implemented | Geo auto-fill pre-existing (P2 `b023cb9`); placeholder during resolution pre-existing. | P3 |
| 4.1.9-R16 | Board route mode «Город прилета» placeholder: `"Укажите город"` (both geo/no-geo) | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Implemented | Arrival placeholder pre-existing; confirmed by `c509131` tests. | P3 |
| 4.1.9-R17 | City fields: entry via manual autocomplete (§4.1.9.1) OR dictionary picker (§4.1.9.2) | 4.1.9 Table 12 | all | `CityAutocomplete.tsx` | Done dc3ee10, d173159 | Manual entry audited `dc3ee10`; dictionary picker audited `d173159`; both entry paths verified. | P3 |
| 4.1.9-R18 | City field X button visible when field has a value; click restores placeholder per Table 13 | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Done 83951d4 | CityAutocomplete X already correct (CSS `has-value`); confirmed working in `83951d4` tests. | P3 |
| 4.1.9-R19 | Board route mode «Дата рейса»: same placeholder / calendar rules / today-tomorrow substitution as R7–R13 | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Done 04a3d9c, 83951d4 | Route-mode date uses same `dateLabels.ts` logic and X button as flight-number mode; verified by `04a3d9c` + `83951d4`. | P3 |
| 4.1.9-R20 | Board route mode «Время рейса» placeholder: `"00:00 – 24:00"` for desktop + tablet (both geo and no-geo) | 4.1.9 Table 12 | desktop, tablet | `OnlineBoardFilter.tsx` | Implemented | Desktop/tablet time default pre-existing (P2 `9aed10c`). | P3 |
| 4.1.9-R21 | Board route mode «Время рейса» placeholder: `"00:00 – 24:00"` for mobile without geo; `-1/+3 от текущего времени пользователя` with geo | 4.1.9 Table 12 | mobile | `OnlineBoardFilter.tsx` | Implemented | Mobile time behavior pre-existing (P2 `9aed10c`, `b023cb9`). | P3 |
| 4.1.9-R22 | «Время рейса» uses a dual-handle slider («Время с» / «Время по») | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Implemented | Dual-handle slider pre-existing; `66518a6` adds gap enforcement on top. | P3 |
| 4.1.9-R23 | Minimum gap between «Время с» and «Время по» handles is 1 hour | 4.1.9 Table 12 ("Минимальный шаг ... 1 час") | all | `OnlineBoardFilter.tsx` | Done 66518a6 | 1h minimum gap enforced in `onTimeChange`; 48 new tests verify Board slider constraints. | P3 |
| 4.1.9-R24 | «Время по» cannot be set earlier than «Время с» | 4.1.9 Table 12 | all | `OnlineBoardFilter.tsx` | Done 66518a6 | Handle crossing blocked by 1h-gap guard in `66518a6`; handles cannot reach equal position either. | P3 |

### Board — Partial-fill placeholder behavior (Table 13)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9-R25 | When geo denied: all fields empty → placeholders as specified (Город вылета=«Укажите город», Город прилета=«Укажите город», Дата=«ДД.ММ.ГГГГ», Время=«00:00-24:00») | 4.1.9 Table 13 (no-geo, all blank) | all | `OnlineBoardFilter.tsx` | Done c509131 | No-geo all-empty state validated and tested in `c509131` filter validation tests. | P3 |
| 4.1.9-R26 | When geo granted and Вылет search: Город вылета = city name (filled), Город прилета placeholder = «Все направления», Дата = «Сегодня» (filled), Время = default range | 4.1.9 Table 13 (geo+departure) | all | `OnlineBoardFilter.tsx` | Done c509131 | Departure partial-fill state verified in `c509131`; all four field states tested. | P3 |
| 4.1.9-R27 | When X is clicked on Город вылета (departure partial-fill): Город вылета → «Укажите город», Город прилета → «Укажите город», Дата → «ДД.ММ.ГГГГ», Время → «00:00-24:00» (all reset) | 4.1.9 Table 13 ("Очищение данных") | all | `OnlineBoardFilter.tsx` | Done c509131 | Cascade clear on departure X verified in `c509131` tests. | P3 |
| 4.1.9-R28 | When geo granted and Прилет search: Город вылета placeholder = «Все направления», Город прилета = city name (filled), Дата = «Сегодня» | 4.1.9 Table 13 (geo+arrival) | all | `OnlineBoardFilter.tsx` | Done c509131 | Arrival partial-fill state verified in `c509131`. | P3 |
| 4.1.9-R29 | When X is clicked on Город прилета (arrival partial-fill): both city fields → «Укажите город», Дата → «ДД.ММ.ГГГГ» | 4.1.9 Table 13 ("Очищение данных" arrival) | all | `OnlineBoardFilter.tsx` | Done c509131 | Arrival clear cascade tested in `c509131`. | P3 |
| 4.1.9-R30 | Mobile: when geo granted, «Время рейса» = `-1/+3 от текущего времени пользователя`; clearing any city resets time to `00:00-24:00` | 4.1.9 Table 13 (mobile geo) | mobile | `OnlineBoardFilter.tsx` | Done c509131 | Mobile time reset on city clear tested in `c509131` filter validation. | P3 |

### Schedule — mode (Table 14)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9-R31 | Schedule has one mode only; no mode label shown in header | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Single-mode schedule filter pre-existing; no mode heading confirmed by `c509131` tests. | P3 |
| 4.1.9-R32 | Schedule filter is NOT collapsible (Возможность свернуть/развернуть — нет) | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | No collapse control pre-existing; confirmed by `c509131` schedule filter tests. | P3 |
| 4.1.9-R33 | Schedule «Город вылета» placeholder (no geo): `"Укажите город"` | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Placeholder pre-existing; confirmed by `c509131` tests. | P3 |
| 4.1.9-R34 | Schedule «Город вылета» placeholder (with geo): detected city name | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Geo fill pre-existing (P2 `c3c1f83`); confirmed unchanged by `c509131`. | P3 |
| 4.1.9-R35 | Schedule «Город прилета» placeholder: `"Укажите город"` (both geo/no-geo) | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Arrival placeholder pre-existing; confirmed by `c509131` tests. | P3 |
| 4.1.9-R36 | Schedule city fields: manual entry (§4.1.9.1) or dictionary picker (§4.1.9.2); X clears + restores placeholder | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done dc3ee10, d173159 | Both entry paths verified (same `CityAutocomplete` component); X clear confirmed by `83951d4`. | P3 |
| 4.1.9-R37 | Schedule «Показать расписание на» placeholder (no geo): `"ДД.ММ.ГГГГ-ДД.ММ.ГГГГ"` | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Placeholder pre-existing; no-geo path confirmed by `8f4d5fc` tests. | P3 |
| 4.1.9-R38 | Schedule «Показать расписание на» placeholder (with geo): `"Текущая неделя"` | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done 8f4d5fc | Geo path sets current-week label via `dateLabels.ts`; unit tests in `8f4d5fc`. | P3 |
| 4.1.9-R39 | Selection in «Показать расписание на» is by whole weeks | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Week-granularity calendar pre-existing; `c509131` validation tests exercise week boundaries. | P3 |
| 4.1.9-R40 | Calendar window for «Показать расписание на»: `[-1, +330]` days from today | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done c509131 | `[-1,+330]` window enforced; `c509131` validates manual entry outside window with `"Некорректная дата"`. | P3 |
| 4.1.9-R41 | If selected week contains today, display value = `"Текущая неделя"` (not the date string) | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done 8f4d5fc | Current-week substitution in Schedule `dateLabels.ts`; unit tests in `8f4d5fc`. | P3 |
| 4.1.9-R42 | «Показать расписание на» X clears value and restores placeholder; X hidden when empty | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done 83951d4 | Schedule outbound date X added with visibility toggle; tested in `83951d4`. | P3 |
| 4.1.9-R43 | Schedule «Время вылета» placeholder: `"00:00 – 24:00"` (all viewports, all geo states) | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Time default pre-existing (P2 `c3c1f83`); no geo variant for schedule — confirmed. | P3 |
| 4.1.9-R44 | Schedule «Время вылета»: dual-handle slider, 1h minimum gap, «Время по» cannot precede «Время с» | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done 66518a6 | 1h gap + crossing guard added for Schedule slider in `66518a6`; 64 new Schedule slider tests. | P3 |
| 4.1.9-R45 | Schedule «Только прямые рейсы»: checkbox, initially unchecked | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Default unchecked pre-existing (P2 `c3c1f83`). | P3 |
| 4.1.9-R46 | Schedule «Показать обратные рейсы»: checkbox, initially unchecked | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Default unchecked pre-existing (P2 `c3c1f83`). | P3 |
| 4.1.9-R47 | When «Показать обратные рейсы» is checked, two extra fields appear: «Дата обратного рейса» and «Время отправления обратного рейса» | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Conditional return fields pre-existing; `c509131` validation tests exercise the round-trip path. | P3 |
| 4.1.9-R48 | «Дата обратного рейса» placeholder: `"ДД.ММ.ГГГГ-ДД.ММ.ГГГГ"` | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Return date placeholder pre-existing; confirmed by `c509131`. | P3 |
| 4.1.9-R49 | «Дата обратного рейса» selection by whole weeks; calendar window `[-1, +330]` | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done c509131 | Return week window enforced; cross-validation with outbound week tested in `c509131`. | P3 |
| 4.1.9-R50 | «Дата обратного рейса» X clears + restores placeholder; X hidden when empty | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done 83951d4 | Schedule return date X added with visibility toggle; tested in `83951d4`. | P3 |
| 4.1.9-R51 | «Время отправления обратного рейса» placeholder: `"00:00 – 24:00"` | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Implemented | Return time placeholder pre-existing. | P3 |
| 4.1.9-R52 | «Время отправления обратного рейса»: dual-handle slider, 1h min gap, time-by cannot precede time-from | 4.1.9 Table 14 | all | `ScheduleFilter.tsx` | Done 66518a6 | Return time slider 1h gap + crossing guard added in `66518a6` alongside outbound slider. | P3 |

### Cross-cutting calendar rule

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9-R53 | If the day boundary is crossed within a session, the date `{-2 days from today}` becomes inactive in the calendar automatically | 4.1.9 ¶ after Table 14 | all | calendar component | TBD (P4) | Not addressed in P3 — reactive midnight calendar update is a calendar component concern; deferred to P4 when calendar is enumerated. | P4 |
| 4.1.9-R54 | Mobile Board only: 3 quick-date chips above the calendar: today, today+1, today+2 | 4.1.9 ¶ "Для мобильной версии" | mobile | `OnlineBoardFilter.tsx` | TBD (P4) | Mobile date-chip strip not explicitly wired in P3; deferred to P4 calendar enumeration. | P4 |
| 4.1.9-R55 | Tapping today chip sets date field to `"Сегодня"` | 4.1.9 ¶ "При клике" | mobile | `OnlineBoardFilter.tsx` | TBD (P4) | Depends on R54 chip strip; label logic (`04a3d9c`) is ready. Deferred to P4. | P4 |
| 4.1.9-R56 | Tapping today+1 chip sets date field to `"Завтра"` | 4.1.9 ¶ "При клике" | mobile | `OnlineBoardFilter.tsx` | TBD (P4) | Depends on R54 chip strip; label logic (`04a3d9c`) is ready. Deferred to P4. | P4 |
| 4.1.9-R57 | Tapping today+2 chip sets date field to `ДД.ММ.ГГГГ` of today+2 date | 4.1.9 ¶ "При клике" | mobile | `OnlineBoardFilter.tsx` | TBD (P4) | Depends on R54 chip strip. Deferred to P4. | P4 |
| 4.1.9-R58 | Mobile geo-granted: «Время рейса» auto-set to `-1/+3h` from current time for Online-Board only (not Schedule) | 4.1.9 ¶ "Если разрешили" | mobile | `OnlineBoardFilter.tsx` | Implemented | Board-only mobile time auto-set pre-existing (P2 `9aed10c`, `b023cb9`); Schedule unaffected (confirmed by `8f4d5fc`). | P3 |

**Rules in this subsection: 58 (R1–R58). Sub-subsection rules counted separately below.**

---

## 4.1.9.1 — Поведение при ручном заполнении атрибутов «Город вылета», «Город прилета»
*Manual city-field entry: autocomplete / quick-search behavior.*

Source: TZ §4.1.9.1 (lines 600–608).

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9.1-R1 | Typing in a city field triggers quick-search after the first character | 4.1.9.1 ¶1 ("после ввода первой буквы") | all | `CityAutocomplete.tsx` | Done dc3ee10 | Dropdown-on-first-keystroke confirmed by assertion tests in `dc3ee10`. | P3 |
| 4.1.9.1-R2 | Search matches by substring in city/airport name AND by 3-character IATA code | 4.1.9.1 ¶1 ("по вхождению символов … по их кодам") | all | `CityAutocomplete.tsx` | Done dc3ee10 | Both name and IATA-code matching verified by `dc3ee10` `searchCities` tests. | P3 |
| 4.1.9.1-R3 | Results are grouped: city → airports in that city | 4.1.9.1 ¶2 item 1 | all | `CityAutocomplete.tsx` | Done dc3ee10 | City-grouped dropdown structure confirmed by `dc3ee10` assertion tests. | P3 |
| 4.1.9.1-R4 | If a city name matches, the city and all its airports appear in results | 4.1.9.1 ¶2 item 2 | all | `CityAutocomplete.tsx` | Done dc3ee10 | City-match shows city + all airports verified in `dc3ee10`. | P3 |
| 4.1.9.1-R5 | If an airport matches (but not the city name), show the containing city plus sibling airports of that airport | 4.1.9.1 ¶2 item 3 | all | `CityAutocomplete.tsx` | Done dc3ee10 | Airport-match parent-city + siblings verified in `dc3ee10`. | P3 |
| 4.1.9.1-R6 | Keyboard-layout transliteration fallback: if no match found by IATA code, re-interpret input as the opposite keyboard layout (e.g. `vjc` → `мос`) and retry | 4.1.9.1 ¶3 ("полученных в английской раскладке … русской раскладке") | all | `CityAutocomplete.tsx` | Done dc3ee10 | `keyboardLayoutConverter.ts` added; EN→RU retry wired into `searchCities`; unit tested in `dc3ee10`. | P3 |
| 4.1.9.1-R7 | Dropdown shows at most 10 results (top 10) | 4.1.9.1 ¶4 ("не более 10 наименований") | all | `CityAutocomplete.tsx` | Done dc3ee10 | 10-result cap verified by `dc3ee10` assertion tests. | P3 |
| 4.1.9.1-R8 | Results sorted alphabetically in the active locale | 4.1.9.1 ¶5 ("сортировка в алфавитном порядке согласно текущему языку") | all | `CityAutocomplete.tsx` | Done dc3ee10 | Locale-aware sort confirmed by `dc3ee10` assertion tests. | P3 |
| 4.1.9.1-R9 | Matching characters in the result label are visually highlighted | 4.1.9.1 (implied by "быстрый поиск" design convention; consistent with Angular reference) | all | `CityAutocomplete.tsx` | Done dc3ee10 | Highlight markup confirmed by `dc3ee10` assertion tests. | P3 |
| 4.1.9.1-R10 | When the user's typed value exactly matches one result, selecting it (Enter or click) commits the value to the field | 4.1.9.1 (exact-match commit — prior commit `9efc76b`) | all | `CityAutocomplete.tsx` | Done dc3ee10 | Exact-match auto-commit pre-existing (`9efc76b`); locked by `dc3ee10` assertion tests. | P3 |
| 4.1.9.1-R11 | Pressing Escape while dropdown is open closes it without committing any value | 4.1.9.1 (cancel behavior) | all | `CityAutocomplete.tsx` | Done dc3ee10 | Escape-restores-last-committed implemented in `dc3ee10`; tested in CityAutocomplete tests. | P3 |
| 4.1.9.1-R12 | Focus loss (click outside) closes the dropdown without committing | 4.1.9.1 (focus-loss) | all | `CityAutocomplete.tsx` | Done dc3ee10 | Click-outside dismiss verified by `dc3ee10` assertion tests. | P3 |

**Rules in §4.1.9.1: 12 (R1–R12).**

---

## 4.1.9.2 — Поведение при выборе значений из справочника для атрибутов вида «Город»
*Dictionary city-picker behavior.*

Source: TZ §4.1.9.2 (lines 609–627), Table 15.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9.2-R1 | Dictionary picker opens when city field receives focus (or on explicit trigger) | 4.1.9.2 ¶1 | all | `CityPickerPopup.tsx` | Done d173159 | Picker-on-focus confirmed by `d173159` CityPickerPopup tests. | P3 |
| 4.1.9.2-R2 | Cities grouped first by region/direction: «Россия и СНГ» is always first; other regions follow alphabetically | 4.1.9.2 Table 15 + ¶ after table | all | `CityPickerPopup.tsx` | Done d173159 | «Россия и СНГ» first + alphabetical remaining regions confirmed by `d173159` tests. | P3 |
| 4.1.9.2-R3 | Within «Россия и СНГ», Russia is first; within Russia, Moscow is the first city | 4.1.9.2 ¶ ("первая должна быть страна «Россия», город «Москва»") | all | `CityPickerPopup.tsx` | Done d173159 | Russia/Moscow pinned positions verified by `d173159` assertion tests. | P3 |
| 4.1.9.2-R4 | Countries within each region sorted alphabetically | 4.1.9.2 ¶ ("остальные страны … в алфавитном порядке") | all | `CityPickerPopup.tsx` | Done d173159 | Country sort within region verified by `d173159` tests. | P3 |
| 4.1.9.2-R5 | Cities within each country displayed in alphabetical order, arranged by columns (left-to-right) | 4.1.9.2 ¶ ("в алфавитном порядке по столбцам слева-направо") | all | `CityPickerPopup.tsx` | Done d173159 | Column-based alphabetical city layout confirmed by `d173159` tests. | P3 |
| 4.1.9.2-R6 | If a city has airports, they are listed under the city in alphabetical order | 4.1.9.2 ¶ ("аэропорты … в алфавитном порядке") | all | `CityPickerPopup.tsx` | Done d173159 | Per-city airport sub-list sorted alphabetically; verified by `d173159` tests. | P3 |
| 4.1.9.2-R7 | Keyboard arrow-key navigation moves focus through the picker list | 4.1.9.2 (keyboard nav convention) | desktop, tablet | `CityPickerPopup.tsx` | Done d173159 | ArrowDown/Up navigation added with `aria-activedescendant`; tested in `d173159`. | P3 |
| 4.1.9.2-R8 | Enter key commits the currently focused item in the picker | 4.1.9.2 (keyboard commit) | desktop, tablet | `CityPickerPopup.tsx` | Done d173159 | Enter-commits-highlighted-item implemented and tested in `d173159`. | P3 |
| 4.1.9.2-R9 | Escape key closes the picker without committing | 4.1.9.2 (keyboard cancel) | all | `CityPickerPopup.tsx` | Done d173159 | Escape closes picker without committing; tested in `d173159`. | P3 |
| 4.1.9.2-R10 | Click outside the picker closes it without committing | 4.1.9.2 (click-outside) | all | `CityPickerPopup.tsx` | Done d173159 | Click-outside dismiss verified by `d173159` tests. | P3 |
| 4.1.9.2-R11 | Picker list is scrollable when content exceeds visible area | 4.1.9.2 (scroll implied by large list) | all | `CityPickerPopup.tsx` | Done d173159 | Scrollable container confirmed by `d173159` (overflow-y CSS + overflow assertion). | P3 |

**Rules in §4.1.9.2: 11 (R1–R11).**

---

## 4.1.9.3 — Поведение и валидация атрибутов фильтра
*Filter validation rules.*

Source: TZ §4.1.9.3 (lines 628–642).

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9.3-R1 | All fillable filter fields are validated before the search request is sent | 4.1.9.3 ¶1 | all | `OnlineBoardFilter.tsx`, `ScheduleFilter.tsx` | Done c509131 | Pre-submit validation gate enforced; tested in `c509131` (77 new OB + 141 new Schedule tests). | P3 |
| 4.1.9.3-R2 | Invalid «Номер рейса»: field highlighted + tooltip `"Неверно указан номер рейса. Номер рейса может состоять только из цифр и не должен быть длиннее 4-х символов."` | 4.1.9.3 ¶1 item 1 | all | `OnlineBoardFilter.tsx` | Done c509131 | Exact error message text verified by `c509131` tests; i18n keys added for all 9 languages. | P3 |
| 4.1.9.3-R3 | When Город вылета = Город прилета: «Город вылета» field highlighted red + tooltip `"Город вылета не может быть равен городу прилета"` | 4.1.9.3 ¶1 item 2 | all | `OnlineBoardFilter.tsx`, `ScheduleFilter.tsx` | Done c509131 | Same-city validation and exact error text verified by `c509131` tests for both Board and Schedule. | P3 |
| 4.1.9.3-R4 | Invalid value in «Город вылета»: field highlighted + tooltip `"Неверно указан пункт вылета"` | 4.1.9.3 ¶1 item 3 | all | `OnlineBoardFilter.tsx`, `ScheduleFilter.tsx` | Done c509131 | Invalid-departure error text and highlight verified by `c509131`. | P3 |
| 4.1.9.3-R5 | If only «Город прилета» is filled (Город вылета empty), search proceeds as arrival-only — **no** «Неверно указан пункт вылета» error | 4.1.9.3 ¶ after item 3 | all | `OnlineBoardFilter.tsx` | Done c509131 | Arrival-only partial search permitted; departure-empty is not an error; verified by `c509131`. | P3 |
| 4.1.9.3-R6 | Invalid value in «Город прилета»: field highlighted + tooltip `"Неверно указан пункт прилета"` | 4.1.9.3 ¶1 item 4 | all | `OnlineBoardFilter.tsx`, `ScheduleFilter.tsx` | Done c509131 | Invalid-arrival error text and highlight verified by `c509131`. | P3 |
| 4.1.9.3-R7 | If only «Город вылета» is filled (Город прилета empty), search proceeds as departure-only — **no** «Неверно указан пункт прилета» error | 4.1.9.3 ¶ after item 4 | all | `OnlineBoardFilter.tsx` | Done c509131 | Departure-only partial search permitted; arrival-empty is not an error; verified by `c509131`. | P3 |
| 4.1.9.3-R8 | Manual date entry outside the allowed calendar window: field highlighted with red border + tooltip `"Некорректная дата"` | 4.1.9.3 ¶1 item 5 | all | date input components | Done c509131 | Out-of-window date validation added with `"Некорректная дата"` error; tested in `c509131`. | P3 |
| 4.1.9.3-R9 | Online-Board date validation window: `[-1, +14]` from today | 4.1.9.3 ¶ "Валидация календаря в Онлайн-табло" | all | `OnlineBoardFilter.tsx` | Done c509131 | Board `[-1,+14]` window from `dateWindow.ts` verified in `c509131` tests. | P3 |
| 4.1.9.3-R10 | Schedule outbound date validation window: `[-1, +330]` from today | 4.1.9.3 ¶ "Валидация календаря в Расписании (рейсы туда)" | all | `ScheduleFilter.tsx` | Done c509131 | Schedule `[-1,+330]` outbound window verified in `c509131` tests. | P3 |
| 4.1.9.3-R11 | Schedule return date validation window: `[+1, +330]` from today (note: lower bound is +1, not -1) | 4.1.9.3 ¶ "Валидация календаря в Расписании (рейсы обратно)" | all | `ScheduleFilter.tsx` | Done c509131 | Return-date lower bound `+1` enforced; tested separately in `c509131`. | P3 |
| 4.1.9.3-R12 | Flight Map filter: **no** validation on any filter fields | 4.1.9.3 ¶ "Валидация параметров фильтра подраздела «Карта полетов» не предусматривается" | all | `FlightsMapFilter.tsx` | Done c509131 | Absence of validation on Flight Map filter confirmed as deliberate; `c509131` does not touch `FlightsMapFilter`. | P3 |
| 4.1.9.3-R13 | Validation errors block the search submit; fields in error are scrolled into view | 4.1.9.3 ¶1 (implied by "поля должны подсвечиваться") | all | filter components | Done c509131 | Submit is blocked when any error is active; error fields rendered with red highlight; tested in `c509131`. | P3 |

**Rules in §4.1.9.3: 13 (R1–R13).**

---

## 4.1.9.4 — Поведение и валидация атрибутов «Показать расписание на» и «Дата обратного рейса»
*Return-date / outbound-range cross-validation.*

Source: TZ §4.1.9.4 (lines 643–660), Table 16.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9.4-R1 | When «Показать обратные рейсы» is checked, the return-date week must not be **earlier** than the outbound-date week | 4.1.9.4 ¶1 | all | `ScheduleFilter.tsx` | Done c509131 | Return-before-outbound guard implemented; tested in `c509131` schedule cross-validation tests. | P3 |
| 4.1.9.4-R2 | Outbound week = X chosen → return calendar disables weeks from [current week] through [X−1]; week X itself is selectable (same-week round-trip allowed) | 4.1.9.4 Table 16 row 3 | all | `ScheduleFilter.tsx` | Done c509131 | Return calendar min-week set to outbound week on selection; week X selectable; tested in `c509131`. | P3 |
| 4.1.9.4-R3 | If outbound week is changed to a new value X1 that is still ≤ previously selected return week Z, return value Z is preserved | 4.1.9.4 Table 16 row 3.1 | all | `ScheduleFilter.tsx` | Done c509131 | Outbound-change within range preserves return; tested in `c509131`. | P3 |
| 4.1.9.4-R4 | If outbound week is changed to X2 > previously selected return week Z, return field is reset to placeholder (value cleared) | 4.1.9.4 Table 16 row 3.2 | all | `ScheduleFilter.tsx` | Done c509131 | Outbound-beyond-return clears return; tested in `c509131`. | P3 |
| 4.1.9.4-R5 | Clearing outbound («Показать расписание на» X): outbound → placeholder; return date, if already set to the current week or later, is reset to the current-week value | 4.1.9.4 Table 16 row 4 | all | `ScheduleFilter.tsx` | Done c509131 | Outbound-clear resets return to current week; tested in `c509131`. | P3 |
| 4.1.9.4-R6 | After outbound cleared (row 4): if user picks a return date Z1 > current week, and then re-picks outbound to a week that still ≤ Z1, both values are preserved | 4.1.9.4 Table 16 row 4.1 | all | `ScheduleFilter.tsx` | Done c509131 | Post-clear independence of return pick tested in `c509131`. | P3 |
| 4.1.9.4-R7 | After outbound cleared + return = current week Z: picking outbound = Z preserves return = Z (same-week allowed) | 4.1.9.4 Table 16 row 4.2 | all | `ScheduleFilter.tsx` | Done c509131 | Same-week scenario validity tested in `c509131`. | P3 |
| 4.1.9.4-R8 | If outbound is picked to X3 > both Z and Z1 (previous return values from rows 4 and 4.1), return is cleared | 4.1.9.4 Table 16 row 4.3 | all | `ScheduleFilter.tsx` | Done c509131 | Beyond-both-values case clears return; tested in `c509131`. | P3 |
| 4.1.9.4-R9 | Clearing return («Дата обратного рейса» X) while outbound is set to week X: return → placeholder; outbound unchanged; return becomes available at week X and later | 4.1.9.4 Table 16 row 5 | all | `ScheduleFilter.tsx` | Done c509131 | Clear-return-only resets only the return field; outbound unchanged; tested in `c509131`. | P3 |

**Rules in §4.1.9.4: 9 (R1–R9).**

---

## 4.1.9.5 — Правила вывода истории поиска «Вы искали»
*Search-history ("You searched") block.*

Source: TZ §4.1.9.5 (lines 661–678), Table 17.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.9.5-R1 | «Вы искали» block is hidden on first open of the section; appears only after at least one search is performed | 4.1.9.5 ¶1 ("При первоначальном открытии … блок должен быть скрыт") | all | `SearchHistory.tsx` | Done 2b0a7ec | Empty initial state verified by `2b0a7ec` session-storage tests. | P3 |
| 4.1.9.5-R2 | Block is positioned below the filter area | 4.1.9.5 ¶1 ("под блоком «Фильтра»") | all | layout | Done 2b0a7ec | DOM layout order confirmed by `2b0a7ec` assertion tests. | P3 |
| 4.1.9.5-R3 | History is shared across Online-Board and Schedule (same list); Flight Map searches are NOT included | 4.1.9.5 ¶1 + ¶2 | all | `src/shared/storage.ts` | Done 2b0a7ec | Shared sessionStorage scope confirmed; Map searches excluded (Map hooks do not call history hook); tested in `2b0a7ec`. | P3 |
| 4.1.9.5-R4 | When history list is long, a scrollbar is provided | 4.1.9.5 ¶3 | all | `SearchHistory.tsx` | Done 2b0a7ec | Scrollable container verified by `2b0a7ec` overflow tests. | P3 |
| 4.1.9.5-R5 | Board flight-number search entry format: icon (board icon) + `{Номер рейса}` including carrier (e.g. `SU 1234`) + `{Дата рейса}` in `ДД.ММ.ГГГГ`; mode label «Номер рейса» is NOT shown | 4.1.9.5 Table 17 row 1 | all | `SearchHistory.tsx` | Done 2b0a7ec | Flight-number entry format verified by `2b0a7ec` item-type tests. | P3 |
| 4.1.9.5-R6 | Board departure search entry: icon + `{Город вылета}` + `{Дата}` + `{Время}` (time omitted if `00:00-24:00`) | 4.1.9.5 Table 17 row 2 | all | `SearchHistory.tsx` | Done 2b0a7ec | Departure entry format + time suppression for full-day verified by `2b0a7ec`. | P3 |
| 4.1.9.5-R7 | Board arrival search entry: icon + `{Город прилета}` + `{Дата}` + `{Время}` (time omitted if `00:00-24:00`) | 4.1.9.5 Table 17 row 3 | all | `SearchHistory.tsx` | Done 2b0a7ec | Arrival entry format verified by `2b0a7ec`. | P3 |
| 4.1.9.5-R8 | Board route (both cities) search entry: icon + `{Город вылета}` + `{Город прилета}` + `{Дата}` + `{Время}` (time omitted if full-day); mode label «Маршрут» NOT shown | 4.1.9.5 Table 17 row 4 | all | `SearchHistory.tsx` | Done 2b0a7ec | Route entry format + no-label confirmed by `2b0a7ec`. | P3 |
| 4.1.9.5-R9 | Schedule one-way entry: schedule icon + `{Город вылета}` + `{Город прилета}` + `{Неделя}` in `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ` format | 4.1.9.5 Table 17 row 5 | all | `SearchHistory.tsx` | Done 2b0a7ec | Schedule one-way entry format verified by `2b0a7ec`. | P3 |
| 4.1.9.5-R10 | Schedule one-way direct-only entry: same format as R9 (icon distinguishes "direct-only" variant) | 4.1.9.5 Table 17 row 6 | all | `SearchHistory.tsx` | Done 2b0a7ec | Direct-only variant stored and displayed; icon variant tested in `2b0a7ec`. | P3 |
| 4.1.9.5-R11 | Schedule round-trip entry: schedule icon + both cities + outbound week + return week | 4.1.9.5 Table 17 row 7 | all | `SearchHistory.tsx` | Done 2b0a7ec | Round-trip entry format with two week ranges verified by `2b0a7ec`. | P3 |
| 4.1.9.5-R12 | Schedule round-trip direct-only entry: same as R11 with direct-only icon variant | 4.1.9.5 Table 17 row 8 | all | `SearchHistory.tsx` | Done 2b0a7ec | Round-trip direct-only entry verified by `2b0a7ec`. | P3 |
| 4.1.9.5-R13 | Hovering a history item (desktop): background turns blue, text + icon turn white | 4.1.9.5 Table 17 (behavior col) | desktop | `SearchHistory.tsx` | Done 2b0a7ec | `SearchHistory.scss` hover fixed to full blue background + white text per TZ; committed in `2b0a7ec`. | P3 |
| 4.1.9.5-R14 | Clicking a history item: pre-fills the corresponding filter mode with all stored parameters AND auto-executes search | 4.1.9.5 Table 17 (behavior col: "При клике должен заполняться фильтр … и выполнятся автоматический поиск") | all | `SearchHistory.tsx` | Done 2b0a7ec | Click rehydrates filter + triggers search; dedup + bump-to-top + most-recent-first ordering all tested in `2b0a7ec`. | P3 |

**Rules in §4.1.9.5: 14 (R1–R14).**

---

# 4.1.10 — Правила выполнения поиска рейсов Онлайн-Табло
*Online-Board search execution rules.*

Source: TZ §4.1.10 (lines 681–698).

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.10-R1 | Search executes only on explicit submit (button click) — not triggered automatically by attribute change | 4.1.10 ¶1 | all | `OnlineBoardFilter.tsx` | Done a5c64a2 | No auto-search on field change confirmed; submit-only trigger tested in `a5c64a2`. | P3 |
| 4.1.10-R2 | Submit button is blocked for 30 seconds after each search dispatch; re-submission only allowed after the 30s throttle expires | 4.1.10 ¶2 ("Кнопка выполнения поиска должна блокироваться на 30 сек") | all | `OnlineBoardFilter.tsx` | Done a5c64a2 | 30s hard-coded lock implemented in `OnlineBoardFilter.tsx`; tested in `a5c64a2`. | P3 |
| 4.1.10-R3 | During search: loader is shown; «Отменить поиск» button appears | 4.1.10 ¶3 | all | `OnlineBoardSearchPage.tsx` | Done a5c64a2 | Loader + «Отменить поиск» button rendered while fetch in flight; tested in `a5c64a2`. | P3 |
| 4.1.10-R4 | During search: section tabs, filter, breadcrumbs, and day-tabs are all non-interactive (disabled for click) | 4.1.10 ¶3 | all | layout / overlay | Done a5c64a2 | `data-searching` attribute on search pages disables filter/tabs/breadcrumbs via `pointer-events:none` CSS; implemented in `a5c64a2`. | P3 |
| 4.1.10-R5 | Search operates across three time sources: scheduled, expected (forecast), and actual times | 4.1.10 ¶4 | all | `src/shared/api/*` | Done a5c64a2 | All three time sources requested by API; AbortController wired through `ApiClient → api → hooks` in `a5c64a2`. | P3 |
| 4.1.10-R6 | Search covers both direct flights and multi-segment flights (same flight number, multiple legs) | 4.1.10 ¶5–6 | all | `src/shared/api/*` | Done a5c64a2 | Multi-segment flights included in results; API query confirmed in `a5c64a2` hook audit. | P3 |
| 4.1.10-R7 | If «Номер рейса» contains fewer than 4 digits, pad with leading zeros before querying (e.g. `38` → `0038`) | 4.1.10 ¶8 | all | `OnlineBoardFilter.tsx` + api layer | Done a5c64a2 | Zero-padding applied before API call; also see 4.1.2-R4; confirmed in `a5c64a2`. | P3 |
| 4.1.10-R8 | If «Номер рейса» has exactly 4 digits, search by exact match (no padding) | 4.1.10 ¶8 | all | api layer | Done a5c64a2 | 4-digit number not further padded; tested in `a5c64a2`. | P3 |
| 4.1.10-R9 | Successful search: navigate to result URL (per §4.1.2) before rendering results | 4.1.10 | all | routing | Done a5c64a2 | URL-first navigation confirmed; page is refresh-safe; verified in `a5c64a2`. | P3 |

## 4.1.10.1 — Обработка ошибок поиска (Онлайн-Табло)
*Online-Board search error handling.*

Source: TZ §4.1.10.1 / lines 699–721, Table 18.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.10.1-R1 | Flight-number search returns only Aurora/Pobeda flights: show message `"Вы указали рейс авиакомпании группы «Аэрофлот» или рейс авиакомпании партнера. Фактическая информация о выполнении рейсов доступна только для собственных рейсов авиакомпании «Аэрофлот». Перейдите на сайт фактического перевозчика по следующей ссылке."` with link to carrier site (Pobeda: `pobeda.aero`; Aurora: `flyaurora.ru`) | 4.1.10.1 ¶1–4 | all | `src/ui/errors/*` | Done a5c64a2 | Per-status error messages `BOARD.ERROR-TIMEOUT / ERROR-4XX / ERROR-5XX` added; Aurora/Pobeda carrier links verified in `a5c64a2`. | P3 |
| 4.1.10.1-R2 | Route/Departure/Arrival search returns only Aurora/Pobeda/other-carrier flights: show `"По выбранным параметрам не найдено рейсов. Измените параметры поиска."` | 4.1.10.1 ¶2 | all | `src/ui/errors/*` | Done a5c64a2 | Empty-results error message wired; error banners tested in `a5c64a2`. | P3 |
| 4.1.10.1-R3 | Flight-number mode: search submitted with no parameters filled → show `"Не верно заданы параметры поиска укажите Номер и Дату рейса."` | 4.1.10.1 ¶3 item 3 | all | `OnlineBoardFilter.tsx` | Done a5c64a2 | Missing-params error for flight-number mode implemented and tested in `a5c64a2`. | P3 |
| 4.1.10.1-R4 | Route mode: search submitted with no parameters filled → show `"Не верно заданы параметры поиска укажите Город вылета, Город прилета и Дату рейса."` | 4.1.10.1 ¶3 item 4 | all | `OnlineBoardFilter.tsx` | Done a5c64a2 | Missing-params error for route mode implemented and tested in `a5c64a2`. | P3 |
| 4.1.10.1-R5 | Error queries (errors 3+4) are NOT added to «Вы искали» history or popular-sections Top-4 | 4.1.10.1 ¶ after item 4 | all | `SearchHistory.tsx` | Done a5c64a2 | Bad-param searches excluded from history; tested in `a5c64a2` error tests. | P3 |
| 4.1.10.1-R6 | Error page structure: left area = section tabs + filter + «Вы искали»; right area = breadcrumbs + page title + day-tabs + error message area | 4.1.10.1 Table 18 | all | `OnlineBoardSearchPage.tsx` | Done a5c64a2 | Layout matches Table 18; error area wired in `OnlineBoardSearchPage.tsx` in `a5c64a2`. | P3 |
| 4.1.10.1-R7 | Errors 3+4 (missing params): day-tabs show 7 tabs starting at -1 from today; all tabs are disabled (non-clickable) | 4.1.10.1 Table 18 ("Табы-дни должны выводиться -1 … быть недоступными") | all | `DayTabs/` | Done a5c64a2 | Disabled day-tabs on missing-params error implemented in `a5c64a2`. | P3 |
| 4.1.10.1-R8 | If filter has a date entered (errors 1+2): day-tabs follow normal 4.1.13.1 day-tab behavior (not disabled) | 4.1.10.1 Table 18 | all | `DayTabs/` | Done a5c64a2 | Day-tabs remain functional when date is present; tested in `a5c64a2` error test suite. | P3 |

---

# 4.1.11 — Правила выполнения поиска рейсов Расписания
*Schedule search execution rules.*

Source: TZ §4.1.11 (lines 724–736).

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.11-R1 | Search executes only on explicit submit (button click) — not triggered automatically by filter changes | 4.1.11 ¶1 | all | `ScheduleFilter.tsx` | Done a5c64a2 | No auto-search on field change; submit-only trigger confirmed in `a5c64a2`. | P3 |
| 4.1.11-R2 | Submit button blocked for 30 seconds after each search; hard-coded constant (not in config) | 4.1.11 ¶2 | all | `ScheduleFilter.tsx` | Done a5c64a2 | 30s hard-coded lock in `ScheduleFilter.tsx`; tested in `a5c64a2`. | P3 |
| 4.1.11-R3 | During search: loader shown; «Отменить поиск» button appears | 4.1.11 ¶3 | all | `ScheduleSearchPage.tsx` | Done a5c64a2 | Loader + cancel button during fetch verified by `a5c64a2` tests. | P3 |
| 4.1.11-R4 | During search: section tabs, filter, breadcrumbs, and week-tabs are non-interactive | 4.1.11 ¶3 | all | layout / overlay | Done a5c64a2 | `data-searching` + `pointer-events:none` CSS disables Schedule navigation during search; implemented in `a5c64a2`. | P3 |
| 4.1.11-R5 | Schedule search operates on scheduled times only (unlike Board which also uses estimated/actual) | 4.1.11 ¶4 ("по времени: по расписанию") | all | `src/shared/api/*` | Done a5c64a2 | Schedule API uses scheduled-only time source; confirmed in `a5c64a2` hook audit. | P3 |
| 4.1.11-R6 | Schedule search covers direct, multi-segment, AND connecting flights (stыковочный — separate flight numbers per segment) | 4.1.11 ¶5 | all | `src/shared/api/*` | Done a5c64a2 | Connecting flights included; confirmed in `a5c64a2`. | P3 |
| 4.1.11-R7 | «Только прямые рейсы» query returns direct + multi-segment flights (same algorithm as Board, per TZ cross-reference) | 4.1.11 ¶7 | all | `src/shared/api/*` | Done a5c64a2 | Direct-only filter returns multi-segment flights; confirmed in `a5c64a2`. | P3 |
| 4.1.11-R8 | Without «Только прямые рейсы»: system returns direct, multi-segment, and connecting flights | 4.1.11 ¶8 | all | `src/shared/api/*` | Done a5c64a2 | All three flight types returned when filter off; confirmed in `a5c64a2`. | P3 |
| 4.1.11-R9 | Round-trip search (туда+обратно) is executed as **one server request** covering both outbound and return weeks; switching between «Рейсы туда» / «Рейсы обратно» lists does NOT trigger a new request or loader | 4.1.11 ¶9 | all | `ScheduleSearchPage.tsx` | Done a5c64a2 | Single-request round-trip; no second fetch on tab switch — AbortController wired in `a5c64a2`; tested. | P3 |
| 4.1.11-R10 | Maximum one connecting leg (стыковка) is processed | 4.1.11 ¶10 | all | `src/shared/api/*` | Done a5c64a2 | Single-connection limit enforced by API parameter; confirmed in `a5c64a2`. | P3 |

## 4.1.11.1 — Обработка ошибок поиска (Расписание)
*Schedule search error handling.*

Source: TZ §4.1.11.1 / lines 737–753, Table 19.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.11.1-R1 | All days in the requested week yield no results: show one shared message `"Рейсы не найдены. По выбранным параметрам не найдено рейсов. Измените параметры поиска."` | 4.1.11.1 ¶1 item 1 | all | `src/ui/errors/*` | Done a5c64a2 | Empty-week error message wired; per-status error messages in `a5c64a2`. | P3 |
| 4.1.11.1-R2 | Some days within the requested week have no results: for those specific days show `"Рейсы не найдены. По выбранным параметрам не найдено рейсов. Измените параметры поиска."` (inline, not a full-page error) | 4.1.11.1 ¶1 item 2 | all | `DayGroupedFlightList.tsx` | Done a5c64a2 | Per-day inline empty-results message implemented in `a5c64a2`. | P3 |
| 4.1.11.1-R3 | Days with no results have distinct visual styling from days with results | 4.1.11.1 ¶ ("дни … должны иметь отличную цветовую индикацию") | all | `WeekTabs.tsx` + day rows | Done a5c64a2 | Empty-day color differentiation implemented in `ScheduleSearchPage.scss` in `a5c64a2`. | P3 |
| 4.1.11.1-R4 | Empty days can still be collapsed/expanded | 4.1.11.1 ¶ | all | `DayGroupedFlightList.tsx` | Done a5c64a2 | Collapse/expand on empty-day rows preserved in `a5c64a2`. | P3 |
| 4.1.11.1-R5 | Missing params search (no cities + no date): show `"Не верно заданы параметры поиска укажите Город вылета, Город прилета и Интервал поиска."` | 4.1.11.1 ¶1 item 3 | all | `ScheduleFilter.tsx` | Done a5c64a2 | Missing-params error text implemented and tested in `a5c64a2`. | P3 |
| 4.1.11.1-R6 | Missing-params queries are NOT added to «Вы искали» or Top-4 popular sections | 4.1.11.1 ¶ after item 3 | all | `SearchHistory.tsx` | Done a5c64a2 | Bad-params Schedule searches excluded from history; tested in `a5c64a2`. | P3 |
| 4.1.11.1-R7 | Error page structure: left = tabs + filter + «Вы искали»; right = breadcrumbs `Главная / Расписание` + page title «Расписание» + week-tabs + error message | 4.1.11.1 Table 19 | all | `ScheduleSearchPage.tsx` | Done a5c64a2 | Layout matches Table 19; error area wired in `ScheduleSearchPage.tsx` in `a5c64a2`. | P3 |
| 4.1.11.1-R8 | Missing-params error (type 3): week-tabs show 7 tabs starting at -1 from today; all tabs disabled | 4.1.11.1 Table 19 ("Табы-недели … недоступными для нажатия") | all | `WeekTabs.tsx` | Done a5c64a2 | Disabled week-tabs on missing-params implemented in `a5c64a2`. | P3 |

---

# 4.1.12 — Отмена поиска рейсов Онлайн-Табло, Расписания
*Search cancellation rules.*

Source: TZ §4.1.12 (lines 757–766).

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.12-R1 | Cancellation is triggered by clicking «Отменить поиск» button (which appears after search is launched) | 4.1.12 ¶1–2 | all | `OnlineBoardSearchPage.tsx`, `ScheduleSearchPage.tsx` | Done a5c64a2 | «Отменить поиск» button appears during fetch; click cancels — Escape key also triggers cancel; tested in `a5c64a2`. | P3 |
| 4.1.12-R2 | After cancelling Board search: filter retains the values that were used for the search; «Найти» button becomes available again | 4.1.12 ¶3 ("В фильтре введены значения … Кнопка «Найти» доступа для повторного нажатия") | all | `OnlineBoardSearchPage.tsx` | Done a5c64a2 | Board cancel restores submit button + keeps filter values; tested in `a5c64a2`. | P3 |
| 4.1.12-R3 | After cancelling Board search: right-area content reverts to Online-Board start-page content (§4.1.6) | 4.1.12 ¶3 | all | `OnlineBoardSearchPage.tsx` | Done a5c64a2 | Right-area reverts to Board start-page state on cancel; tested in `a5c64a2`. | P3 |
| 4.1.12-R4 | After cancelling Schedule search: filter retains values; «Показать расписание» button becomes available again | 4.1.12 ¶4 | all | `ScheduleSearchPage.tsx` | Done a5c64a2 | Schedule cancel restores submit button + keeps filter values; tested in `a5c64a2`. | P3 |
| 4.1.12-R5 | After cancelling Schedule search: right-area content reverts to Schedule start-page content (§4.1.7) | 4.1.12 ¶4 | all | `ScheduleSearchPage.tsx` | Done a5c64a2 | Right-area reverts to Schedule start-page state on cancel; tested in `a5c64a2`. | P3 |
| 4.1.12-R6 | Cancellation aborts the in-flight HTTP request (no stale response rendered after cancel) | 4.1.12 (implied by cancel semantics) | all | `src/shared/api/*` | Done a5c64a2 | AbortController wired through `ApiClient → api → hooks`; aborted response discarded; 3 new tests in `a5c64a2`. | P3 |
| 4.1.12-R7 | Navigating away from the page (tab switch, back button) while search is in flight also cancels the request | 4.1.12 (cancel on unmount) | all | fetch hooks | Done a5c64a2 | Abort on component unmount / route change implemented in `useOnlineBoard` + `useScheduleSearch`; tested in `a5c64a2`. | P3 |

---

# 4.1.13 — Список результатов поиска рейсов Онлайн-Табло
*Online-Board results list: day tabs (4.1.13.1), sort (4.1.13.2), collapsed row (4.1.13.3), expanded row (4.1.13.4). Target rule count: **80–100**.*

Covers the full per-day results list shown after executing a search in Online-Board. Today's list auto-scrolls to current time and auto-expands the nearest flight. Other days open at the first result with no auto-scroll. No pagination — scroll only. No auto-refresh (live updates arrive via SignalR/WebSocket). Desktop and tablet have sticky day-tabs + filter; mobile has no sticky elements.

## §4.1.13 — opening + structure (R1–R18)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.13-R1 | Today's results list is scrolled to user's current time on load | 4.1.13 ¶1 | all | `OnlineBoardSearchPage.tsx`, `scrollToCurrentTime.ts` | Done 38a5120 | `findNearestFlightIndex` helper + `FlightList.initialCurrentFlightId` wired; integration tests verify today-tab scroll. | P4 |
| 4.1.13-R2 | The flight nearest to the user's current time is auto-expanded on the today tab | 4.1.13 ¶1 | all | `OnlineBoardSearchPage.tsx` | Done 38a5120 | Auto-expand nearest flight on today tab; SignalR-update guard prevents re-scroll after user has scrolled. | P4 |
| 4.1.13-R3 | Non-today results open at the first result with no auto-scroll, sorted per §4.1.13.2 | 4.1.13 ¶2 | all | `OnlineBoardSearchPage.tsx` | Done 38a5120 | Integration test verifies no scroll-to-current on future/past tabs. | P4 |
| 4.1.13-R4 | Results are shown per-day (no multi-day concatenation); infinite scroll within a day | 4.1.13 ¶3 | all | `OnlineBoardSearchPage.tsx` | Implemented | Pre-existing per-day architecture; no concatenation across day-tabs. | P4 |
| 4.1.13-R5 | No pagination UI — scroll-only for large result sets | 4.1.13 ¶3 | all | — | Implemented | No pagination component rendered in OB results; verified by codebase inspection. | P4 |
| 4.1.13-R6 | Today's results list does NOT auto-refresh (no polling); updates come via SignalR | 4.1.13 ¶4 | all | — | Implemented | No polling interval in board hook; SignalR is the sole live-update mechanism (pre-existing). | P4 |
| 4.1.13-R7 | Desktop/tablet layout: left column = subsystem switcher + filter + «Вы искали»; right column = breadcrumbs + page title + day-tabs + flight list (Table 20) | 4.1.13 Table 20 | desktop, tablet | `OnlineBoardSearchPage.tsx` | Implemented | Two-column PageLayout in place; assertion tests in f6def71. | P4 |
| 4.1.13-R8 | Mobile layout: breadcrumbs → page title → subsystem switcher → filter → «Вы искали» → day-list → flight list (Table 20) | 4.1.13 Table 20 | mobile | `OnlineBoardSearchPage.tsx` | Implemented | Single-column stacking on mobile confirmed by PageLayout.test. | P4 |
| 4.1.13-R9 | Subsystem switcher (Table 21 row 1): "Онлайн-Табло" tooltip = «Информация о фактическом выполнении рейсов в ближайшие дни» (desktop only); links to §4.1.8 cross-section nav | 4.1.13 Table 21 row 1 | desktop | `SubsystemSwitcher/` | Implemented | Tooltip text confirmed matches TZ during P3 (C6 resolved); no change needed. | P4 |
| 4.1.13-R10 | Flight list (Table 21 row 7): each flight has collapsed (default) and expanded representations; only one flight is expanded at a time | 4.1.13 Table 21 row 7 + ¶ after table | all | `OnlineBoardFlightList/` | Implemented | Single-expanded-at-a-time accordion behavior pre-existing; tested in 3b5ae9a. | P4 |
| 4.1.13-R11 | Expanding a different flight auto-collapses the previously expanded flight | 4.1.13 ¶ after Table 21 | all | `OnlineBoardFlightList/` | Implemented | Accordion collapse-on-new-expand behavior pre-existing; verified by FlightCard tests. | P4 |
| 4.1.13-R12 | On desktop/tablet: day-tabs and filter are sticky at top of page during scroll | 4.1.13 Table 22 | desktop, tablet | `PageLayout.tsx`, `ScrollUpButton.tsx` | Implemented f6def71 | Sticky wrapper verified — filter column carries `__column-left` (CSS sticky), stickyContent gets `__sticky-content` wrapper; 27 tests pass. | P4 |
| 4.1.13-R13 | On mobile: no elements are sticky (day-tabs, filter, breadcrumbs all scroll with page) | 4.1.13 Table 22 | mobile | `PageLayout.tsx` | Implemented f6def71 | No sticky wrapper applied on mobile; verified by PageLayout.test assertions. | P4 |
| 4.1.13-R14 | Scroll-up button («Вверх») is displayed as an icon, fixed in bottom-right corner of the flight-list area; does not move during scroll | 4.1.13 ¶ «Кнопка Вверх» | all | `ScrollUpButton.tsx` | Implemented f6def71 | Fixed bottom-right positioning confirmed; ScrollUpButton.test covers visibility, aria-label, cleanup. | P4 |
| 4.1.13-R15 | Scroll-up button appears when breadcrumbs + page title + subsystem tabs scroll out of viewport (desktop/tablet); on mobile additionally filter + day-list must scroll out | 4.1.13 ¶ «Кнопка Вверх» + Table 22 | all | `ScrollUpButton.tsx` | Implemented f6def71 | 300px threshold verified — button hidden below threshold, visible above it. | P4 |
| 4.1.13-R16 | Clicking scroll-up button scrolls list back to breadcrumbs visible, then button disappears | 4.1.13 ¶ «Кнопка Вверх» | all | `ScrollUpButton.tsx` | Implemented f6def71 | Click scrolls to top; button disappears on scroll-back; tested in ScrollUpButton.test. | P4 |
| 4.1.13-R17 | Scroll-up button has no hover/active color change | 4.1.13 ¶ «Кнопка Вверх» | all | `ScrollUpButton.tsx` | Implemented f6def71 | No hover/active style override; verified by test (correct aria-label/type). | P4 |
| 4.1.13-R18 | Filter remains accessible (sticky left column) when scrolling through large result list on desktop/tablet | 4.1.13 Table 22 | desktop, tablet | `PageLayout.tsx` | Implemented f6def71 | Filter sticky alongside day-tabs confirmed by PageLayout.test overlay assertions. | P4 |

## §4.1.13.1 — Поведение Табов-дней (Day-tabs behavior)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.13.1-R1 | Day-tabs (Табы-дни) paginate in groups of 7 | 4.1.13.1 ¶2 | desktop, tablet | `DayTabs/` | Done 4396242 | 7-per-page grouping verified; padding tabs fill remainder. | P4 |
| 4.1.13.1-R2 | Active day-tabs range: `[-1 day, +14 days]` from user's current date | 4.1.13.1 ¶3 | all | `DayTabs/` | Done 4396242 | Fixed `daysAfter: 7→14` in `OnlineBoardSearchPage`; active range now `[-1, +14]`. | P4 |
| 4.1.13.1-R3 | Pagination starts from the `-1` day (yesterday) tab as the first tab in the first page | 4.1.13.1 ¶4 | desktop, tablet | `DayTabs/` | Done 4396242 | First page starts at yesterday tab; tested in DayTabs.test. | P4 |
| 4.1.13.1-R4 | If the last active tab is not the last in a 7-slot group, the remaining slots are filled with inactive (non-clickable) tabs to complete the 7 | 4.1.13.1 ¶5 | desktop, tablet | `DayTabs/` | Done 4396242 | Inactive padding tabs added; right-arrow disabled on last page. | P4 |
| 4.1.13.1-R5 | When padding-inactive tabs are shown, the right-arrow paging control is disabled | 4.1.13.1 ¶5 | desktop, tablet | `DayTabs/` | Done 4396242 | Right-arrow disabled when padded; tested in DayTabs.test. | P4 |
| 4.1.13.1-R6 | The user-requested day is highlighted (active style) and auto-scrolled into view in the day-tabs | 4.1.13.1 ¶6 | desktop, tablet | `DayTabs/` | Done 4396242 | `aria-current="date"` on active tab; `scrollIntoView` via `activeBtnRef` on `selectedDate` change. | P4 |
| 4.1.13.1-R7 | Clicking a day-tab triggers a new search for that day using the current filter values | 4.1.13.1 ¶7 | desktop, tablet | `DayTabs/` | Implemented | Pre-existing tab-click → search dispatch; confirmed by DayTabs.test. | P4 |
| 4.1.13.1-R8 | When switching day-tabs, the results list is re-fetched for the clicked day | 4.1.13.1 ¶8 | desktop, tablet | `DayTabs/` | Implemented | Re-fetch on tab change is pre-existing behavior; locked in 4396242 tests. | P4 |
| 4.1.13.1-R9 | On mobile, day-tabs are replaced by a scrollable day-list (same active-range, no 7-per-page paging) | 4.1.13.1 ¶9 | mobile | `DayList/` | Implemented | Mobile day-list component pre-existing; active range now matches [-1,+14]. | P4 |
| 4.1.13.1-R10 | Day-tab for today is visually distinct from other days (current-day marker) | 4.1.13.1 (TZ Table 21 + mockup App.4) | all | `DayTabs/` | Implemented | Today styling pre-existing; tested in DayTabButton.test. | P4 |
| 4.1.13.1-R11 | Yesterday tab (-1 day) is always present and active if within the [-1, +14] range | 4.1.13.1 ¶3 | all | `DayTabs/` | Done 4396242 | Yesterday tab generated from range start; verified clickable and active in DayTabs.test. | P4 |

## §4.1.13.2 — Сортировка (Default sort order)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.13.2-R1 | No user-triggered re-sort — only default sort is applied | 4.1.13.2 ¶1 | all | `sortFlights.ts` | Done 8b0d559 | No sort controls rendered; default sort only; 18 unit tests. | P4 |
| 4.1.13.2-R2 | Default sort for "Номер рейса" mode: ascending by departure date | 4.1.13.2 ¶2 | all | `sortFlights.ts` | Done 8b0d559 | Flight-number mode sorts by scheduled departure time ascending. | P4 |
| 4.1.13.2-R3 | Default sort for "Маршрут" mode: by segment departure time, ordering yesterday < today < tomorrow | 4.1.13.2 ¶3 | all | `sortFlights.ts` | Done 8b0d559 | Day ordering emerges naturally from absolute ISO timestamps; no bespoke bucketing. | P4 |
| 4.1.13.2-R4 | For "Маршрут" mode multi-segment: sort by the departure time of the segment that matched the search | 4.1.13.2 ¶3 | all | `sortFlights.ts` | Done 8b0d559 | Matched-segment departure used as sort key for multi-leg. | P4 |
| 4.1.13.2-R5 | Default sort for "Вылет" mode: same as "Маршрут" (by segment departure time, yesterday < today < tomorrow, matched-segment for multi-segment) | 4.1.13.2 ¶4 | all | `sortFlights.ts` | Done 8b0d559 | Departure-only mode sort verified by 18 unit tests. | P4 |
| 4.1.13.2-R6 | Default sort for "Прилет" mode: by segment **arrival** time, ordering yesterday < today < tomorrow (matched-segment for multi-segment) | 4.1.13.2 ¶5 | all | `sortFlights.ts` | Done 8b0d559 | Arrival mode sorts by last-leg arrival time; tested in sortFlights.test. | P4 |
| 4.1.13.2-R7 | Sort takes into account all three time variants: scheduled, estimated, actual — uses whichever is currently relevant | 4.1.13.2 ¶1 | all | `sortFlights.ts` | Done 8b0d559 | Sort uses actual/estimated when available; falls back to scheduled; missing timestamp pushed to end. | P4 |

## §4.1.13.3 — Свернутое представление (Collapsed row)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.13.3-R1 | Flight number displayed as `SU XXXX` (4-digit format) | 4.1.13.3 Table 23 row 1 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Flight-number format locked in FlightCard.test Table 23 row 1 assertion. | P4 |
| 4.1.13.3-R2 | Carrier icon: full (non-mini, non-round) airline logo for direct flight (Table 23) | 4.1.13.3 Table 23 row 2 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Full logo (non-round) for direct flight verified in FlightCard.test. | P4 |
| 4.1.13.3-R3 | Departure time: show the most current of scheduled / estimated / actual | 4.1.13.3 Table 23 row 3 | all | `FlightCard.tsx`, `TimeGroup.tsx` | Implemented 3b5ae9a | Time priority (actual > estimated > scheduled) verified by FlightCard.test Table 23 row 3. | P4 |
| 4.1.13.3-R4 | When departure time was changed: show the current (actual/estimated) time normally, and the superseded time (scheduled or estimated) struck-through in small font below | 4.1.13.3 Table 23 row 3 | all | `TimeGroup.tsx` | Implemented 3b5ae9a | Strikethrough of superseded time verified in FlightCard.test. | P4 |
| 4.1.13.3-R5 | Departure day-change badge `±X` shown if flight crosses midnight; tooltip on hover shows «день» for ±1, else `DD.MM.YYYY` of the relevant date; no tooltip on mobile | 4.1.13.3 Table 23 row 4 | all | `DayChangeBadge/` | Implemented 3b5ae9a | Badge + tooltip content locked in FlightCard.test; consistent with §4.1.17. | P4 |
| 4.1.13.3-R6 | Departure city name shown | 4.1.13.3 Table 23 row 5 | all | `FlightCard.tsx` | Implemented 3b5ae9a | City field rendered; verified by FlightCard.test. | P4 |
| 4.1.13.3-R7 | Departure airport name shown if available; Шереметьево / Внуково rendered as clickable link opening in new browser tab | 4.1.13.3 Table 23 row 6 | all | `FlightCard.tsx` | TBD (P5) | Airport-as-link for SVO/VKO deferred; airport name display verified but link not implemented. | P4 |
| 4.1.13.3-R8 | Departure terminal code shown if available | 4.1.13.3 Table 23 row 7 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Terminal conditional display verified in FlightCard.test. | P4 |
| 4.1.13.3-R9 | Flight status chip shown with icon + text; for multi-segment flight shows current executing segment status, switching at end of prior segment deboarding time (Appendix 2 rules) | 4.1.13.3 Table 23 row 8 | all | `FlightCard.tsx` | Partial 3b5ae9a | Status chip icon+text verified. Multi-segment status-switching logic (Appendix 2 deboarding-time threshold) not yet implemented; deferred to P5. | P4 |
| 4.1.13.3-R10 | Arrival time: show the most current of scheduled / estimated / actual | 4.1.13.3 Table 23 row 9 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Arrival time priority verified in FlightCard.test. | P4 |
| 4.1.13.3-R11 | When arrival time was changed: show actual/estimated time normally, superseded time struck-through in small font | 4.1.13.3 Table 23 row 9 | all | `FlightCard.tsx`, `TimeGroup.tsx` | Implemented 3b5ae9a | Strikethrough on changed arrival time verified. | P4 |
| 4.1.13.3-R12 | Arrival day-change badge `±X` with same tooltip rules as departure | 4.1.13.3 Table 23 row 10 | all | `DayChangeBadge/` | Implemented 3b5ae9a | Arrival day-change badge locked in FlightCard.test. | P4 |
| 4.1.13.3-R13 | Arrival city name shown | 4.1.13.3 Table 23 row 11 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Arrival city field verified. | P4 |
| 4.1.13.3-R14 | Arrival airport name shown if available; Шереметьево / Внуково as link opening new tab | 4.1.13.3 Table 23 row 12 | all | `FlightCard.tsx` | TBD (P5) | Airport-as-link deferred; airport name displayed but SVO/VKO link not implemented. | P4 |
| 4.1.13.3-R15 | Arrival terminal code shown if available | 4.1.13.3 Table 23 row 13 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Arrival terminal conditional display verified. | P4 |
| 4.1.13.3-R16 | Expand/collapse toggle element (arrow icon) shown on every row | 4.1.13.3 Table 23 row 14 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Toggle arrow present; verified in FlightCard.test. | P4 |
| 4.1.13.3-R17 | Multi-segment / connecting flight: full airline logo (non-mini) shown in collapsed row (Table 24) | 4.1.13.3 Table 24 row 2 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Full logo for multi-seg collapsed verified in FlightCard.test Table 24 assertions. | P4 |
| 4.1.13.3-R18 | Multi-segment collapsed: from = city/airport/terminal of **first segment departure**; to = city/airport/terminal of **last executing segment arrival** | 4.1.13.3 Table 24 rows 5–13 | all | `FlightCard.tsx` | Implemented 3b5ae9a | First-dep/last-arr for multi-seg collapsed verified in FlightCard.test. | P4 |
| 4.1.13.3-R19 | Route-change icon shown if flight route was altered (icon «Измен маршрут») | 4.1.13.3 Table 24 row 15 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Route-change icon conditional render verified. | P4 |
| 4.1.13.3-R20 | Return-to-origin icon shown if flight returned to departure airport; route-change icon also shown in this case | 4.1.13.3 Table 24 row 16 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Return-to-origin icon + co-shown route-change verified in FlightCard.test. | P4 |
| 4.1.13.3-R21 | Intermediate-landing badge shown between segments; if single landing: city + airport + terminal + icon; if 2+ landings: count + icon + «Смена терминала»/«Смена аэропорта» flags | 4.1.13.3 Table 24 row 17 + §4.1.13.3.2 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Single vs multi-stop badge content verified in FlightCard.test. | P4 |
| 4.1.13.3-R22 | Intermediate-landing badge variant type 1 (no stop badge): same city + same airport + same terminal (or one terminal missing) | 4.1.13.3.2 rules | all | `FlightCard.tsx` | Implemented 3b5ae9a | Badge type 1 conditions verified. | P4 |
| 4.1.13.3-R23 | Intermediate-landing badge variant type 2 (city-change): city of prev segment arrival ≠ city of next segment departure | 4.1.13.3.2 rules | all | `FlightCard.tsx` | Implemented 3b5ae9a | Badge type 2 (city change) verified. | P4 |
| 4.1.13.3-R24 | Intermediate-landing badge variant type 3 (terminal change): same city + same airport + different terminals | 4.1.13.3.2 rules | all | `FlightCard.tsx` | Implemented 3b5ae9a | Badge type 3 (terminal change) verified. | P4 |
| 4.1.13.3-R25 | Intermediate-landing badge variant type 4 (airport change): same city + different airport | 4.1.13.3.2 rules | all | `FlightCard.tsx` | Implemented 3b5ae9a | Badge type 4 (airport change) verified. | P4 |
| 4.1.13.3-R26 | When comparing airports for badge selection, use current available data for prev-segment arrival and next-segment departure (scheduled and actual both considered) | 4.1.13.3.2 rules | all | `FlightCard.tsx` | Implemented 3b5ae9a | Airport comparison uses both scheduled and actual data; verified in FlightCard.test. | P4 |
| 4.1.13.3-R27 | If a planned multi-segment flight changes to a direct (cancelled segment), cancelled segment is hidden, row renders as direct flight with route-change icon | 4.1.13.3 ¶ after Table 24 | all | `FlightCard.tsx` | Implemented 3b5ae9a | Multi→direct change rendering verified; route-change icon shown. | P4 |

## §4.1.13.4 — Развернутое представление (Expanded row)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.13.4-R1 | Expanded row structure for direct flight (Table 26): flight-number header + departure block + status block + arrival block + operation-blocks (registration/boarding/deboarding) + action-buttons area | 4.1.13.4 Table 26 | all | `FlightCard.tsx` | Implemented 9f66237 | Expanded layout structure verified in FlightCard.test (16 assertion tests). | P4 |
| 4.1.13.4-R2 | Per Table 26 row 18: operation blocks (Регистрация / Посадка / Высадка) shown in order Регистрация → Посадка → Высадка | 4.1.13.4 §4.1.13.4.3 | all | `FlightCard.tsx` | Implemented 9f66237 | Block ordering Рег → Посадка → Высадка verified. | P4 |
| 4.1.13.4-R3 | Flight time display (Table 27): block-departure time and block-arrival time are each shown as scheduled / estimated / actual depending on flight status; superseded times struck-through in small font | 4.1.13.4 Table 27 | all | `FlightCard.tsx` | Implemented 9f66237 | Time-block algorithm per Table 27 verified; strikethrough tested. | P4 |
| 4.1.13.4-R4 | Each time block includes UTC offset label `HH:MM UTC±HH:MM` and day-change badge `±X`; day-change badge has hover tooltip showing «День» for ±1, else `DD.MM.YYYY`; no tooltip on mobile | 4.1.13.4 ¶ after Table 27 | all | `FlightCard.tsx` | Implemented 9f66237 | UTC offset + day-change tooltip in expanded verified. | P4 |
| 4.1.13.4-R5 | «Регистрация» block shown when registration has started and not yet closed; for SVO uses actual data with statuses Ожидается/Идет/Закончена; for other airports uses calculated statuses with «Уточняется» | 4.1.13.4 §4.1.13.4.3 Table 28 | all | `FlightCard.tsx` | Implemented 9f66237 | Registration block show/hide and status logic verified in FlightCard.test. | P4 |
| 4.1.13.4-R6 | «Регистрация» block timing: show when `Y - X ≤ 24h` and `Y - X > min-check-in-time`; block disappears when `Y - X ≤ min-check-in-time` | 4.1.13.4 Table 28 | all | `FlightCard.tsx` | Partial 9f66237 | Timing window implemented; check-in counter number deferred — API type has `checkingStatus` string but no counter-number field; needs backend extension (backlog). | P4 |
| 4.1.13.4-R7 | For delayed flights, registration block shows «Уточняется / Идет» when delay `> 15 min`; end-time uses expected departure minus min-check-in + delay offset | 4.1.13.4 Table 28 delayed rows | all | `FlightCard.tsx` | Implemented 9f66237 | Delayed-flight reg block behavior verified. | P4 |
| 4.1.13.4-R8 | «Посадка» block: starts ~40 min before departure, boarding "ongoing" for 20 min window, "finished" until departure; SVO uses actual data, others use «Уточняется» when expected data not received | 4.1.13.4 Table 29 | all | `FlightCard.tsx` | Done 9f66237 | Gate number (`departure.gate`) + boarding-type (`departure.dispatch`) added; boarding block timing tested. | P4 |
| 4.1.13.4-R9 | «Высадка» block: shown when flight has landed and deboarding is active; closed after 1h past actual arrival for SVO; uses «Уточняется» status for 30 min when only «Прибыл» status received without actual data | 4.1.13.4 Table 30 | all | `FlightCard.tsx` | Done 9f66237 | Baggage belt (`arrival.bagBelt`) added as conditional field; deboarding block logic tested. | P4 |
| 4.1.13.4-R10 | Registration block shows: start time (HH:MM) + end time (HH:MM); boarding block shows: start time + end time + gate number (if received) + boarding-type (ramp/bus, if received) | 4.1.13.4 Table 28-29 attr cols | all | `FlightCard.tsx` | Done 9f66237 | Gate, ramp/bus (dispatch), start, end all added and tested. | P4 |
| 4.1.13.4-R11 | Action area: «Поделиться» (share) icon shown; share opens VK/other social networks + copy-link; China locale also shows Weibo | 4.1.13.4 §4.1.13.4.5 | all | `SharePanel/` | Implemented 9f66237 | Share button + panel verified in FlightCard.test. | P4 |
| 4.1.13.4-R12 | Action area: «Купить» button available when departure is > 2h away (by UTC) and flight is not cancelled | 4.1.13.4 §4.1.13.4.6 | all | `FlightCard.tsx` | Implemented 9f66237 | Buy button visibility rule verified (>2h UTC, not cancelled). | P4 |
| 4.1.13.4-R13 | «Купить» for multi-segment: single button for whole flight; availability calculated from first segment departure | 4.1.13.4 §4.1.13.4.6 | all | `FlightCard.tsx` | Implemented 9f66237 | Single Buy button for multi-seg uses first-seg departure time. | P4 |
| 4.1.13.4-R14 | «Купить» not shown for cancelled flights | 4.1.13.4 §4.1.13.4.6 | all | `FlightCard.tsx` | Implemented 9f66237 | Buy hidden for cancelled verified in FlightCard.test. | P4 |
| 4.1.13.4-R15 | «Купить» click opens SB search page in new browser tab with route params (`route`, `cabin=economy`, `adults=1`, `autosearch=Y`) | 4.1.13.4 §4.1.13.4.6 Table 31 | all | `FlightCard.tsx` | Implemented 9f66237 | SB URL params (Table 31 format) verified. | P4 |
| 4.1.13.4-R16 | «Онлайн-Регистрация» button shown when departure > min-check-in time away and flight not cancelled; for Россия airline links to Россия check-in page | 4.1.13.4 §4.1.13.4.7 | all | `OnlineCheckInButton/` | TBD (P5) | Online check-in button deferred; aircraft tail number display (aircraft.registration) also deferred to details-page parity task. | P4 |
| 4.1.13.4-R17 | For multi-segment expanded: per-segment breakdown rendered; intermediate-landing badge between segments (same badge logic as collapsed §4.1.13.3.2) | 4.1.13.4 Table 26 multi-seg variant | all | `FlightCard.tsx` | Implemented 9f66237 | Per-segment layout in expanded multi-seg verified; codeshare chips per-segment deferred (currently merged into header via _childFlightIds). | P4 |
| 4.1.13.4-R18 | «Детали рейса» button in expanded row opens flight details page (§4.1.15) | 4.1.13.4 §4.1.13.4.8 | all | `FlightCard.tsx` | Implemented 9f66237 | Details link navigates to §4.1.15 page; tested in FlightCard.test. | P4 |

---

# 4.1.14 — Список результатов поиска рейсов Расписания
*Schedule results list: week tabs (4.1.14.1), sort (4.1.14.2), collapsed (4.1.14.3), expanded (4.1.14.4). Largest subsection by page count (~100 pages in TZ). Target rule count: **150–200**.*

Covers the full weekly results list shown after executing a Schedule search. Results are shown per week, grouped by day within the week. Active date range: `[-1 day, +330 days]` from today. No pagination — scroll within expanded day groups. Desktop/tablet have sticky week-tabs + search-direction header + column header; mobile has no sticky elements.

## §4.1.14 — opening + structure (R1–R24)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.14-R1 | Results displayed per week; week is split into days; only days within `[-1, +330]` window are shown | 4.1.14 ¶1 | all | `ScheduleSearchPage.tsx`, `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Per-week grouping with day-level expansion pre-existing; locked in DayGroupedFlightList.test. | P4 |
| 4.1.14-R2 | No pagination — scroll within expanded day groups; infinite vertical scroll | 4.1.14 ¶2 | all | — | Implemented | No pagination component in Schedule results; verified by codebase inspection. | P4 |
| 4.1.14-R3 | Desktop/tablet layout (Table 33): left = subsystem switcher + filter + «Вы искали»; right = breadcrumbs + page title + week-tabs + search-direction + column-header + flight list | 4.1.14 Table 33 | desktop, tablet | `ScheduleSearchPage.tsx` | Implemented | Two-column layout pre-existing; consistent with §4.1.13-R7 PageLayout pattern. | P4 |
| 4.1.14-R4 | Mobile layout (Table 33): stacked — breadcrumbs → page title → subsystem switcher → filter → «Вы искали» → week-list → search-direction → flight list | 4.1.14 Table 33 | mobile | `ScheduleSearchPage.tsx` | Implemented | Single-column stacking pre-existing; matches PageLayout mobile pattern. | P4 |
| 4.1.14-R5 | «Направление поиска» (search-direction toggle) shown only when «Показать обратные рейсы» was checked; toggles between «Рейсы туда» and «Рейсы обратно» result lists | 4.1.14 Table 34 row 6 | all | `SearchDirectionToggle/` | Implemented | Direction toggle visibility + switching pre-existing; locked in 6f67c06 tests. | P4 |
| 4.1.14-R6 | Column header (Шапочка списка рейсов, Table 34 row 7): shows columns «Рейс», «Авиакомпания, борт», «Вылет», «Время в пути», «Прилет»; «Вылет» and «Прилет» columns have asterisk footnote | 4.1.14 Table 34 row 7 | desktop, tablet | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Column header with footnote asterisks verified in DayGroupedFlightList.test. | P4 |
| 4.1.14-R7 | Footnote at bottom of list: «* Время в Системе - МЕСТНОЕ» | 4.1.14 Table 34 row 7 | all | `ScheduleSearchPage.tsx` | Implemented 6f67c06 | Footnote text rendered below list; verified in DayGroupedFlightList.test. | P4 |
| 4.1.14-R8 | Each day in the results list can be collapsed/expanded independently | 4.1.14 ¶ after Table 34 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Day-group accordion independently collapsible; tested. | P4 |
| 4.1.14-R9 | Each flight within a day can be collapsed/expanded; only one flight per day is expanded at a time; expanding new flight auto-collapses the previously expanded one | 4.1.14 ¶ after Table 34 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Single-expanded-at-a-time per day verified in DayGroupedFlightList.test. | P4 |
| 4.1.14-R10 | On load, only active days within `[-1, +330]` are shown (inactive days hidden) | 4.1.14 ¶ results behavior | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Inactive day filtering verified; active window uses scheduleWindowBounds(). | P4 |
| 4.1.14-R11 | If search was on the current week, the current day of week is expanded on load | 4.1.14 ¶ results behavior | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Today's day-group auto-expanded on current-week search; tested. | P4 |
| 4.1.14-R12 | If only one active day remains in the search week (e.g. Sunday), that day is auto-expanded | 4.1.14 ¶ results behavior | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Last-day-of-week auto-expand verified. | P4 |
| 4.1.14-R13 | If search was on a future week, the first day of that week is auto-expanded | 4.1.14 ¶ results behavior | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | First-day auto-expand for future week verified in DayGroupedFlightList.test. | P4 |
| 4.1.14-R14 | On desktop/tablet: week-tabs, search-direction header, and column header are sticky at top during scroll | 4.1.14 Table 35 | desktop, tablet | `PageLayout.tsx` | Implemented f6def71 | Sticky for week-tabs + direction + column header verified; PageLayout.test covers sticky wrappers. | P4 |
| 4.1.14-R15 | On mobile: no elements are sticky (all scroll with page) | 4.1.14 Table 35 | mobile | `PageLayout.tsx` | Implemented f6def71 | No sticky on mobile schedule results; confirmed by PageLayout.test. | P4 |
| 4.1.14-R16 | Scroll-up button (icon) fixed in bottom-right; appears when breadcrumbs + page title + subsystem tabs scroll out of viewport (desktop/tablet); on mobile also filter + week-list must scroll out | 4.1.14 ¶ «Кнопка Вверх» + Table 35 | all | `ScrollUpButton.tsx` | Implemented f6def71 | Scroll-up button trigger verified (same component as OB); 300px threshold tested. | P4 |
| 4.1.14-R17 | Clicking scroll-up button scrolls page back to breadcrumbs visible, button then disappears | 4.1.14 ¶ «Кнопка Вверх» | all | `ScrollUpButton.tsx` | Implemented f6def71 | Scroll-up click behavior verified in ScrollUpButton.test. | P4 |
| 4.1.14-R18 | Scroll-up button has no hover/active color change | 4.1.14 ¶ «Кнопка Вверх» | all | `ScrollUpButton.tsx` | Implemented f6def71 | No hover style on scroll-up confirmed. | P4 |
| 4.1.14-R19 | When «Показать обратные рейсы» is active, both outbound and return results share the same week-tab set; week-tab validation applies calendar rules from §4.1.9.4 | 4.1.14.1 ¶ (round-trip) | all | `WeekTabs/` | Implemented 6f67c06 | Round-trip week-tab validation cross-check pre-existing; verified in WeekTabs.test. | P4 |
| 4.1.14-R20 | Round-trip week-tabs apply the same validation to each direction independently (Figures 19-24) | 4.1.14.1 ¶ Figures 19-24 | all | `WeekTabs/` | Implemented 6f67c06 | Per-direction tab blocking per mockup Figs 19-24 verified. | P4 |
| 4.1.14-R21 | Filter is sticky (left column) during scroll on desktop/tablet | 4.1.14 Table 35 | desktop, tablet | `PageLayout.tsx` | Implemented f6def71 | Filter sticky alongside week-tabs confirmed; PageLayout.test overlay assertions. | P4 |
| 4.1.14-R22 | Subsystem switcher tooltips: «Онлайн-Табло» button = «Информация о фактическом выполнении рейсов в ближайшие дни»; desktop only | 4.1.14 Table 34 row 1 | desktop | `SubsystemSwitcher/` | Implemented | Tooltip text matches TZ (confirmed by C6 resolution `53b5359`); shared component. | P4 |
| 4.1.14-R23 | «Расписание» button tooltip: «Информация о планируемом выполнении рейсов на ближайший год»; desktop only | 4.1.14 Table 34 row 1 | desktop | `SubsystemSwitcher/` | Implemented | Schedule button tooltip text confirmed matches TZ pre-P4. | P4 |
| 4.1.14-R24 | «Карта полетов» button tooltip: «Информация о карте полетов»; desktop only | 4.1.14 Table 34 row 1 | desktop | `SubsystemSwitcher/` | Implemented | Map button tooltip confirmed matches TZ pre-P4. | P4 |

## §4.1.14.1 — Поведение Табов-недель (Week-tabs behavior)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.14.1-R1 | Week-tabs paginate in groups of 7 weeks | 4.1.14.1 ¶1 | desktop, tablet | `WeekTabs/` | Done 6f67c06 | 7-per-page week grouping verified in WeekTabs.test. | P4 |
| 4.1.14.1-R2 | One week tab = 7 calendar days (Mon–Sun); inactive days (outside `[-1, +330]`) within a week are hidden but the week tab itself remains active if it contains at least one active day | 4.1.14.1 ¶2 | all | `WeekTabs/` | Done 6f67c06 | Partial-week handling verified; tab active but out-of-range days hidden. | P4 |
| 4.1.14.1-R3 | Active week-tabs: from the week containing `-1 day` up to the week containing `+330 days` from today | 4.1.14.1 ¶3 | all | `WeekTabs/` | Done 6f67c06 | Fixed active range from hardcoded WEEKS_AFTER=30 (≈210 days) to `scheduleWindowBounds()` [-1,+330 days]; WeekTabs.test verifies. | P4 |
| 4.1.14.1-R4 | Pagination starts from the week containing `-1 day` (same as current week start) | 4.1.14.1 ¶4 | desktop, tablet | `WeekTabs/` | Done 6f67c06 | First page starts at week of -1 day; verified in WeekTabs.test. | P4 |
| 4.1.14.1-R5 | If the last active week-tab is not the last in a 7-slot page, remaining slots are filled with inactive (non-clickable) week-tabs to complete the 7; right-arrow paging then disabled | 4.1.14.1 ¶5 | desktop, tablet | `WeekTabs/` | Done 6f67c06 | Inactive padding + disabled right-arrow added; WeekTabs.test verifies. | P4 |
| 4.1.14.1-R6 | The user-requested week tab is highlighted (active style) and auto-scrolled into view | 4.1.14.1 ¶6 | all | `WeekTabs/` | Done 6f67c06 | Auto-scroll via `useEffect` on `selectedMonday` prop change; requested-week highlight tested. | P4 |
| 4.1.14.1-R7 | Clicking a week-tab triggers search for that week using current filter values | 4.1.14.1 ¶7 | all | `WeekTabs/` | Implemented | Search re-executes on week-tab click pre-existing; locked in WeekTabs.test. | P4 |
| 4.1.14.1-R8 | When switching week-tabs, results list is re-fetched for the selected week | 4.1.14.1 ¶8 | all | `WeekTabs/` | Implemented | API call on week change pre-existing; verified by WeekTabs.test. | P4 |
| 4.1.14.1-R9 | On mobile, week-tabs are replaced by a scrollable week-list with equivalent behavior | 4.1.14.1 ¶9 | mobile | `WeekList/` | Implemented | Week-list on mobile pre-existing; active range now matches [-1,+330]. | P4 |
| 4.1.14.1-R10 | If the last active week is only a partial week (e.g. only Sunday at day +330), that week-tab is still active but only that one valid day is shown within it | 4.1.14.1 ¶2 | all | `WeekTabs/` | Done 6f67c06 | Partial last-week handling verified in WeekTabs.test. | P4 |
| 4.1.14.1-R11 | When new week-tab is clicked, sort resets to default (departure time ascending, 00:00–23:59) | 4.1.14.2 ¶ last | all | `ScheduleSearchPage.tsx` | Implemented 6f67c06 | Sort reset on week-tab switch verified in DayGroupedFlightList.test. | P4 |

## §4.1.14.2 — Сортировка (Sort)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.14.2-R1 | Default sort: by departure time ascending (00:00 → 23:59) | 4.1.14.2 ¶1 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Default departure-ascending sort pre-existing; locked in DayGroupedFlightList.test. | P4 |
| 4.1.14.2-R2 | For multi-segment flights: sort by the searched segment's departure time | 4.1.14.2 ¶2 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Multi-seg uses matched-segment departure for sort; verified. | P4 |
| 4.1.14.2-R3 | For connecting flights (стыковочные): sort by the first segment's departure time | 4.1.14.2 ¶3 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Connecting sort by first-segment departure verified. | P4 |
| 4.1.14.2-R4 | User can re-sort by: «Вылет» (departure time), «Время в пути» (duration), «Прилет» (arrival time) | 4.1.14.2 ¶4 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Three sort columns clickable; verified in DayGroupedFlightList.test. | P4 |
| 4.1.14.2-R5 | «Вылет» user-sort: ascending/descending departure time, with ordering yesterday < today < tomorrow | 4.1.14.2 ¶5 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Departure user-sort direction toggle + day ordering verified. | P4 |
| 4.1.14.2-R6 | «Прилет» user-sort: ascending/descending arrival time, with ordering yesterday < today < tomorrow | 4.1.14.2 ¶6 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Arrival user-sort direction toggle + day ordering verified. | P4 |
| 4.1.14.2-R7 | «Время в пути» user-sort: ascending/descending total flight duration | 4.1.14.2 ¶7 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Duration sort direction toggle verified. | P4 |
| 4.1.14.2-R8 | Sort is applied per expanded day-group; when next day-group is expanded it inherits the sort that was applied previously | 4.1.14.2 ¶8 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Sort persistence across day-groups within same week verified. | P4 |
| 4.1.14.2-R9 | Sort (user re-sort) applies to the start and end point of the searched route (direct/connecting) or segment (multi-segment) | 4.1.14.2 ¶4 | all | `DayGroupedFlightList.tsx` | Implemented 6f67c06 | Sort applies to correct route endpoints; verified. | P4 |

## §4.1.14.3 — Свернутое представление (Collapsed row)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.14.3-R1 | Collapsed rows for outbound and return directions have identical element structure | 4.1.14.3 ¶1 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Same component used for both directions; verified in DayGroupedFlightList.test. | P4 |
| 4.1.14.3-R2 | Direct flight collapsed (Table 36): flight number `SU XXXX` (4-digit) | 4.1.14.3 Table 36 row 1 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Schedule flight number format locked in DayGroupedFlightList.test Table 36 row 1. | P4 |
| 4.1.14.3-R3 | Direct flight: mini (round) airline icon; if no icon available, show empty space (not placeholder) | 4.1.14.3 Table 36 row 2 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Mini-round logo verified; empty space when missing tested. | P4 |
| 4.1.14.3-R4 | Direct flight: departure time = scheduled time (schedule only; no estimated/actual override in collapsed) | 4.1.14.3 Table 36 row 3 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Departure shows scheduled only in schedule collapsed; verified. | P4 |
| 4.1.14.3-R5 | Departure day-change badge `±X` with hover tooltip (hover = «день» for ±1, else `DD.MM.YYYY`; no tooltip on mobile) | 4.1.14.3 Table 36 row 4 | all | `DayChangeBadge/` | Implemented 6f67c06 | Day-change badge on schedule departure verified in DayGroupedFlightList.test. | P4 |
| 4.1.14.3-R6 | Departure city name shown | 4.1.14.3 Table 36 row 5 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Departure city rendered; verified. | P4 |
| 4.1.14.3-R7 | Departure airport shown if data received; Шереметьево/Внуково as clickable link opening new tab | 4.1.14.3 Table 36 row 6 | all | `ScheduleFlightRow/` | TBD (P5) | Airport-as-link for SVO/VKO deferred; airport name display verified but link not implemented. | P4 |
| 4.1.14.3-R8 | Departure terminal shown if data received; hidden if not | 4.1.14.3 Table 36 row 7 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Terminal conditional display verified. | P4 |
| 4.1.14.3-R9 | Duration shown as icon + formatted time: `{MM}мин.` / `{HH}ч. {MM}мин.` / `{D}д. {MM}мин.` / `{D}д. {HH}ч. {MM}мин.` | 4.1.14.3 Table 36 row 8 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Duration format verified in DayGroupedFlightList.test. | P4 |
| 4.1.14.3-R10 | Duration = sum of all segment durations + all transfer/layover durations | 4.1.14.3 Table 36 row 8 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Duration calculation includes transfer time; verified. | P4 |
| 4.1.14.3-R11 | Arrival time = scheduled | 4.1.14.3 Table 36 row 9 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Arrival shows scheduled only; verified. | P4 |
| 4.1.14.3-R12 | Arrival day-change badge `±X` with same hover tooltip rules | 4.1.14.3 Table 36 row 10 | all | `DayChangeBadge/` | Implemented 6f67c06 | Arrival day-change badge locked in DayGroupedFlightList.test. | P4 |
| 4.1.14.3-R13 | Arrival city name shown | 4.1.14.3 Table 36 row 11 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Arrival city verified. | P4 |
| 4.1.14.3-R14 | Arrival airport shown if data received; Шереметьево/Внуково as link | 4.1.14.3 Table 36 row 12 | all | `ScheduleFlightRow/` | TBD (P5) | Airport-as-link deferred (same as R7); airport name verified. | P4 |
| 4.1.14.3-R15 | Arrival terminal shown if data received; hidden if not | 4.1.14.3 Table 36 row 13 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Arrival terminal conditional verified. | P4 |
| 4.1.14.3-R16 | Expand/collapse toggle arrow shown on every row | 4.1.14.3 Table 36 row 14 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Toggle arrow rendered; verified in DayGroupedFlightList.test. | P4 |
| 4.1.14.3-R17 | Multi-segment collapsed (Table 37): single flight number for whole flight, single mini-icon (same carrier throughout); departure = first segment, arrival = last segment | 4.1.14.3 Table 37 rows 1–14 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Multi-seg single number + single round icon (`3ae59da`) + first/last endpoints verified. | P4 |
| 4.1.14.3-R18 | Multi-segment collapsed: intermediate-landing badge between segments (same badge type logic as §4.1.13.3.2 / §4.1.14.3.2) | 4.1.14.3 Table 37 row 15 | all | `IntermediateLandingBadge/` | Implemented 6f67c06 | Intermediate-landing badge in schedule collapsed verified. | P4 |
| 4.1.14.3-R19 | Connecting flight (direct+direct, Table 38): two flight numbers displayed separated (e.g. `SU XXXX - SU XXXX`); two mini-icons, one per segment; if same airline, two identical icons | 4.1.14.3 Table 38 rows 1–2 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Connecting flight numbers + dual mini-icons verified in DayGroupedFlightList.test Table 38. | P4 |
| 4.1.14.3-R20 | Connecting flight: if a segment has no mini-icon, show empty space (no placeholder) | 4.1.14.3 Table 38 row 2 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Empty space when mini-icon missing verified. | P4 |
| 4.1.14.3-R21 | Connecting flight collapsed (Table 38): departure from first segment, arrival at last segment; transfer badge between segments | 4.1.14.3 Table 38 rows 3–15 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Connecting flight endpoints + transfer badge verified. | P4 |
| 4.1.14.3-R22 | Connecting+multi-segment hybrid (Table 39): flight numbers of all participating legs; mini-icons per leg; departure = first segment, arrival = last | 4.1.14.3 Table 39 | all | `ScheduleFlightRow/` | Implemented 6f67c06 | Mixed multi-seg+connecting layout verified in DayGroupedFlightList.test Table 39. | P4 |
| 4.1.14.3-R23 | Transfer/intermediate-landing badge (§4.1.14.3.2): for connecting flights badge type = «Пересадка»; for multi-segment = «Промежуточная посадка»; badge shows city + airport + terminal + type icon | 4.1.14.3.2 | all | `TransferBadge/`, `IntermediateLandingBadge/` | Implemented 6f67c06 | Correct badge type per flight type verified. | P4 |
| 4.1.14.3-R24 | For 2+ stops: badge shows stop count + icon + change-of-city/airport/terminal flags; flags shown for any stop that had the change | 4.1.14.3.2 ¶ (2+ stops) | all | `TransferBadge/` | Implemented 6f67c06 | Multi-stop badge with count + flags verified. | P4 |
| 4.1.14.3-R25 | Badge type 1 (no-change): prev-arr city = next-dep city AND prev-arr airport = next-dep airport AND terminals same (or one terminal missing) | 4.1.14.3.2 rules | all | `TransferBadge/` | Implemented 6f67c06 | Badge type 1 condition verified. | P4 |
| 4.1.14.3-R26 | Badge type 2 (city-change): prev-arr city ≠ next-dep city → show city-change variant | 4.1.14.3.2 rules | all | `TransferBadge/` | Implemented 6f67c06 | Badge type 2 condition verified. | P4 |
| 4.1.14.3-R27 | Badge type 3 (terminal-change): same city + same airport + different terminals | 4.1.14.3.2 rules | all | `TransferBadge/` | Implemented 6f67c06 | Badge type 3 condition verified. | P4 |
| 4.1.14.3-R28 | Badge type 4 (airport-change): same city + different airports | 4.1.14.3.2 rules | all | `TransferBadge/` | Implemented 6f67c06 | Badge type 4 condition verified. | P4 |
| 4.1.14.3-R29 | Шереметьево/Внуково names in badge rendered as links opening new tab | 4.1.14.3.2 | all | `TransferBadge/` | TBD (P5) | Airport links in transfer badge deferred (same as R7/R14 airport-as-link backlog). | P4 |
| 4.1.14.3-R30 | For 2+ mixed stops (промежуточные посадки + пересадки): badge shows type «Пересадки»; flags priority order: «Смена города» > «Смена аэропорта» > «Смена терминала»; shown in occurrence order | 4.1.14.3.2 (2+ mixed) | all | `TransferBadge/` | Implemented 6f67c06 | Mixed-stop badge flag ordering verified in DayGroupedFlightList.test. | P4 |

## §4.1.14.4 — Развернутое представление (Expanded row)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.14.4-R1 | Expanded row has background color distinct from collapsed rows, plus a left-edge colored bar spanning the full expanded height | 4.1.14.4 ¶1 | all | `ScheduleFlightBody.tsx`, `ScheduleFlightBody.scss` | Implemented 4290c81 | Expanded row background + left bar verified in ScheduleFlightBody.test. | P4 |
| 4.1.14.4-R2 | Outbound and return direction expanded rows have identical element structure | 4.1.14.4 ¶2 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Same component/structure for both directions verified. | P4 |
| 4.1.14.4-R3 | Direct flight expanded (Table 41): header = flight number + mini-icon + departure block + duration + arrival block + toggle; action area = «Поделиться» + «Купить» + «Статус рейса» + «Детали рейса» | 4.1.14.4 Table 41 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Direct flight expanded structure verified in ScheduleFlightBody.test. | P4 |
| 4.1.14.4-R4 | Per-segment info block (in expanded direct, Table 41): segment number + full airline logo + aircraft type (if data received, shown under logo, else hidden) | 4.1.14.4 Table 41 / Table 42 per-seg rows | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Full logo + aircraft type per segment verified. | P4 |
| 4.1.14.4-R5 | Segment departure time (HH:MM) + day-change badge (with hover tooltip); departure city + airport (SVO/VKO as link) + terminal | 4.1.14.4 Table 42 per-seg rows 4–8 | all | `ScheduleFlightBody.tsx` | Partial 4290c81 | Segment departure block (time, badge, city, terminal) verified; SVO/VKO airport-as-link deferred to P5. | P4 |
| 4.1.14.4-R6 | Segment duration = `arr_scheduled - dep_scheduled` (schedule times used for schedule expanded) | 4.1.14.4 Table 42 per-seg row 9 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Segment duration uses scheduled times; verified. | P4 |
| 4.1.14.4-R7 | Segment arrival time (HH:MM) + day-change badge; arrival city + airport (SVO/VKO as link) + terminal | 4.1.14.4 Table 42 per-seg rows 10–14 | all | `ScheduleFlightBody.tsx` | Partial 4290c81 | Segment arrival block (time, badge, city, terminal) verified; SVO/VKO airport-as-link deferred to P5. | P4 |
| 4.1.14.4-R8 | Codeshare numbers (дополнительные номера рейса) shown if received; hidden if no data | 4.1.14.4 Table 41 row 16 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Codeshare field conditional display verified. | P4 |
| 4.1.14.4-R9 | Multi-segment expanded (Table 42): header shows single flight number + single mini-icon; then «Маршрут» (route strip) section (desktop/tablet only) + per-segment blocks | 4.1.14.4 Table 42 | desktop, tablet | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Multi-seg expanded header + route strip verified in ScheduleFlightBody.test. | P4 |
| 4.1.14.4-R10 | «Маршрут» (route strip) NOT shown on mobile | 4.1.14.4 Table 42 ¶ Маршрут | mobile | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Route strip hidden on mobile verified. | P4 |
| 4.1.14.4-R11 | Route strip content (multi-seg, Table 42 Маршрут): segment sequence: dep-time + city → [seg-num + seg-duration + day-change] → [stop-duration + city + airport + terminal] → ... → arr-time | 4.1.14.4 Table 42 Маршрут rows | desktop, tablet | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Route strip sequence elements verified. | P4 |
| 4.1.14.4-R12 | Route strip scroll: left/right scroll arrows shown when content exceeds area width (more than one intermediate stop); arrows hidden if content fits | 4.1.14.4 Table 42 Маршрут rows 1, 16 | desktop, tablet | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Route strip overflow scroll arrows verified. | P4 |
| 4.1.14.4-R13 | Intermediate-stop duration in route strip = `next_dep_scheduled - prev_arr_scheduled` | 4.1.14.4 Table 42 Маршрут row 11 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Stop duration calculation verified. | P4 |
| 4.1.14.4-R14 | If prev-arr city = next-dep city: show one city name; if different cities: show both cities with their airports/terminals | 4.1.14.4 Table 42 Маршрут row 12 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | City-change display in route strip verified. | P4 |
| 4.1.14.4-R15 | If prev-arr airport = next-dep airport: show one airport name; if different: show both airports (with their terminals) | 4.1.14.4 Table 42 Маршрут row 13 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Airport-change display in route strip verified. | P4 |
| 4.1.14.4-R16 | Inter-segment transfer/landing badge in expanded (§4.1.14.4.2): shows icon + type + duration + city + airport + terminal; duration = `next_dep_scheduled - prev_arr_scheduled` | 4.1.14.4 §4.1.14.4.2 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Transfer bar content (type, duration, city, airport, terminal) verified in ScheduleFlightBody.test. | P4 |
| 4.1.14.4-R17 | Expanded transfer badge type logic (§4.1.14.4.2) same as collapsed (type 1–4 by city/airport/terminal equality) | 4.1.14.4 §4.1.14.4.2 rules | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Expanded badge type matches collapsed type conditions; verified. | P4 |
| 4.1.14.4-R18 | Шереметьево/Внуково in expanded transfer badge rendered as link opening new tab | 4.1.14.4 §4.1.14.4.2 | all | `ScheduleFlightBody.tsx` | TBD (P5) | Airport link in expanded transfer badge deferred (airport-as-link backlog). | P4 |
| 4.1.14.4-R19 | Multi-leg operator-logo is compact (round mini-icon) in multi-seg expanded header (commit `3ae59da` parity) | 4.1.14.4 Table 42 row 2 | all | `src/features/schedule/*` | Implemented 3ae59da | Verified by commit `3ae59da`. | P4 |
| 4.1.14.4-R20 | Action area: «Поделиться» (share) — same social network list as §4.1.13.4.5; VK + copy-link; China locale adds Weibo | 4.1.14.4 §4.1.14.4.3 | all | `SharePanel/` | Implemented 4290c81 | Share in schedule expanded row verified in ScheduleFlightBody.test. | P4 |
| 4.1.14.4-R21 | «Купить» button: shown when `scheduled_departure_UTC > now + 2h` AND `scheduled_departure_UTC < now + 330 days` AND flight not cancelled | 4.1.14.4 §4.1.14.4.4 | all | `ScheduleFlightBody.tsx` | Done 4290c81 | Buy gate fixed: >2h lower bound AND <330 days upper bound AND not cancelled; 25 tests cover all cases. | P4 |
| 4.1.14.4-R22 | «Купить» for multi-segment/connecting: single button; availability uses first segment departure | 4.1.14.4 §4.1.14.4.4 | all | `ScheduleFlightBody.tsx` | Done 4290c81 | Single Buy for multi-seg uses first-leg departure; tested. | P4 |
| 4.1.14.4-R23 | «Купить» not shown for cancelled flights; rule applies to Online-Board and the same flight in Schedule | 4.1.14.4 §4.1.14.4.4 | all | `ScheduleFlightBody.tsx` | Done 4290c81 | Buy hidden for cancelled; cross-subsystem consistency tested. | P4 |
| 4.1.14.4-R24 | «Статус рейса» button shown only when the departure day equals today; for multi-seg/connecting: single button, based on first-segment departure | 4.1.14.4 §4.1.14.4.5 | all | `ScheduleFlightBody.tsx` | Done 4290c81 | Status gate fixed: same-day departure UTC only; first-leg governs; tested in ScheduleFlightBody.test. | P4 |
| 4.1.14.4-R25 | «Статус рейса» click: opens Online-Board flight card in new browser tab; mini-list in that card shows only the one clicked flight | 4.1.14.4 §4.1.14.4.5 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Status click opens OB card in new tab; verified. | P4 |
| 4.1.14.4-R26 | «Статус рейса» for multi-segment: opens a separate OB card for each segment in new browser tabs (requires browser popup permission) | 4.1.14.4 §4.1.14.4.5 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Multi-seg opens multiple tabs verified. | P4 |
| 4.1.14.4-R27 | «Статус рейса» for Аврора segment: navigates to `https://www.flyaurora.ru/`; for Победа: `https://www.pobeda.aero` | 4.1.14.4 §4.1.14.4.5 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Аврора/Победа external URL navigation verified. | P4 |
| 4.1.14.4-R28 | «Статус рейса» not shown if all segments are operated by airlines other than Аэрофлот/Россия/Аврора/Победа | 4.1.14.4 §4.1.14.4.5 | all | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Status hidden for non-group carriers verified. | P4 |
| 4.1.14.4-R29 | «Статус рейса» hover tooltip: «Подробнее о фактическом выполнении рейса» (desktop only; no tooltip on mobile) | 4.1.14.4 §4.1.14.4.5 | desktop | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Status button desktop-only tooltip verified. | P4 |
| 4.1.14.4-R30 | «Детали рейса» button: opens flight details card (§4.1.16) with date-tab set to the first-segment scheduled departure date | 4.1.14.4 §4.1.14.4.6 | all | `ScheduleFlightBody.tsx` | Done 4290c81 | Details button added (§4.1.14.4.6); always visible when `onStatus` handler provided; opens §4.1.16 with correct date-tab. | P4 |
| 4.1.14.4-R31 | «Мобильная версия» для кнопки «Статус рейса» в сегментах — кнопка не выводится (рис. 26) | 4.1.14.4 §4.1.14.4.5 Fig 26 | mobile | `ScheduleFlightBody.tsx` | Implemented 4290c81 | Status button suppressed in mobile per-segment view verified. | P4 |

---

# 4.1.15 — Карточка рейса, открытого из Онлайн-Табло
*Flight details page entered from Online-Board.*

Covers: form structure (4.1.15.1), mini-list (4.1.15.2), day tabs (4.1.15.3), direct-flight info (4.1.15.4), multi-segment info (4.1.15.5), intermediate landing (4.1.15.6), timeline-time algorithm (4.1.15.7), status rules (4.1.15.8), previous-flight algorithm (4.1.15.9), onboard meals (4.1.15.10), onboard services (4.1.15.11). TZ source lines 1927–2308.

## §4.1.15.1 — Структура экранной формы (Screen form structure)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R1 | Page entered via «Детали рейса» button from Online-Board expanded row | 4.1.15.1 TZ line 3 | all | `OnlineBoardDetailsPage.tsx` | Implemented 896dea9 | Entry-point navigation verified in OnlineBoardDetailsPage tests `896dea9`. | P5 |
| 4.1.15-R2 | Flight card of current day must NOT auto-refresh; only browser refresh triggers update | 4.1.15.1 TZ line 4 | all | `OnlineBoardDetailsPage.tsx` | Implemented 896dea9 | No polling or SignalR subscription on details page; data loaded once on mount. Verified in `896dea9` test suite. | P5 |
| 4.1.15-R3 | Desktop/tablet layout: left column = «Вернуться к Онлайн-Табло» + mini-list; right column = breadcrumbs, page title, day-tabs, flight header, flight info | 4.1.15.1 Table 47 | desktop, tablet | `OnlineBoardDetailsPage.tsx` | Done 896dea9 | Two-column layout wired: FlightsMiniList in contentLeft, DayTabs + flight info in contentRight. Tests R3/R5/R6/R7 in `OnlineBoardDetailsPage.test.tsx`. | P5 |
| 4.1.15-R4 | Mobile layout: breadcrumbs → page title → «Вернуться к Онлайн-Табло» → day list → flight header → flight info (single column, no mini-list) | 4.1.15.1 Table 47 | mobile | `OnlineBoardDetailsPage.tsx` | Done 896dea9 | Mobile single-column layout verified; mini-list hidden at mobile breakpoint per R22. | P5 |
| 4.1.15-R5 | «Вернуться к Онлайн-Табло» navigates back to the search results with search state preserved; if search was for today, list is scrolled to the flight with departure closest to user's current time | 4.1.15.1 Table 48 row 1 | all | `OnlineBoardDetailsPage.tsx` | Done 896dea9 | Back-navigation with preserved search state tested in `896dea9`. | P5 |
| 4.1.15-R6 | Flight header contains: flight number, codeshare numbers, airline icon (full, not round), route-changed icon, airport-return icon, share, buy, online check-in, last-update timestamp | 4.1.15.1 Table 48 row 5 | all | `FlightSchedule/` | Done 896dea9 | Header elements presence verified in `OnlineBoardDetailsPage.test.tsx` R6 assertion. | P5 |
| 4.1.15-R7 | On scroll down: day-tabs and flight header are sticky (fixed to top); mini-list scrolls with main content (always accessible); no sticky on mobile | 4.1.15.1 Table 49 | desktop, tablet | `OnlineBoardDetailsPage.tsx` | Done 896dea9 | DayTabs wired into stickyContent prop; test R7 verifies stickyContent receives day-tabs. | P5 |
| 4.1.15-R8 | Day-change badge `{-X}` / `{+X}` must appear for ALL times on the flight card where applicable (night flights, registration starting previous day, etc.) | 4.1.15.1 TZ line 52 | all | `TimeGroup.tsx`, `dayChange.ts` | Implemented 63fc606 | Per-type day-change badges (scheduled + actual independent) cover all time fields. Wiring confirmed in `63fc606` + `7fcb844`. | P5 |
| 4.1.15-R9 | Day-change badge tooltip on hover: shows «День» if badge is -1/+1; shows «ДД.ММ.ГГГГ» departure/arrival date if badge is ±2 or more | 4.1.15.1 TZ line 53 | desktop, tablet | `dayChange.ts:dayChangeBadgeTooltip` | Implemented 63fc606 | `dayChangeBadgeTooltip` already Implemented per 4.1.17-R5; covers this surface too. | P5 |
| 4.1.15-R10 | Day-change badge tooltip NOT shown on mobile | 4.1.15.1 TZ line 53 | mobile | — | TBD (backlog) | Same deferral as 4.1.17-R8: CSS media query needed; deferred to backlog. | P5 |

## §4.1.15.2 — Мини-список (Mini-list)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R11 | Mini-list shows the same results set the user searched from, with minimal data fields | 4.1.15.2 TZ line 56 | desktop, tablet | `FlightsMiniList/` | Implemented 896dea9 | FlightsMiniList receives search-context flights prop; verified in `FlightsMiniListItem.test.tsx`. | P5 |
| 4.1.15-R12 | Selected flight in mini-list has distinct color indicator («выбранного рейса») | 4.1.15.2 TZ line 57 | desktop, tablet | `FlightsMiniList/` | Done 896dea9 | Selected-state indicator tested (R12 in `896dea9`). | P5 |
| 4.1.15-R13 | Mini-list is auto-scrolled to the selected flight on load | 4.1.15.2 TZ line 58 | desktop, tablet | `FlightsMiniList/` | Done 896dea9 | Auto-scroll on mount tested (R13 in `896dea9`). | P5 |
| 4.1.15-R14 | Mini-list sort order: departure or arrival time (scheduled/expected/actual) per search mode — same as §4.1.13.2 sort rules | 4.1.15.2 TZ line 59 | desktop, tablet | `FlightsMiniList/` | Implemented 896dea9 | Sort order inherited from search-context results; no re-sort in mini-list. | P5 |
| 4.1.15-R15 | Mini-list panel scrolls as main page scrolls (sticky panel behavior) | 4.1.15.2 TZ line 60 | desktop, tablet | `OnlineBoardDetailsPage.tsx` | Done 896dea9 | Mini-list wired to contentLeft (non-sticky); scrolls with page as required. | P5 |
| 4.1.15-R16 | Clicking another flight in mini-list: color indicator moves to new flight AND opens that flight's card | 4.1.15.2 TZ line 61 | desktop, tablet | `FlightsMiniList/` | Done 896dea9 | Click → navigation + indicator update tested (R16 in `896dea9`). | P5 |
| 4.1.15-R17 | Moving focus to main flight info panel does NOT remove the color indicator from mini-list | 4.1.15.2 TZ line 61 | desktop, tablet | `FlightsMiniList/` | Done 896dea9 | Indicator persistence on focus change tested (R17 in `896dea9`). | P5 |
| 4.1.15-R18 | Mini-list row fields: flight number, departure time, status icon, arrival time; day-change badges if applicable; departure/arrival date (one of: scheduled/expected/actual); dep city name; dep airport name (SVO/VKO as hyperlink); dep terminal code; arr city name; arr airport name (SVO/VKO as hyperlink); arr terminal code | 4.1.15.2 Table 50 | desktop, tablet | `FlightsMiniListItem.tsx` | Implemented 896dea9 | All Table 50 fields rendered in FlightsMiniListItem; SVO/VKO as role=link spans. | P5 |
| 4.1.15-R19 | SVO airport name in mini-list shown as link → `https://www.svo.aero/ru/main` (new tab) | 4.1.15.2 Table 50 | desktop, tablet | `FlightsMiniListItem.tsx` | Done 896dea9 | SVO link rendered as role=link span opening external site in new tab. Tested in `FlightsMiniListItem.test.tsx`. | P5 |
| 4.1.15-R20 | VKO airport name in mini-list shown as link → `http://www.vnukovo.ru` (new tab) | 4.1.15.2 Table 50 | desktop, tablet | `FlightsMiniListItem.tsx` | Done 896dea9 | VKO link rendered as role=link span opening external site in new tab. Tested in `FlightsMiniListItem.test.tsx`. | P5 |
| 4.1.15-R21 | After browser refresh, mini-list retains full state (same search query as when card was first opened) — guaranteed by URL encoding of search params per §4.1.2 | 4.1.15.2 TZ line 76-77 | desktop, tablet | — | Implemented 896dea9 | URL-encoded search params guarantee refresh-safe state (P1 §4.1.2 implementation). | P5 |
| 4.1.15-R22 | Mini-list NOT shown on mobile | 4.1.15.2 TZ line 78 | mobile | `FlightsMiniList/` | Done 896dea9 | Mini-list hidden at mobile breakpoint via CSS; tested (R22 in `896dea9`). | P5 |

## §4.1.15.3 — Поведение Табов-дней (Day-tabs behavior)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R23 | Day-tabs present on details page, grouped in sets of 7, starting from -1 from today | 4.1.15.3 TZ lines 80-84 | all | `BoardDetailsHeader/` | Done 896dea9 | 7-tab grouping starting from -1 verified (R23 in `OnlineBoardDetailsPage.test.tsx`). | P5 |
| 4.1.15-R24 | Active day-tabs range: [-1 from today, +14 from today]; days outside range are inactive (grayed, non-clickable) | 4.1.15.3 TZ line 82 | all | `DayTabs/` | Implemented 896dea9 | Online-Board [-1, +14] window enforced in DayTabs component; days outside window rendered inactive. | P5 |
| 4.1.15-R25 | Day-tabs also respect which days this flight actually operates (per §4.1.16.8 execution-days algorithm); non-operating days are blocked | 4.1.15.3 TZ line 83 | all | — | TBD (backlog) | Per-flight operating-day blocking not yet implemented in tab layer; deferred to backlog. | P5 |
| 4.1.15-R26 | If the last active tab is not the last in the 7-slot group, remaining slots are filled but rendered inactive; right-scroll arrow is inactive | 4.1.15.3 TZ line 85 | all | `DayTabs/` | Implemented 896dea9 | Fill-to-7 and right-arrow disabling present in DayTabs; tested in existing DayTabs unit tests. | P5 |
| 4.1.15-R27 | The tab matching the currently open day is highlighted; page auto-scrolls to it | 4.1.15.3 TZ line 87 | all | `DayTabs/` | Done 896dea9 | Active-tab highlight + scroll tested (R27 in `896dea9`). | P5 |
| 4.1.15-R28 | Clicking a new day-tab loads that day's card; loader shown if response takes time | 4.1.15.3 TZ line 88 | all | `OnlineBoardDetailsPage.tsx` | Done 896dea9 | Navigation on tab click tested (R28 in `896dea9`). | P5 |
| 4.1.15-R29 | Mobile: day-tabs replaced by a date list (not tab chips) | 4.1.15.3 TZ line 90 | mobile | `OnlineBoardDetailsPage.tsx` | Implemented 896dea9 | Mobile date-list variant present; tested at mobile viewport. | P5 |

## §4.1.15.4 — Информация о прямом рейсе (Direct flight info)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R30 | Flight header: flight number format `SU XXXX` | 4.1.15.4 Table 51 row 1 | all | `FlightSchedule/` | Implemented 21b6c90 | Header flight number format verified in audit. | P5 |
| 4.1.15-R31 | Flight header: codeshare numbers shown as comma-separated list, no label | 4.1.15.4 Table 51 row 2 | all | `FlightSchedule/` | Implemented 21b6c90 | Codeshare display verified in direct-flight audit. | P5 |
| 4.1.15-R32 | Flight header: full airline icon (not round mini icon) | 4.1.15.4 Table 51 row 3 | all | `FlightSchedule/` | Implemented 21b6c90 | Full icon (not round) confirmed in direct-flight audit; wired via operatorIcon helper `5d31f43`. | P5 |
| 4.1.15-R33 | Flight header: «Последнее обновление» timestamp in format `ЧЧ:ММ ДД.ММ.ГГГГ` | 4.1.15.4 Table 51 row 7 | all | `FlightSchedule/` | Implemented 21b6c90 | Last-update timestamp field and format verified in audit. | P5 |
| 4.1.15-R34 | Departure time: shows one of [scheduled / expected / actual]; if changed, prior time shown smaller + strikethrough | 4.1.15.4 Table 51 row 1 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Time priority + strikethrough logic pre-existing; verified in direct-flight audit. | P5 |
| 4.1.15-R35 | Day-change badge for departure time with tooltip rules per §4.1.15.1 | 4.1.15.4 Table 51 row 2 (Info) | all | `TimeGroup.tsx` | Implemented 63fc606 | Per-type dep badge covered by 4.1.17-R4 implementation; wired in TimeGroup. | P5 |
| 4.1.15-R36 | Departure city name displayed | 4.1.15.4 Table 51 row 3 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Departure city field verified in audit. | P5 |
| 4.1.15-R37 | Departure airport name displayed; SVO/VKO shown as hyperlink (new tab to respective site) | 4.1.15.4 Table 51 rows 4, 11 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Airport field verified; SVO/VKO link pre-existing. | P5 |
| 4.1.15-R38 | Departure terminal code shown if data present | 4.1.15.4 Table 51 row 5 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Terminal conditional display verified. | P5 |
| 4.1.15-R39 | Status shown as icon + text; status-change rules per Appendix 2 | 4.1.15.4 Table 51 row 6 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Status icon+text pair verified in audit. | P5 |
| 4.1.15-R40 | Timeline: moving aircraft icon colored per status (see §4.1.15.8) | 4.1.15.4 Table 51 row 7.1 | all | `OnlineBoardDetailsPage.tsx` | Done e33c8c4 | Status-to-color mapping fixed (Delayed→orange, Sent in isInFlight branch); 8 per-status assertions. | P5 |
| 4.1.15-R41 | Timeline: «В пути» label + elapsed time per §4.1.15.7 algorithm; format `{Д}д. {ЧЧ}ч. {ММ}мин.` (shorter units omitted if zero) | 4.1.15.4 Table 51 row 7.2 | all | `timelineTime.ts` | Done 877cd87 | `formatTimelineDuration` implements zero-unit omission; wired into OnlineBoardDetailsPage «В пути». 24 unit tests. | P5 |
| 4.1.15-R42 | Timeline: «До прилета» label + remaining time per §4.1.15.7 algorithm; same format as «В пути» | 4.1.15.4 Table 51 row 7.3 | all | `timelineTime.ts` | Done 877cd87 | «До прилета» remaining time wired; same formatter as «В пути». | P5 |
| 4.1.15-R43 | Day-change badge for arrival time with tooltip rules per §4.1.15.1 | 4.1.15.4 Table 51 row 8 (Info) | all | `TimeGroup.tsx` | Implemented 63fc606 | Per-type arr badge covered by 4.1.17-R2/R4. | P5 |
| 4.1.15-R44 | Arrival time: shows one of [scheduled / expected / actual]; if changed, prior time shown smaller + strikethrough | 4.1.15.4 Table 51 row 9 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Arrival time priority + strikethrough verified in audit. | P5 |
| 4.1.15-R45 | Arrival city name displayed | 4.1.15.4 Table 51 row 10 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Arrival city field verified. | P5 |
| 4.1.15-R46 | Arrival terminal code shown if data present | 4.1.15.4 Table 51 row 12 (Info) | all | `FlightSchedule/` | Implemented 21b6c90 | Arrival terminal conditional display verified. | P5 |
| 4.1.15-R47 | «Времена выполнения рейса» block: scheduled dep time with UTC offset, scheduled arr time with UTC offset, per §4.1.13.4.2 | 4.1.15.4 Table 51 (Времена) | all | `FlightSchedule/` | Implemented 21b6c90 | UTC-offset time block verified in direct-flight audit. | P5 |
| 4.1.15-R48 | «Времена выполнения рейса» block: each time accompanied by its date `ДД.ММ.ГГГГ` | 4.1.15.4 Table 51 (Времена) row 2 | all | `FlightSchedule/` | Implemented 21b6c90 | Date next to UTC times verified. | P5 |
| 4.1.15-R49 | «Времена выполнения рейса» block: footnote «* Время прилета и расстояния являются расчетными и примерными. Время может измениться в зависимости от погодных условий и загрузки аэропорта.» | 4.1.15.4 Table 51 (Времена) row 3 | all | `FlightSchedule/` | Implemented 21b6c90 | Footnote text verified in direct-flight audit. | P5 |
| 4.1.15-R50 | «Детали рейса» accordion: collapsible/expandable with chevron direction change | 4.1.15.4 Table 51 (Детали) row 1 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Accordion expand/collapse + chevron flip verified. | P5 |
| 4.1.15-R51 | Registration block shown as soon as registration opens; never hidden again once shown | 4.1.15.4 Table 51 (Детали) row 2.1 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Registration block lifecycle verified in direct-flight audit. | P5 |
| 4.1.15-R52 | Registration block: start time `ЧЧ:ММ ДД.ММ.ГГГГ` under label «Время начала» | 4.1.15.4 Table 51 (Детали) row 2.2 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Reg start-time label + format verified. | P5 |
| 4.1.15-R53 | Registration block: end time `ЧЧ:ММ ДД.ММ.ГГГГ` under label «Время окончания» | 4.1.15.4 Table 51 (Детали) row 2.3 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Reg end-time label + format verified. | P5 |
| 4.1.15-R54 | Reg block footnote «* Время в системе - МЕСТНОЕ.» shown only when reg block is the SOLE block in «Детали рейса» (boarding/deboarding not yet shown) | 4.1.15.4 Table 51 (Детали) row 2.4 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Footnote conditional logic verified in audit. | P5 |
| 4.1.15-R55 | Boarding block shown as soon as boarding opens; never hidden once shown; footnote «* Время в системе - МЕСТНОЕ.» shown if Registration block is present | 4.1.15.4 Table 51 (Детали) rows 3.1, 3.2 | all | `BoardingPanel.tsx` | Done 21b6c90 | Boarding block lifecycle + gate + dispatch fields added (Angular parity); 4 new BoardingPanel tests. | P5 |
| 4.1.15-R56 | Deboarding block shown as soon as deboarding starts; never hidden once shown; footnote if reg+boarding blocks are both present | 4.1.15.4 Table 51 (Детали) rows 4.1, 4.2 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Deboarding lifecycle + footnote condition verified. | P5 |
| 4.1.15-R57 | Aircraft block («Борт»): shown only if any aircraft data received; if data present, show whatever attributes were received | 4.1.15.4 Table 51 (Детали) row 5.1 | all | `AircraftPanel.tsx` | Implemented 21b6c90 | Aircraft block conditional verified; 2 new AircraftPanel tests. | P5 |
| 4.1.15-R58 | Aircraft block: «Тип ВС» rendered as a hyperlink → `https://www.aeroflot.ru/ru-ru/about/plane_park` (new tab) | 4.1.15.4 Table 51 (Детали) row 5.2 | all | `AircraftPanel.tsx` | Implemented 21b6c90 | Aircraft-type hyperlink verified in audit. | P5 |
| 4.1.15-R59 | Aircraft block: seat counts — «Количество мест», «Эконом», «Комфорт», «Бизнес» | 4.1.15.4 Table 51 (Детали) rows 5.3–5.6 | all | `AircraftPanel.tsx` | Implemented 21b6c90 | Seat-count fields verified. | P5 |
| 4.1.15-R60 | Aircraft block: «Название» (aircraft name/registration) | 4.1.15.4 Table 51 (Детали) row 5.7 | all | `AircraftPanel.tsx` | Done 21b6c90 | Aircraft tail number (registration) added from `aircraft.registration`; i18n key AIRPLANE.TAIL-NUMBER added to all 9 locales. | P5 |
| 4.1.15-R61 | Aircraft block: «Предыдущий рейс» link to previous flight card (new tab); calculated per §4.1.15.9 | 4.1.15.4 Table 51 (Детали) row 5.8 | all | `AircraftPanel.tsx` | Done 1740af6 | Previous-flight rendered as clickable link (new tab); freshness gate > today−2 days. | P5 |
| 4.1.15-R62 | For -1 day flights: reg/boarding/deboarding blocks all shown with all attributes | 4.1.15.4 TZ line 153 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | -1 day flight block display verified in audit. | P5 |
| 4.1.15-R63 | For today's flights: blocks appear progressively per §4.1.13.4.3 algorithm; shown but not hidden once visible; hidden if data not yet received | 4.1.15.4 TZ line 154 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Progressive block appearance verified in audit. | P5 |
| 4.1.15-R64 | For +1 day flights: same progressive algorithm as today (per §4.1.13.4.3); no block pre-shown | 4.1.15.4 TZ line 155 | all | `FlightDetailsAccordion.tsx` | Implemented 21b6c90 | Future-day block behavior verified in audit. | P5 |
| 4.1.15-R65 | «Расписание рейса» accordion block: collapsible; fields = scheduled dep UTC±offset, scheduled arr UTC±offset, «Время в пути», days-of-week, footnote | 4.1.15.4 Table 51 (Расписание) | all | `FlightSchedule/` | Implemented 21b6c90 | Schedule accordion fields verified in direct-flight audit. | P5 |

## §4.1.15.5 — Информация о многосегментном рейсе (Multi-segment flight info)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R66 | Multi-segment = single flight number, same airline on all legs | 4.1.15.5 TZ line 158 | all | `FullRouteTimeline/` | Implemented 7fcb844 | Definition applied to routing in FullRouteTimeline; verified in multi-segment audit. | P5 |
| 4.1.15-R67 | Header «Маршрут» strip shown on desktop/tablet; hidden on mobile | 4.1.15.5 Table 52 (Маршрут header) | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Route strip desktop/tablet visibility verified; hidden on mobile. | P5 |
| 4.1.15-R68 | Route strip: left/right scroll arrows shown if more than one intermediate stop; hidden if all fits | 4.1.15.5 Table 52 rows 1, 16 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Scroll arrows logic verified in audit. | P5 |
| 4.1.15-R69 | Route strip: first-segment departure time (one of scheduled/expected/actual); if changed, old time strikethrough + smaller | 4.1.15.5 Table 52 row 2 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Route strip departure time priority verified. | P5 |
| 4.1.15-R70 | Route strip: segment-level day-change badges with tooltip | 4.1.15.5 Table 52 rows 3, 9 | desktop, tablet | `Timeline/TimeCell` | Done 7fcb844 | TimeCell component added to Timeline; day-change badges on dep/arr times in route bar. 9 new assertion tests (no-badge-when-0, ±1/±2/-1, actual-takes-priority, multi-badge). | P5 |
| 4.1.15-R71 | Route strip: segment index N=(n+1) displayed per segment | 4.1.15.5 Table 52 row 7 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Segment ordinal numbers verified in audit. | P5 |
| 4.1.15-R72 | Route strip: segment duration = actual arrival − actual departure (using best available time per position) | 4.1.15.5 Table 52 row 8 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Segment duration calculation verified; uses best-available times. | P5 |
| 4.1.15-R73 | Route strip: intermediate stop city — if dep.prev == arr.next same city shown once; if changed (route-change event) new city shown, old city strikethrough | 4.1.15.5 Table 52 row 12 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Intermediate-city display and route-change strikethrough verified. | P5 |
| 4.1.15-R74 | Route strip: intermediate stop airport name; SVO/VKO as link; same same/different airport logic as city | 4.1.15.5 Table 52 row 13 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Intermediate airport + SVO/VKO link verified. | P5 |
| 4.1.15-R75 | Route strip: intermediate stop terminal — if same terminal both sides show once; if different show both | 4.1.15.5 Table 52 row 14 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Terminal display logic verified in audit. | P5 |
| 4.1.15-R76 | Route strip: stop duration (ground time) = next-seg dep − prev-seg arr (best available times) | 4.1.15.5 Table 52 row 11 | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Ground-time calculation verified in multi-segment audit. | P5 |
| 4.1.15-R77 | «Общая информация по сегменту» block: segment #, flight#, codeshare, full airline icon, route-changed icon, airport-return icon, collapse/expand control | 4.1.15.5 Table 52 (Общая info) rows 1–7 | all | `FullRouteTimeline/` | Implemented 7fcb844 | Per-segment header fields verified; routeChanged/returnToAirport from any leg, codesharing in header confirmed. | P5 |
| 4.1.15-R78 | Segment collapse: hides times block («Время выполнения сегмента») and details block («Детали сегмента»); always keeps rows 1–6 of header visible | 4.1.15.5 Table 52 (Общая info) row 7 | all | `FullRouteTimeline/` | Implemented 7fcb844 | Segment collapse behavior verified in multi-segment audit. | P5 |
| 4.1.15-R79 | Per-segment departure/arrival time fields: same rules as direct flight (priority, strikethrough, day-change badge) | 4.1.15.5 Table 52 (Общая info) rows 8–18 | all | `Timeline/TimeCell`, `LegRoute` | Done 7fcb844 | TimeCell badge on per-segment times; LegRoute arrival day-change badge confirmed. | P5 |
| 4.1.15-R80 | Per-segment timeline (embedded): aircraft icon + «В пути» + «До прилета» per §4.1.15.7 and §4.1.15.8 | 4.1.15.5 Table 52 rows 13.1–13.3 | all | `timelineTime.ts`, `OnlineBoardDetailsPage.tsx` | Done 877cd87 | Per-segment timeline uses timelineTime.ts algorithm; status coloring from e33c8c4. | P5 |
| 4.1.15-R81 | «Детали сегмента» block: same reg/boarding/deboarding/aircraft/meals/services sub-blocks as direct flight | 4.1.15.5 Table 52 (Детали) | all | `FlightDetailsAccordion.tsx` | Implemented 7fcb844 | Per-segment details block verified; uses same sub-blocks as direct flight. | P5 |
| 4.1.15-R82 | Intermediate-landing plaque (§4.1.15.6) rendered between segments | 4.1.15.5 Table 52 (Плашка) | all | `TransferBar/` | Done c0c2d7d | Plaque rendered between segments via TransferBar with isIntermediateLanding prop. | P5 |
| 4.1.15-R83 | «Расписание рейса» block (multi-segment): single schedule for all segments; «Время в пути» = total (all segments + ground times); if unplanned extra stop, show original scheduled duration | 4.1.15.5 Table 52 (Расписание) | all | `FlightSchedule/` | Implemented 7fcb844 | Combined schedule + total duration logic verified in multi-segment audit. | P5 |

## §4.1.15.6 — Информация о промежуточной посадке (Intermediate landing)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R84 | Intermediate-landing plaque shown between two segments when they share the same flight number | 4.1.15.6 TZ line 253 | all | `TransferBar/` | Done c0c2d7d | isIntermediateLanding prop added; plaque label switches between «Промежуточная посадка» and «Пересадка» based on flight-number identity. | P5 |
| 4.1.15-R85 | Plaque fields: type icon, duration, start–end times with day-change badges, city, airport (optional), terminal (optional) | 4.1.15.6 TZ lines 258-270 | all | `TransferBar/` | Done c0c2d7d | All plaque fields verified; StationChange always rendered (not only when separated). | P5 |
| 4.1.15-R86 | Plaque duration = arr time of prev segment − dep time of next segment (best available per position) | 4.1.15.6 TZ line 265 | all | `TransferBar/` | Done c0c2d7d | Duration uses actual/estimated UTC times when viewType=Onlineboard instead of always scheduled. | P5 |
| 4.1.15-R87 | Plaque time format `ЧЧ:ММ{±X}–ЧЧ:ММ{±X}` using best available time per position | 4.1.15.6 TZ line 266 | all | `TransferBar/` | Implemented c0c2d7d | Time format with badges verified; best-available time selection wired. | P5 |
| 4.1.15-R88 | SVO/VKO airport name on plaque shown as hyperlink (new tab) | 4.1.15.6 TZ line 272 | all | `TransferBar/` | Implemented c0c2d7d | SVO/VKO link on plaque verified in intermediate-landing audit. | P5 |
| 4.1.15-R89 | Day-change badge tooltip on plaque: «День» for ±1; no tooltip on mobile | 4.1.15.6 TZ lines 273-275 | desktop, tablet | `TransferBar/` | Implemented c0c2d7d | Badge tooltip on plaque verified; mobile suppression follows 4.1.17-R8 deferral. | P5 |
| 4.1.15-R90 | Plaque type A (same city/airport/terminal): shown when arr-prev airport = dep-next airport AND terminals equal (or one side missing) | 4.1.15.6 TZ lines 277-286 | all | `TransferBar/` | Implemented c0c2d7d | Plaque-A condition verified in intermediate-landing audit. | P5 |
| 4.1.15-R91 | Plaque type B (city change): shown when arr-prev city ≠ dep-next city | 4.1.15.6 TZ line 287 | all | `TransferBar/` | Implemented c0c2d7d | Plaque-B city-change condition verified. | P5 |
| 4.1.15-R92 | Plaque type C (terminal change): same city + same airport + different terminals | 4.1.15.6 TZ lines 288-293 | all | `TransferBar/` | Implemented c0c2d7d | Plaque-C terminal-change condition verified. | P5 |
| 4.1.15-R93 | Plaque type D (airport change within same city): same city, airports differ (using scheduled vs actual comparison matrix) | 4.1.15.6 TZ lines 294-306 | all | `TransferBar/` | Implemented c0c2d7d | Plaque-D airport-change comparison matrix logic verified. | P5 |

## §4.1.15.7 — Алгоритм расчета времени на «Временной шкале» (Timeline time algorithm)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R94 | Total flight minutes = (actual/expected arr time UTC) − (actual dep time UTC) | 4.1.15.7 TZ line 309 | all | `timelineTime.ts:computeTimelineCalc` | Done 877cd87 | Formula implemented; arrival uses actual > estimated > scheduled priority. 24 unit tests. | P5 |
| 4.1.15-R95 | Elapsed minutes «В пути» = current time UTC − dep time UTC | 4.1.15.7 TZ line 310 | all | `timelineTime.ts:computeTimelineCalc` | Done 877cd87 | Elapsed formula implemented + unit tests. | P5 |
| 4.1.15-R96 | Remaining minutes «До прилета» = arr time UTC − current time UTC | 4.1.15.7 TZ line 311 | all | `timelineTime.ts:computeTimelineCalc` | Done 877cd87 | Remaining formula implemented + unit tests. | P5 |
| 4.1.15-R97 | Aircraft position on timeline = elapsed / total as percentage | 4.1.15.7 TZ line 312 | all | `timelineTime.ts:computeTimelineCalc` | Done 877cd87 | Position percentage calculation implemented + unit tests. | P5 |
| 4.1.15-R98 | Display format: `{Д}д. {ЧЧ}ч. {ММ}мин.` — omit units that are zero; e.g. «45 мин.», «2ч. 15 мин.», «1д. 3ч. 5 мин.» | 4.1.15.7 TZ line 313 | all | `timelineTime.ts:formatTimelineDuration` | Done 877cd87 | `formatTimelineDuration` omits zero leading units («45мин.» not «0ч. 45мин.»); all branches tested. | P5 |

## §4.1.15.8 — Правила показа статусов на временной шкале (Status display rules)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R99 | Aircraft icon on timeline is colored per flight status (colors per Fig 28 / Appendix 2) | 4.1.15.8 TZ line 315-317 | all | `OnlineBoardDetailsPage.tsx` | Done e33c8c4 | Delayed→orange, Sent included in isInFlight; 8 per-status assertions covering all FlightStatus enum values. | P5 |
| 4.1.15-R100 | «В пути» and «До прилета» labels shown/hidden per status according to Fig 28 rules | 4.1.15.8 TZ line 315-317 | all | `OnlineBoardDetailsPage.tsx` | Done e33c8c4 | Label visibility per-status verified in `OnlineBoardDetailsPage.test.tsx` 8 per-status tests. | P5 |

## §4.1.15.9 — Алгоритм расчета предыдущего рейса (Previous-flight algorithm)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R101 | System shows previous flight number (the flight this aircraft operated before the current one) | 4.1.15.9 TZ line 319 | all | `AircraftPanel.tsx` | Done 1740af6 | Previous-flight rendered as clickable link opening prior flight's card in new tab; freshness gate (> today−2 days); outside window falls back to plain text. | P5 |
| 4.1.15-R102 | Source: `OnwardFlight` field in AOMM telegram identifies the NEXT flight for this aircraft; `PreviousFlight` is derived inversely | 4.1.15.9 TZ line 320 | all | — | Implemented 1740af6 | Backend concern; FE reads `previousFlight` from API and renders it. URL built with `buildFlightUrlParams` using `previousFlight.localDate`. | P5 |
| 4.1.15-R103 | If `OnwardFlight` for another flight changes/is removed, `PreviousFlight` on the current flight is also updated (backend concern; FE renders API result) | 4.1.15.9 TZ lines 321-323 | all | — | Implemented 1740af6 | Backend concern; FE re-renders when API returns updated prev-flight data (no caching of prev-flight in FE state). | P5 |

## §4.1.15.10 — Питание на борту (On-board meals)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R104 | Meal block shows: icon + name for each meal class present (Economy / Comfort / Business / Special) | 4.1.15.10 TZ lines 326-332 | all | `MealsPanel/` | Implemented 21b6c90 | Meal class icons and names verified via assertion tests in direct-flight audit. | P5 |
| 4.1.15-R105 | Clicking a meal class opens `https://www.aeroflot.ru/ru-ru/information/onboard/dining?0000#meal-type_{N}` in new tab (N=0 Economy, 1 Comfort, 2 Business, 3 Special) | 4.1.15.10 TZ lines 333-341 | all | `MealsPanel/` | Implemented 21b6c90 | Meal click target URLs verified in audit (already covered by existing assertion tests). | P5 |
| 4.1.15-R106 | Meal block hidden entirely if no meal data received | 4.1.15.10 TZ line 342 | all | `MealsPanel/` | Implemented 21b6c90 | Meal block absent when data missing — verified by existing assertion tests. | P5 |
| 4.1.15-R107 | Special-meal icon shows a loading gif until API response; then show static icon if positive response, hide icon if negative | 4.1.15.10 TZ lines 344-347 | all | `MealsPanel/` | Implemented 21b6c90 | Special-meal loader → static/hidden flow verified (fallback icon logic). | P5 |
| 4.1.15-R108 | Special-meal API call rules: if no Economy AND no Business data → don't call API; if only Business → call with `cabin=business`; if both Economy+Business → call with `cabin=econom` only; no Business call needed | 4.1.15.10 TZ lines 349-363 | all | `MealsPanel/` | Implemented 21b6c90 | API call conditions verified in direct-flight audit. | P5 |
| 4.1.15-R109 | Meal API responses cached for 3 hours | 4.1.15.10 TZ line 363 | all | `MealsPanel/` | Implemented 21b6c90 | 3h cache on meal API verified (server-side cache header; FE benefits automatically). | P5 |

## §4.1.15.11 — Услуги на борту (On-board services)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.15-R110 | Services block shows: icon + name for each service in data (Покупки на борту, Space+, Интернет на борту, Система мультимедийных развлечений, Выбор места) | 4.1.15.11 TZ lines 368-375 | all | `ServicesPanel/` | Implemented 21b6c90 | Service icons and names verified via existing assertion tests in direct-flight audit. | P5 |
| 4.1.15-R111 | Clicking a service opens its URL in new tab (Покупки: `.../shopping_onboard_ec`; Space+: `.../space_plus`; Интернет: `.../at_height`; Мультимедиа: `entertainment.aeroflot.ru`; Выбор места: `.../additional_service/#seat_reservation`) | 4.1.15.11 TZ lines 376-381 | all | `ServicesPanel/` | Implemented 21b6c90 | Each service URL target verified in direct-flight audit. | P5 |
| 4.1.15-R112 | Services block hidden entirely if no service data received | 4.1.15.11 TZ line 382 | all | `ServicesPanel/` | Implemented 21b6c90 | Block absent when data missing — verified by existing assertion tests. | P5 |

---

# 4.1.16 — Карточка рейса, открытого из Расписания
*Flight details page entered from Schedule.*

Same sub-structure as 4.1.15 plus connecting-flight variant (4.1.16.6) and intermediate-landing+transfer (4.1.16.7) and execution-days algorithm (4.1.16.8). TZ source lines 2309–2886.

## §4.1.16.1 — Структура экранной формы (Screen form structure)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R1 | Page entered via «Детали рейса» button from Schedule expanded row | 4.1.16.1 TZ line 385 | all | `ScheduleDetailsPage.tsx` | Implemented 896dea9 | Entry-point navigation verified in ScheduleDetailsPage tests. | P5 |
| 4.1.16-R2 | Desktop/tablet layout: left = «Вернуться к Расписанию» + mini-list; right = breadcrumbs, page title, day-tabs, flight header, flight info | 4.1.16.1 Table 54 | desktop, tablet | `ScheduleDetailsPage.tsx` | Done 896dea9 | FlightsMiniList wired into contentLeft, DayTabs (schedule window [-1, +330]) into stickyContent. Tests R2/R4 in `ScheduleDetailsPage.test.tsx`. | P5 |
| 4.1.16-R3 | Mobile layout: breadcrumbs → page title → «Вернуться к Расписанию» → day list → flight header → flight info (no mini-list) | 4.1.16.1 Table 54 | mobile | `ScheduleDetailsPage.tsx` | Done 896dea9 | Mobile single-column layout tested (R3 in `ScheduleDetailsPage.test.tsx`). | P5 |
| 4.1.16-R4 | «Вернуться к Расписанию» navigates back with search state preserved (filter + data) | 4.1.16.1 Table 55 row 1 | all | `ScheduleDetailsPage.tsx` | Done 896dea9 | Back-navigation with preserved search state tested (R4 in `896dea9`). | P5 |
| 4.1.16-R5 | Flight header contains: flight number(s), codeshare, airline icon, share, buy, «Статус рейса» button, last-update timestamp; no «Онлайн-Регистрация» button (Schedule variant) | 4.1.16.1 Table 55 row 5 | all | `ScheduleDetailsPage.tsx` | Done 896dea9 | Header verified (R5 in `ScheduleDetailsPage.test.tsx`): no check-in button, «Статус рейса» link present. | P5 |
| 4.1.16-R6 | On scroll: day-tabs and flight header are sticky on desktop/tablet; mini-list scrolls with page; no sticky on mobile | 4.1.16.1 Table 56 | desktop, tablet | `ScheduleDetailsPage.tsx` | Done 896dea9 | DayTabs wired into stickyContent; R6 verified in `ScheduleDetailsPage.test.tsx`. | P5 |
| 4.1.16-R7 | Flight info section supports three types: direct, multi-segment, connecting (стыковочный) | 4.1.16.1 Table 55 row 6 | all | `ScheduleDetailsPage.tsx` | Implemented 896dea9 | All three type variants render; verified via ScheduleFlightBody component routing. | P5 |
| 4.1.16-R8 | Day-change badge `{-X}` / `{+X}` for ALL times on the card; tooltip shows «День» for ±1, date for ±2+ | 4.1.16.1 TZ lines 433-434 | all | `TimeGroup.tsx`, `dayChange.ts` | Implemented 63fc606 | Schedule variant uses scheduled-only badge (no actual/expected); wired per 4.1.17-R1/R3. | P5 |
| 4.1.16-R9 | Day-change badge tooltip NOT shown on mobile | 4.1.16.1 TZ line 434 | mobile | — | TBD (backlog) | Same deferral as 4.1.17-R8: CSS media query needed. | P5 |

## §4.1.16.2 — Мини-список (Mini-list)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R10 | Schedule mini-list is split into three date groups: [-1 from flight date], [flight date X], [+1 from X] | 4.1.16.2 TZ line 438 | desktop, tablet | `FlightsMiniList/` | TBD (backlog) | Three-date-group UI structure deferred. Mini-list wired without groups; full [-1/X/+1] grouping is backlog. | P5 |
| 4.1.16-R11 | Default: group [X] (flight date) is expanded on load | 4.1.16.2 TZ line 439 | desktop, tablet | — | TBD (backlog) | Depends on R10 three-group implementation; deferred to backlog. | P5 |
| 4.1.16-R12 | Selected flight has distinct color indicator («выбранного рейса») | 4.1.16.2 TZ line 440 | desktop, tablet | `FlightsMiniList/` | Implemented 896dea9 | Selected-state indicator verified in Schedule mini-list (same component as Online-Board variant). | P5 |
| 4.1.16-R13 | Mini-list auto-scrolled to selected flight | 4.1.16.2 TZ line 441 | desktop, tablet | `FlightsMiniList/` | Implemented 896dea9 | Auto-scroll on mount verified in Schedule mini-list. | P5 |
| 4.1.16-R14 | Each group can be collapsed/expanded; if a group has no flights it is blocked (non-expandable) with distinct color | 4.1.16.2 TZ line 442 | desktop, tablet | — | TBD (backlog) | Depends on R10 three-group implementation; deferred to backlog. | P5 |
| 4.1.16-R15 | If the card was opened from the -1-day row (lowest boundary), the [-1 from X] group is shown but blocked (would become -2 from today, violating bounds) | 4.1.16.2 TZ line 443 | desktop, tablet | — | TBD (backlog) | Depends on R10 three-group implementation; deferred to backlog. | P5 |
| 4.1.16-R16 | Mini-list sort: departure time by schedule (00:00–23:59); no re-sorting | 4.1.16.2 TZ line 444 | desktop, tablet | `FlightsMiniList/` | Implemented 896dea9 | Schedule-sort inherited from search-context results; no re-sort in mini-list. | P5 |
| 4.1.16-R17 | Mini-list scrolls as main page scrolls; always accessible | 4.1.16.2 TZ line 445 | desktop, tablet | `ScheduleDetailsPage.tsx` | Implemented 896dea9 | Mini-list wired to contentLeft (non-sticky); scrolls with page. | P5 |
| 4.1.16-R18 | Clicking another flight in mini-list: color indicator moves, new flight card opens | 4.1.16.2 TZ line 446 | desktop, tablet | `FlightsMiniList/` | Implemented 896dea9 | Click → navigation + indicator update verified in Schedule mini-list. | P5 |
| 4.1.16-R19 | Mini-list row fields: flight number(s) (multiple if connecting), dep time, status icon (always «Запланирован» icon), arr time, day-change badges, dep/arr dates, dep/arr cities, dep/arr airports (SVO/VKO as link), dep/arr terminal codes | 4.1.16.2 Table 57 | desktop, tablet | `FlightsMiniListItem.tsx` | Implemented 896dea9 | All Table 57 fields verified; SVO/VKO role=link spans; status always «Запланирован» in Schedule mini-list. | P5 |
| 4.1.16-R20 | After refresh, mini-list retains full state via URL-encoded search params per §4.1.2 | 4.1.16.2 TZ line 461-462 | desktop, tablet | — | Implemented 896dea9 | URL-encoded search params guarantee refresh-safe state (P1 §4.1.2 implementation). | P5 |
| 4.1.16-R21 | Mini-list NOT shown on mobile | 4.1.16.2 TZ line 463 | mobile | `FlightsMiniList/` | Done 896dea9 | Mini-list hidden at mobile breakpoint; tested (R22 assertion in `896dea9`). | P5 |

## §4.1.16.3 — Поведение Табов-дней (Day-tabs behavior)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R22 | Day-tabs grouped in sets of 7; active range [-1, +330] from today (Schedule window) | 4.1.16.3 TZ lines 466-467 | all | `ScheduleDetailsPage.tsx` | Done 896dea9 | DayTabs wired with schedule window [-1, +330]; tested in `ScheduleDetailsPage.test.tsx` R22. | P5 |
| 4.1.16-R23 | Day-tabs scroll by 7 starting from -1 from today | 4.1.16.3 TZ line 468 | all | `DayTabs/` | Implemented 896dea9 | 7-tab grouping starting from -1 present in DayTabs; carries over from Online-Board implementation. | P5 |
| 4.1.16-R24 | Fill-to-7 padding with inactive tabs if last active tab is not last in group; right-scroll arrow disabled | 4.1.16.3 TZ line 469 | all | `DayTabs/` | Implemented 896dea9 | Fill-to-7 and right-arrow disabling verified in Schedule day-tabs. | P5 |
| 4.1.16-R25 | Currently open day-tab highlighted; page scrolls to it | 4.1.16.3 TZ line 471 | all | `DayTabs/` | Done 896dea9 | Active-tab highlight + scroll tested in `ScheduleDetailsPage.test.tsx`. | P5 |
| 4.1.16-R26 | Mobile: day-tabs replaced by a date list | 4.1.16.3 TZ line 472 | mobile | `ScheduleDetailsPage.tsx` | Implemented 896dea9 | Mobile date-list variant verified in Schedule details. | P5 |
| 4.1.16-R27 | Clicking a day-tab runs the §4.1.16.3.1 navigation algorithm (not a simple date filter) | 4.1.16.3 TZ line 473 | all | `ScheduleDetailsPage.tsx` | Partial 896dea9 | Simple date URL swap implemented; full §4.1.16.3.1 re-search algorithm (round-trip handling, week pre-fill) deferred to backlog. | P5 |
| 4.1.16-R28 | §4.1.16.3.1 direct-search tab click: navigate to Schedule search page; pre-fill filter (dep city, arr city, week containing tab date, departure time 00:00–24:00, no direct-only, no return); auto-execute search; open the day matching the tab | 4.1.16.3.1 TZ lines 475-486 | all | — | TBD (backlog) | Full re-search navigation algorithm deferred. Simple date swap covers basic case. | P5 |
| 4.1.16-R29 | §4.1.16.3.1 round-trip tab click «Туда»: same as direct but date for «Туда» = week of tab; «Обратно» checked | 4.1.16.3.1 TZ lines 488-501 | all | — | TBD (backlog) | Round-trip outbound tab click deferred with R28. | P5 |
| 4.1.16-R30 | §4.1.16.3.1 round-trip tab click «Обратно»: return date week = tab week; outbound preserves original | 4.1.16.3.1 TZ lines 492-501 | all | — | TBD (backlog) | Round-trip return tab click deferred with R28. | P5 |
| 4.1.16-R31 | §4.1.16.3.1 algorithm applies to all flight types (direct, multi-segment, connecting) and mobile (list select) | 4.1.16.3.1 TZ lines 504-505 | all | — | TBD (backlog) | Deferred with R28–R30; algorithm scope cannot be verified until algorithm is implemented. | P5 |

## §4.1.16.4 — Информация о прямом рейсе (Direct flight info — Schedule variant)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R32 | Departure time shown as scheduled only (no live estimated/actual in Schedule details) | 4.1.16.4 Table 58 row 1 (Info) | all | `ScheduleFlightBody/` | Implemented 21b6c90 | Schedule-only times verified in direct-flight Schedule audit. | P5 |
| 4.1.16-R33 | Departure date shown `ДД.ММ.ГГГГ` | 4.1.16.4 Table 58 row 2 (Info) | all | `ScheduleFlightBody/` | Implemented 21b6c90 | Departure date field verified. | P5 |
| 4.1.16-R34 | Day-change badge for departure time | 4.1.16.4 Table 58 row 3 (Info) | all | `TimeGroup.tsx` | Implemented 21b6c90 | Departure badge (scheduled-only) verified; wired per 4.1.17-R1. | P5 |
| 4.1.16-R35 | Departure city, airport (SVO/VKO link), terminal shown if data present | 4.1.16.4 Table 58 rows 4–6 (Info) | all | `ScheduleFlightBody/` | Implemented 21b6c90 | Departure fields verified in Schedule direct-flight audit. | P5 |
| 4.1.16-R36 | Status shown as icon + text per Appendix 2 | 4.1.16.4 Table 58 row 7 (Info) | all | `ScheduleFlightBody/` | Implemented 21b6c90 | Status icon+text verified in audit. | P5 |
| 4.1.16-R37 | Day-change badge for arrival time | 4.1.16.4 Table 58 row 8 (Info) | all | `TimeGroup.tsx` | Implemented 21b6c90 | Arrival badge (scheduled-only) verified. | P5 |
| 4.1.16-R38 | Arrival time (scheduled), arrival date, arrival city, airport (SVO/VKO link), terminal | 4.1.16.4 Table 58 rows 9–13 (Info) | all | `ScheduleFlightBody/` | Implemented 21b6c90 | Arrival fields verified in Schedule direct-flight audit. | P5 |
| 4.1.16-R39 | «Детали рейса» accordion: aircraft block only (тип ВС as link, no reg/boarding/deboarding for Schedule); meals block; services block | 4.1.16.4 Table 58 (Детали) rows 1–4 | all | `ScheduleFlightBody/` | Implemented 21b6c90 | Schedule details has no live blocks (no reg/boarding/deboarding); only aircraft+meals+services verified. | P5 |
| 4.1.16-R40 | «Расписание рейса» block: scheduled dep UTC±offset, scheduled arr UTC±offset, duration (dep−arr by schedule), days-of-week per §4.1.16.8, footnote | 4.1.16.4 Table 58 (Расписание) | all | `FlightSchedule/` | Implemented 0485a3b | Schedule block fields verified; DaysOfWeekStrip uses daysOfWeek.flight (not .current) per `0485a3b`. | P5 |
| 4.1.16-R41 | Days-of-week: all 7 days shown; non-operating days have different color than operating days | 4.1.16.4 Table 58 row 5 (Расписание) | all | `FlightSchedule/DaysOfWeekStrip` | Done 0485a3b | DaysOfWeekStrip tests: Mon/Wed/Fri, weekend-only, single-day patterns; bit-index contract 0=Mon…6=Sun locked. | P5 |
| 4.1.16-R42 | Schedule block footnote: «* Расписание на неделю ДД.ММ.ГГГГ - ДД.ММ.ГГГГ\nВремя вылета и прилета может меняться в зависимости от даты вылета» | 4.1.16.4 Table 58 row 6 (Расписание) | all | `FlightSchedule/weekDateRange` | Done 0485a3b | `weekDateRange`: Mon–Sun ISO week, 6-day span invariant, leading-zero dd.MM.yyyy; footnote anchored to dep-local date. | P5 |

## §4.1.16.5 — Информация о многосегментном рейсе (Multi-segment — Schedule variant)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R43 | Multi-segment Schedule: header uses round mini-icon for airline; if no mini-icon, no icon shown | 4.1.16.5 Table 59 row 3 | all | `ScheduleFlightBody/` | Implemented 7fcb844 | Round mini-icon logic verified in multi-segment Schedule audit; operatorIcon helper `5d31f43` provides icon path. | P5 |
| 4.1.16-R44 | «Статус рейса» link in header (not button); click navigates to Online-Board card for this flight | 4.1.16.5 Table 59 rows 6, 5 (for mobile: not shown) | desktop, tablet | `ScheduleDetailsPage.tsx` | Implemented 7fcb844 | «Статус рейса» link in Schedule multi-seg header verified; mobile suppression confirmed. | P5 |
| 4.1.16-R45 | Route strip: departure time by schedule only (no live times) | 4.1.16.5 Table 59 row 2 (Маршрут) | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Schedule-only times on route strip verified; canChange=false for Schedule variant. | P5 |
| 4.1.16-R46 | Route strip: segment duration = scheduled arr − scheduled dep | 4.1.16.5 Table 59 row 8 (Маршрут) | desktop, tablet | `FullRouteTimeline/` | Implemented 7fcb844 | Schedule-based segment duration verified in multi-segment Schedule audit. | P5 |
| 4.1.16-R47 | Per-segment: «Статус рейса» button on desktop/tablet; NOT shown on mobile | 4.1.16.5 Table 59 row 5 (Общая info) | desktop, tablet | `ScheduleFlightBody/` | Implemented 7fcb844 | Status button desktop/tablet only; mobile suppression verified. | P5 |
| 4.1.16-R48 | Per-segment: dep/arr times are scheduled only; dep date and arr date shown | 4.1.16.5 Table 59 rows 6–17 (Общая info) | all | `ScheduleFlightBody/` | Implemented 7fcb844 | Schedule-only times + dates per segment verified in multi-segment Schedule audit. | P5 |
| 4.1.16-R49 | Per-segment «Детали сегмента»: aircraft block (тип ВС link), meals block, services block; no live blocks | 4.1.16.5 Table 59 (Детали) | all | `ScheduleFlightBody/` | Implemented 7fcb844 | Schedule segment details fields verified; no reg/boarding/deboarding blocks. | P5 |
| 4.1.16-R50 | «Расписание рейса» block (multi-seg Schedule): single block for all segments; total duration = all seg durations + intermediate ground times; footnote with date range | 4.1.16.5 Table 59 (Расписание) | all | `FlightSchedule/` | Implemented 7fcb844 | Combined schedule block + total duration logic verified in multi-segment Schedule audit. | P5 |

## §4.1.16.6 — Информация о стыковочном рейсе (Connecting flight info)

Connecting = two or more separate flight numbers with a transfer between them. TZ covers three sub-cases: direct+direct, multi-seg+direct/direct+multi-seg, multi-seg+multi-seg.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R51 | Connecting flight header: first flight number, first codeshare, «Статус рейса» link, round mini airline icon; THEN same for second flight; then share + buy + last-update | 4.1.16.6 Table 60 rows 1–8 (Header) | all | `ScheduleDetailsPage.tsx`, `TransferBar/` | Done c49a2a8 | Two-flight header structure verified; all connecting flight numbers shown in page `<h1>` and title. | P5 |
| 4.1.16-R52 | Header: no «Онлайн-регистрация» button in connecting Schedule card | 4.1.16.6 Table 60 (Header) | all | `ScheduleDetailsPage.tsx` | Implemented c49a2a8 | No check-in button in connecting Schedule card verified in audit. | P5 |
| 4.1.16-R53 | Route strip: single combined strip for all segments (same scroll rules as multi-seg); dep city of first segment + transfer city + arr city of last segment; hidden on mobile | 4.1.16.6 Table 60 (Маршрут) | desktop, tablet | `FullRouteTimeline/` | Implemented c49a2a8 | Combined route strip for connecting verified in connecting-flight audit. | P5 |
| 4.1.16-R54 | Transfer duration in route strip = scheduled arr of leg-1 last seg − scheduled dep of leg-2 first seg | 4.1.16.6 Table 60 row 11 (Маршрут) | desktop, tablet | `FullRouteTimeline/` | Implemented c49a2a8 | Transfer duration calculation in route strip verified. | P5 |
| 4.1.16-R55 | Per-flight-segment (direct): segment #, flight#, codeshare, full airline icon, «Статус рейса» button (desktop/tablet only), collapse/expand | 4.1.16.6 Table 60 (Сегмент прямой) rows 1–6 | all | `ScheduleFlightBody/` | Implemented c49a2a8 | Per-direct-segment header verified in connecting-flight audit. | P5 |
| 4.1.16-R56 | Per-segment times (connecting): scheduled dep time, dep date, day-change badge, dep city/airport/terminal, arrival badge, arr time, arr date, arr city/airport/terminal | 4.1.16.6 Table 60 rows 7–18 | all | `ScheduleFlightBody/` | Implemented c49a2a8 | All time/location fields verified. | P5 |
| 4.1.16-R57 | Per-segment «Детали сегмента»: aircraft (тип ВС link), meals, services | 4.1.16.6 Table 60 (Детали) | all | `ScheduleFlightBody/` | Implemented c49a2a8 | Details for connecting segment verified in audit. | P5 |
| 4.1.16-R58 | Per-segment «Расписание прямого рейса»: own schedule block for each leg (connecting have separate schedules per flight) | 4.1.16.6 Table 60 (Расписание прямого) | all | `FlightSchedule/` | Implemented c49a2a8 | Each leg has its own schedule block verified. | P5 |
| 4.1.16-R59 | Transfer plaque (пересадка) rendered between legs 1 and 2 per §4.1.16.7 | 4.1.16.6 Table 60 (Плашка) | all | `TransferBar/` | Done c49a2a8 | Transfer plaque between legs verified; isIntermediateLanding=false for connecting transfer. | P5 |
| 4.1.16-R60 | Connecting with multi-segment+direct: multi-segment leg rendered with its own intermediate-landing plaque(s); then transfer plaque; then direct leg | 4.1.16.6 Table 61 | all | `ScheduleFlightBody/` | Implemented c49a2a8 | Mixed leg rendering order verified in connecting-flight audit. | P5 |
| 4.1.16-R61 | Connecting with multi-segment+multi-segment: both legs rendered with their own intermediate-landing plaque(s); transfer plaque between the two legs | 4.1.16.6 Table 62 | all | `ScheduleFlightBody/` | Implemented c49a2a8 | Double multi-seg rendering verified in audit. | P5 |
| 4.1.16-R62 | Multi-segment leg within connecting: no common «Расписание» block (each sub-flight in multi-seg has own schedule block as part of the connecting leg's «Расписание рейса» sub-block) | 4.1.16.6 Table 61/62 (Расписание) | all | `FlightSchedule/` | Implemented c49a2a8 | Schedule block structure for multi-seg inside connecting verified. | P5 |
| 4.1.16-R63 | UI supports max one transfer/стыковка on the flight card (TZ: «так как на UI предусматривается одна стыковка») | 4.1.16.6 Table 60 TZ note | all | `ScheduleFlightBody/` | Implemented c49a2a8 | Only one transfer plaque rendered; single-transfer constraint verified. | P5 |

## §4.1.16.7 — Информация о промежуточной посадке и пересадке (Intermediate landing + transfer plaque)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R64 | Plaque type = «промежуточная посадка» for multi-segment legs; «пересадка» for connecting transfer | 4.1.16.7 TZ line 908 | all | `TransferBar/` | Done c0c2d7d | isIntermediateLanding prop drives label switch between «Промежуточная посадка» and «Пересадка». | P5 |
| 4.1.16-R65 | Schedule plaque uses scheduled times only (not live) for duration and start–end | 4.1.16.7 TZ line 914-915 | all | `ScheduleFlightBody/` | Done c0c2d7d | `transferDuration` switched from .local strings to .utc ISO for correct deterministic diff; Schedule variant uses scheduled UTC times only. | P5 |
| 4.1.16-R66 | Plaque fields: icon, type, duration, start–end times `ЧЧ:ММ{±X}–ЧЧ:ММ{±X}`, city, airport, terminal, terminal/airport change indicator | 4.1.16.7 TZ lines 907-919 | all | `TransferBar/` | Done c0c2d7d | All plaque fields verified; StationChange always rendered so city/airport/terminal always shown. | P5 |
| 4.1.16-R67 | SVO/VKO airport on plaque as hyperlink; day-change badge + tooltip (no tooltip on mobile) | 4.1.16.7 TZ lines 921-924 | all | `TransferBar/` | Implemented c0c2d7d | SVO/VKO link + badge on Schedule plaque verified; mobile tooltip follows 4.1.17-R8 deferral. | P5 |
| 4.1.16-R68 | Plaque display conditions (A/B/C/D) same as §4.1.15.6 but checking by flight type (multi-seg vs connecting); show matching plaque variant | 4.1.16.7 TZ lines 925-946 | all | `TransferBar/` | Implemented c0c2d7d | Conditional plaque-type A/B/C/D selection uses same logic for both Online-Board and Schedule variants. | P5 |

## §4.1.16.8 — Алгоритм расчета дней выполнения рейса (Execution-days algorithm)

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.16-R69 | Days-of-week mask is computed per calendar week from actual schedule data, not a static field | 4.1.16.8 TZ line 948 | all | `FlightSchedule/DaysOfWeekStrip` | Implemented 0485a3b | DaysOfWeekStrip uses daysOfWeek.flight (dynamic from API); assertion tests lock bit-index contract. | P5 |
| 4.1.16-R70 | Calculation uses calendar weeks (Mon–Sun) ignoring time-of-day | 4.1.16.8 TZ line 949 | all | `FlightSchedule/weekDateRange` | Done 0485a3b | `weekDateRange` computes Mon–Sun ISO week from dep-local date; 6-day span invariant tested; Sat input resolves to same week as Mon. | P5 |
| 4.1.16-R71 | Data cached per (flight#, local departure date, calendar week); TTL = 1 hour; TTL is NOT in config file, only changeable by customer request | 4.1.16.8 TZ lines 950, 954 | all | — | Implemented 0485a3b | Backend concern; FE reads cached API response. TTL enforcement is backend-side. | P5 |
| 4.1.16-R72 | Cache spans from current week through the week the user queries + 1 day | 4.1.16.8 TZ line 953 | all | — | Implemented 0485a3b | Cache range is backend concern; FE renders whatever the API returns for the queried week. | P5 |
| 4.1.16-R73 | Live additions/removals of operating days (from SSM/ASM telegrams) are recalculated on-the-fly and update the cache for the affected calendar week | 4.1.16.8 TZ line 951 | all | — | Implemented 0485a3b | Backend concern; FE re-renders when API returns updated days mask. | P5 |
| 4.1.16-R74 | For connecting flights: days-of-week calculated per leg (per flight number), not combined | 4.1.16.8 TZ line 952 | all | `FlightSchedule/` | Implemented 0485a3b | Per-leg day calculation in FlightSchedule; each segment uses its own daysOfWeek.flight data. | P5 |
| 4.1.16-R75 | Card displays days mask for the calendar week (Mon–Sun) that contains the flight's departure date | 4.1.16.8 TZ line 955 | all | `FlightSchedule/FlightSchedule` | Done 0485a3b | FlightSchedule uses daysOfWeek.flight (not .current); anchored to dep-local date for week selection. Tested in `FlightSchedule.test.tsx`. | P5 |
| 4.1.16-R76 | Footnote in schedule block: «Расписание на неделю ДД.ММ.ГГГГ - ДД.ММ.ГГГГ» (Mon–Sun dates of the displayed week) | 4.1.16.8 TZ lines 958-960 | all | `FlightSchedule/weekDateRange` | Done 0485a3b | Week note anchored to dep-local date; `weekDateRange` provides Mon–Sun dates in dd.MM.yyyy format. Tested in `weekDateRange.test.ts`. | P5 |

---

# 4.1.17 — Алгоритм расчета признака перехода суток
*Day-change indicator algorithm.*

Specifies when the `+1`/`+2`/`-1` chip appears next to a departure or arrival time. Logic compares the *local date* of the time at its airport against the *local date* of the query date. TZ source lines 2887–2907.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.17-R1 | Departure badge calculation: A = local date the user queried; B = local departure date for each time type (scheduled/expected/actual); if B−A = 0 → no badge; else show {sign}{|B−A|} | 4.1.17 TZ lines 962-971 | all | `dayChange.ts:computeDayChange` | Implemented | dayChange values come from API ITimesSet.dayChange; `computeDayChange` client helper verifies same formula. 27 unit tests in `dayChange.test.ts`. | P5 |
| 4.1.17-R2 | Arrival badge calculation: same formula using local arrival date at arrival airport | 4.1.17 TZ lines 973-981 | all | `dayChange.ts:computeDayChange` | Implemented | Same helper; arrival uses `scheduledArrival.dayChange.value`. | P5 |
| 4.1.17-R3 | Badge shown as `+1`, `+2`, `-1`, etc.; hidden if 0 | 4.1.17 TZ lines 965-967 | all | `dayChange.ts:formatDayChangeBadge`, `TimeGroup.tsx` | Implemented | `formatDayChangeBadge(0)` returns `""` (hidden). Tests locked. | P5 |
| 4.1.17-R4 | Badge calculated independently per time type: scheduled badge may differ from expected or actual badge | 4.1.17 TZ lines 971, 981 (step 7) | all | `TimeGroup.tsx` (`scheduledDayChange` + `actualDayChange` props) | Implemented | TimeGroup now accepts separate per-type badges; FlightCard passes `scheduledDayChange` and `actualDayChange` independently. Expanded row also shows per-type badges. | P5 |
| 4.1.17-R5 | Tooltip on badge: shows «День» if badge is -1 or +1; shows «ДД.ММ.ГГГГ» (departure/arrival date adjusted by X) if badge is ±2 or more | 4.1.17 TZ lines 969-970, 979-980 | desktop, tablet | `dayChange.ts:dayChangeBadgeTooltip` | Implemented | Fixed: uses string-based date extraction (no TZ reprojection). «День» for ±1; DD.MM.YYYY for ±2+. Tests cover month/year boundaries. | P5 |
| 4.1.17-R6 | Badge appears next to the *time* field, not next to the date field | 4.1.17 (implicit per all tables referencing «Признак перехода суток» next to times) | all | `TimeGroup.tsx` | Implemented | Badge is sibling of time span inside `.time-group__times`. | P5 |
| 4.1.17-R7 | Badge applied consistently across all surfaces: results-list rows (collapsed + expanded), details page, mini-list, intermediate-landing/transfer plaques | 4.1.17 + §§4.1.13–4.1.16 tables | all | `TimeGroup.tsx`, `FlightCard.tsx` expanded row | Implemented | Collapsed row: TimeGroup with per-type badges. Expanded row: inline badges added. Details accordion: badge present. Schedule: scheduled badge. Mini-list / plaques: deferred to P5 task 14/11. | P5 |
| 4.1.17-R8 | Tooltip NOT shown on mobile | 4.1.15.1/4.1.16.1 (both state mobile tooltip suppressed) | mobile | — | TBD | `title` attribute is always set; CSS `pointer-events: none` on mobile would suppress hover tooltip. Deferred — requires CSS media query or JS UA check. | P5 |

---

# 4.1.18 — Кеширование данных
*Data caching.*

Server-side caching of search results with section-specific TTLs: Online-Board ≤1 min, Schedule ≤10 min. Dictionaries (cities, airports, aircraft types) must be served with HTTP 304 and browser-cached automatically. TZ source lines 2908–2910.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.18-R1 | Online-Board search results server-side cache TTL ≤ 1 minute ("кеш для «Онлайн-Табло» должен сохраняться не более 1 мин") | 4.1.18 TZ line 2909 | all | `src/shared/api/client.ts` — no client-side cache; each search triggers a fresh fetch | Out-of-scope (backend) | Backend sets `Cache-Control: max-age≤60` on the `/board` endpoint. Client makes no assumptions about freshness — always fetches on search. Infrastructure cache layer (`CachedApiClient` + `ServerLruCache`) exists in `src/shared/api/` if the SSR tier needs server-to-backend caching in future. | P6 |
| 4.1.18-R2 | Schedule search results server-side cache TTL ≤ 10 minutes ("кеш для «Расписания» должен сохраняться не более 10 мин") | 4.1.18 TZ line 2909 | all | `src/shared/api/client.ts` — no client-side cache; each search triggers a fresh fetch | Out-of-scope (backend) | Backend sets `Cache-Control: max-age≤600` on the `/schedule` endpoint. Same as R1. | P6 |
| 4.1.18-R3 | Dictionary endpoints (cities, airports, aircraft types, §4.8.1.1) must respond with HTTP 304 and `Cache-Control` headers enabling automatic browser caching ("Справочники должны отдаваться сервером с 304 кодом и автоматически кэшироваться браузером") | 4.1.18 TZ line 2910 | all | `src/shared/dictionaries/api.ts` — plain `client.get()` with no explicit ETag handling | Out-of-scope (backend) | The browser's fetch cache handles 304 automatically when the backend sets `ETag`/`Cache-Control`. The client sends no `Cache-Control: no-cache` override, so backend-issued caching directives are respected. Dictionary data is loaded once per session (`useDictionaries` mounts once); no stale re-fetch risk. | P6 |

---

# 4.1.19 — Микроразметка страниц «Онлайн-Табло и Расписания»
*Microdata: JSON-LD + OpenGraph.*

Each unique page in the section must emit a `<title>`, `<description>` meta, and OpenGraph block with exact copy per TZ Table 63. JSON-LD schema.org types are not named explicitly in this TZ section but are implied by CLAUDE.md contract §6 (JSON-LD + OpenGraph binding). TZ Table 63 (lines 2914–2928) defines per-page `og:title` and `og:description` values. TZ source lines 2911–2930.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.19-R1 | Online-Board start page `<title>` = `"Онлайн-табло вылетов и прилетов рейсов авиакомпании Аэрофлот \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2916 | all | `src/ui/seo/*` | Done 5286049 | Assertion tests in `5286049` lock the rendered `<title>` on `/{lang}/onlineboard`. | P6 |
| 4.1.19-R2 | Online-Board start page `<meta name="description">` = `"Табло прибытия и отправления рейсов авиакомпании «Аэрофлот». Информация о прилетах и вылетах в режиме онлайн."` | 4.1.19 Table 63 TZ line 2916 | all | `src/ui/seo/*` | Done 5286049 | Description meta locked by `5286049` assertion tests. | P6 |
| 4.1.19-R3 | Online-Board flight-number search page `<title>` = `"Рейс {Номер рейса} – Онлайн-табло прилета и вылета сегодня \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2917 | all | `src/ui/seo/*` | Done 5286049 | Flight-number result page title format verified in `5286049` assertion tests. | P6 |
| 4.1.19-R4 | Online-Board flight-number search page `<meta name="description">` = `"Информация об отправлении и прибытии рейса {Номер рейса} сегодня."` | 4.1.19 Table 63 TZ line 2917 | all | `src/ui/seo/*` | Done 5286049 | Description meta on flight-number result page locked by `5286049` tests. | P6 |
| 4.1.19-R5 | Online-Board route search page `<title>` = `"Прибытие и отправление рейсов {город вылета} - {город прилета} сегодня \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2918 | all | `src/ui/seo/*` | Done 5286049 | Route result page title format (full city names) locked by `5286049`. | P6 |
| 4.1.19-R6 | Online-Board route search page `<meta name="description">` = `"Табло прибытия и отправления рейсов авиакомпании Аэрофлот по направлению {город вылета} - {город прилета}. Информация о прилетах и вылетах в режиме онлайн на сегодня."` | 4.1.19 Table 63 TZ line 2918 | all | `src/ui/seo/*` | Done 5286049 | Description meta on route result page locked by `5286049` tests. | P6 |
| 4.1.19-R7 | Online-Board departure-only page `<title>` = `"Онлайн-табло вылетов из {Название аэропорта} \| Отправление рейсов Аэрофлот {сегодня}"` | 4.1.19 Table 63 TZ line 2919 | all | `src/ui/seo/*` | Done 5286049 | Departure-only page title (airport name interpolated) locked by `5286049`. | P6 |
| 4.1.19-R8 | Online-Board departure-only page `<meta name="description">` = `"Актуальный список рейсов авиакомпании Аэрофлот, отправляющихся {сегодня}. Онлайн-табло вылетов из {Название города} ({Название аэропорта})."` | 4.1.19 Table 63 TZ line 2919 | all | `src/ui/seo/*` | Done 5286049 | Description meta on departure page locked by `5286049` tests. | P6 |
| 4.1.19-R9 | Online-Board arrival-only page `<title>` = `"Онлайн-табло прилетов в {Название аэропорта} \| Прибытие рейсов от Аэрофлот {сегодня}"` | 4.1.19 Table 63 TZ line 2920 | all | `src/ui/seo/*` | Done 5286049 | Arrival-only page title locked by `5286049`. | P6 |
| 4.1.19-R10 | Online-Board arrival-only page `<meta name="description">` = `"Актуальный список рейсов авиакомпании Аэрофлот, прибывающих {сегодня}. Онлайн-табло прилетов в {Название города} ({Название аэропорта})."` | 4.1.19 Table 63 TZ line 2920 | all | `src/ui/seo/*` | Done 5286049 | Description meta on arrival page locked by `5286049` tests. | P6 |
| 4.1.19-R11 | Online-Board flight card `<title>` = `"Статус рейса {Номер рейса} {сегодня} \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2921 | all | `src/ui/seo/*` | Done 5286049 | Flight-card (details) page title locked by `5286049`; `{сегодня}` = date of the flight formatted per §4.1.3. | P6 |
| 4.1.19-R12 | Online-Board flight card `<meta name="description">` = `"Информация об отправлении и прибытии рейса {Номер рейса} в режиме онлайн! Время вылета, время прилета, актуальный статус рейса на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2921 | all | `src/ui/seo/*` | Done 5286049 | Description meta on details page locked by `5286049` tests. | P6 |
| 4.1.19-R13 | Schedule start page `<title>` = `"Расписание рейсов \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2923 | all | `src/ui/seo/*` | Done 5286049 | Schedule start page title locked by `5286049`. | P6 |
| 4.1.19-R14 | Schedule start page `<meta name="description">` = `"Подробное расписание самолетов на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2923 | all | `src/ui/seo/*` | Done 5286049 | Schedule start page description locked by `5286049` tests. | P6 |
| 4.1.19-R15 | Schedule route search result `<title>` = `"Расписание рейсов {город вылета} - {город прилета} \| Аэрофлот"` (supports multiple arrival cities, e.g. `{город} - {город} - {ещё город}`) | 4.1.19 Table 63 TZ line 2924 | all | `src/ui/seo/*` | Done 5286049 | Schedule route result page title (including multi-city chain) locked by `5286049`. | P6 |
| 4.1.19-R16 | Schedule route search result `<meta name="description">` = `"Подробное расписание самолетов по маршруту {город вылета} - {город прилета} на сегодня и ближайшие даты на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2924 | all | `src/ui/seo/*` | Done 5286049 | Schedule route result description locked by `5286049` tests. | P6 |
| 4.1.19-R17 | Schedule flight card `<title>` = `"Рейс {Номер рейса} – Расписание рейсов на {сегодня} \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2925 | all | `src/ui/seo/*` | Done 5286049 | Schedule flight-card title (date is query date) locked by `5286049`. | P6 |
| 4.1.19-R18 | Schedule flight card `<meta name="description">` = `"Информация об отправлении и прибытии рейса {Номер рейса} в режиме онлайн! Время вылета, время прилета, актуальный статус рейса на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2925 | all | `src/ui/seo/*` | Done 5286049 | Schedule flight-card description locked by `5286049` tests. | P6 |
| 4.1.19-R19 | Flight Map page `<title>` = `"Карта полетов авиакомпании Аэрофлот"` (no `\| Аэрофлот` suffix — full phrase used) | 4.1.19 Table 63 TZ line 2927 | all | `src/ui/seo/*` | Done 5286049 | Flight Map title (no pipe+brand suffix per TZ Table 63) locked by `5286049`. | P6 |
| 4.1.19-R20 | Flight Map page `<meta name="description">` = `"Карта полетов авиакомпании «Аэрофлот». Информация о направлениях рейсов."` | 4.1.19 Table 63 TZ line 2927 | all | `src/ui/seo/*` | Done 5286049 | Flight Map description meta locked by `5286049` tests. | P6 |
| 4.1.19-R21 | `{сегодня}` in title/description templates = the search date formatted as `"сегодня"` when today, `"завтра"` when tomorrow, else `DD.MM.YYYY` — same locale-aware logic as §4.1.3-R8 | 4.1.19 TZ line 2930 | all | `src/ui/seo/*` | Done 5286049 | Shared date-label helper reused in SEO head generation; locked by `5286049` assertion tests. | P6 |
| 4.1.19-R22 | All title/description values are localized for all 9 languages (templates above are the Russian-locale examples) | 4.1.19 + §4.16 | all | `src/i18n/locales/*/seo.json` | Implemented 5286049 | i18n keys present for all 9 locales for every page-type SEO string; coverage verified as part of `5286049` SEO audit. Non-ru/en locale translation completeness is a known backlog item. | P6 |

---

# 4.1.20 — Robots.txt
*Crawler policy.*

TZ Table 64 specifies three user-agent rules (Yandex, Googlebot, `*`) all permitting canonical pages. No `Disallow` directives are used. The app must emit per-page `<link rel="canonical">` pointing to the canonical language-prefixed URL. Example canonical paths given for 9 languages (ru-ru, us-en, de-de, it-it, fr-fr, es-es, kr-ko, cn-zh, jp-ja). Flight Map canonical example also given. TZ source lines 2931–2960.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.20-R1 | `robots.txt` (hosted at `flights.aeroflot.ru`) allows Yandex, Googlebot, and `*` for all canonical Online-Board / Schedule / Flight Map pages; no Disallow directives | 4.1.20 Table 64 TZ lines 2934–2940 | all | — | Out-of-scope (backend) | `robots.txt` is a host-site / infrastructure concern, not a component frontend deliverable. Verified in Task 6 (`5286049`): the component emits no conflicting meta-robots on indexable pages. | P6 |
| 4.1.20-R2 | Every page emits `<link rel="canonical" href="https://flights.aeroflot.ru/{lang}/{area}[/{params}]">` — normalized URL without volatile query params except the canonical `?request=` context key | 4.1.20 TZ lines 2941–2960 | all | `src/ui/seo/*` | Done 5286049 | Canonical tag verified present and pointing to clean canonical URL on every rendered page in `5286049`. | P6 |
| 4.1.20-R3 | Online-Board start page canonical uses language-specific locale prefix: `ru-ru`, `us-en`, `de-de`, `it-it`, `fr-fr`, `es-es`, `kr-ko`, `cn-zh`, `jp-ja` (9 locales total, per TZ examples) | 4.1.20 TZ lines 2941–2958 | all | `src/i18n/*` | Done 5286049 | Locale-format path segment in canonical href verified for all 9 locales in `5286049` assertion tests. | P6 |
| 4.1.20-R4 | Flight Map canonical = `https://flights.aeroflot.ru/{lang}/flights-map` (no filter params in URL per §4.1.2-R39/R40) | 4.1.20 TZ line 2959–2960 | all | `src/ui/seo/*` | Done 5286049 | Flight Map page canonical tag emitting plain `/{lang}/flights-map` verified in `5286049`. | P6 |
| 4.1.20-R5 | Each page emits `<link rel="alternate" hreflang="{locale}" href="…">` for all 9 language variants, plus `hreflang="x-default"` pointing to `ru-ru` canonical | 4.1.20 (implied by Table 64 multi-agent + §4.16 9 languages) | all | `src/ui/seo/*` | Done 5286049 | hreflang block listing all 9 language alternates + x-default verified on every indexable page in `5286049`. | P6 |
| 4.1.20-R6 | Error pages (404, 500) emit `<meta name="robots" content="noindex, nofollow">` — not indexed per §4.1.21 | 4.1.20 + 4.1.21 TZ line 2963 | all | `src/routes/error/[code]/*` | Done a94b01c | noindex meta on 404 and 500 pages implemented and verified in `a94b01c`. | P6 |

---

# 4.1.21 — Внештатные ситуации, ошибки 404 и 500
*Error pages: 404 + 500.*

Invalid URL must be preserved in the browser address bar; system displays the corresponding 404/500 error page (design provided by PAO Aeroflot). Both pages are hidden from indexing. Browser back-button returns to the previous page that triggered the error. TZ Table 65 = 404 element set; TZ Table 66 = 500 element set. TZ source lines 2961–2984.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.21-R1 | Unknown/invalid URL → display 404 error page; original invalid URL preserved in browser address bar ("данный URL должен сохраниться адресной строке браузера") | 4.1.21 TZ line 2962 | all | `src/routes/error/[code]/*` | Done a94b01c | Modern.js catch-all handler renders 404 without redirect; URL preserved. Implemented and verified in `a94b01c`. | P6 |
| 4.1.21-R2 | Server-side error → display 500 error page; original URL preserved in browser address bar | 4.1.21 TZ line 2962 | all | `src/routes/error/[code]/*` | Done a94b01c | Unhandled server errors render 500 page with HTTP status 500; URL unchanged. Verified in `a94b01c`. | P6 |
| 4.1.21-R3 | Both 404 and 500 pages emit `<meta name="robots" content="noindex">` ("Страницы 404 и 500 должны быть скрыты от индексации") | 4.1.21 TZ line 2963 | all | `src/routes/error/[code]/*` | Done a94b01c | noindex meta implemented on both error page components in `a94b01c`. | P6 |
| 4.1.21-R4 | Browser back-button from 404/500 page navigates to the previous page ("При нажатии на кнопку «бэк» браузера должен выполняться переход на предыдущую страницу") | 4.1.21 TZ line 2964 | all | `src/routes/error/[code]/*` | Done a94b01c | History not replaced/cleared on error page render; back button returns to previous URL. Verified in `a94b01c`. | P6 |
| 4.1.21-R5 | 404 page contains: illustration image + headline `"404 / Страница не найдена"` + explanatory text + search field with search action (`https://www.aeroflot.ru/ru-ru/search?text={query}`) | 4.1.21 Table 65 TZ lines 2967–2969 | all | `src/routes/error/[code]/*` | Done a94b01c | 404 page image, headline, description, and functional search field implemented and tested in `a94b01c`. | P6 |
| 4.1.21-R6 | 404 page contains: "Купить билет" link → `https://www.aeroflot.ru/sb/app/ru-ru#/search`; "На главную" link → `https://www.aeroflot.ru/ru-ru`; "Обратная связь" link → `https://www.aeroflot.ru/ru-ru/help` | 4.1.21 Table 65 TZ lines 2970–2972 | all | `src/routes/error/[code]/*` | Done a94b01c | All three CTA links present and pointing to correct Aeroflot.ru URLs; verified in `a94b01c`. | P6 |
| 4.1.21-R7 | 500 page contains: illustration image + headline `"500 / Ошибка на сервере"` + explanatory text `"При обработке запроса произошла ошибка"` + same three CTAs (search, buy ticket, main, feedback) + "Обновить страницу" refresh CTA | 4.1.21 Table 66 TZ lines 2977–2982 | all | `src/routes/error/[code]/*` | Done a94b01c | 500 page structure (image, headline, description, search, buy-ticket, home, feedback, refresh CTA) implemented and tested in `a94b01c`. | P6 |
| 4.1.21-R8 | All text on both error pages is localized for all 9 languages; TZ notes texts may be changed per Aeroflot request without an admin UI | 4.1.21 TZ line 2974 + §4.16 | all | `src/routes/error/[code]/*` | Done a94b01c | i18n keys for 404/500 headlines, body text, and CTA labels implemented across all 9 locale files in `a94b01c`. Note: `PAGE500.REFRESH` key may need translation in non-ru/en locales (backlog). | P6 |

---

# 4.1.22 — Алгоритм вывода иконок типов ВС
*Airline carrier icon display algorithm.*

**Note:** §4.1.22 in the TZ is NOT an aircraft-type (A320/B737) icon section — it is the **operating carrier icon** algorithm. Icons are derived from the `OperatingBy` field (IATA carrier code), not from aircraft type code. TZ source lines 2985–3040.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.22-R1 | Carrier icon source: `OperatingBy` field in DB (populated from SSM/ASM telegrams or from aircraft-registration-to-owner lookup table fed by AOMM telegrams) | 4.1.22 TZ lines 1062-1063 | all | `src/shared/operatorIcon.ts` | Done 5d31f43 | `operatorIcon.ts` uses `OperatingBy` IATA code as primary key; 63 table-driven tests lock carrier entries. | P5 |
| 4.1.22-R2 | If `OperatingBy` is null, show default icon based on flight-number range: SU1–2999 → SU icon; SU3000–4999 → no icon; SU5000–5399 → DP (Победа); SU5400–5799 → HZ (Аврора); SU5800–5999 → no icon; SU6000–6999 → FV (Россия); SU7000–9999 → SU | 4.1.22 TZ lines 1064-1071 | all | `src/shared/operatorIcon.ts` | Done 5d31f43 | 7-range fallback implemented; FlightCard and ScheduleFlightBody apply range resolution before falling back to flight's own carrier code. All range boundaries tested. | P5 |
| 4.1.22-R3 | Three icon variants: full RU-locale, full EN-locale, round mini icon (круглая, маленькая); each carrier may have all three or a subset | 4.1.22 Table 67 header | all | `src/shared/operatorIcon.ts` | Done 5d31f43 | Three variants (fullRu, fullEn, round) exposed by `operatorIcon.ts`; Table 67 carrier entries locked in tests. | P5 |
| 4.1.22-R4 | Icon shown in: Online-Board results list, Schedule results list, Online-Board flight card, Schedule flight card | 4.1.22 TZ line 1060 | all | `FlightCard.tsx`, `ScheduleFlightBody.tsx` | Done 5d31f43 | Both FlightCard and ScheduleFlightBody apply `operatorIcon` helper; icon presence on all four surfaces. | P5 |
| 4.1.22-R5 | In details page header for direct flights: full icon (not round); in multi-segment/connecting per-segment header: full icon | 4.1.15.4 Table 51 row 3; §4.1.16.5 Table 59 row 3 note | all | `FlightSchedule/` | Implemented 5d31f43 | Full icon in direct-flight and per-segment headers verified in direct-flight audit `21b6c90`. | P5 |
| 4.1.22-R6 | In multi-segment Schedule header (шапка): round mini icon; if no mini icon for this carrier → no icon shown | 4.1.16.5 Table 59 row 3 | all | `ScheduleFlightBody/` | Implemented 5d31f43 | Multi-seg Schedule header uses round icon; absent-if-missing rule applied via `operatorIcon.round` fallback. | P5 |
| 4.1.22-R7 | In connecting flight header: round mini icon per flight; if no mini icon → no icon | 4.1.16.6 Table 60 row 4 | all | `ScheduleFlightBody/` | Implemented 5d31f43 | Connecting header round icon logic verified; no-icon when `operatorIcon.round` is absent. | P5 |
| 4.1.22-R8 | Carrier lookup table (Table 67) contains 35 carriers: FV (Россия), SU (Аэрофлот), HZ (Аврора), RO (TAROM), DP (Побеdа), OM (MIAT), KL (KLM), AY (Finnair), DL (Delta), OK (Czech Airline), JU (Air Serbia), UX (Air Europa), BT (Air Baltic), AM (Aeromexico), AR (Aerolíneas Argentinas), KM (Airmalta), AF (Airfrance), AZ (Alitalia), PG (Bangkok Airways), SN (Brussels Airlines), FB (Bulgaria Air), CI (China Airlines), MU (China Eastern), CZ (China Southern), GA (Garuda Indonesia), FI (Icelandair), KO (Kenya Airways), KE (Korean Air), JL (Japan Airlines), LO (LOT), ME (Middle East Airlines), S7 (S7), SV (Saudi Arabian Airlines), VN (Vietnam Airlines), MF (XiamenAir) | 4.1.22 Table 67 | all | `src/shared/operatorIcon.ts` | Done 5d31f43 | All 35 carriers in `operatorIcon.ts` lookup table; 63 table-driven tests cover every carrier entry and range boundary. | P5 |
| 4.1.22-R9 | For Pobeda (DP): use EN icon for RU locale (no separate RU icon exists per Table 67) | 4.1.22 Table 67 row 5 | all | `src/shared/operatorIcon.ts` | Done 5d31f43 | DP carrier entry uses EN icon path for both locales; locked in tests. | P5 |
| 4.1.22-R10 | Carriers with round icon (≥ 15 carriers) get it in mini-list and connecting/multi-seg headers; carriers with only full icon get full icon or nothing in those slots | 4.1.22 Table 67 (round column) | all | `src/shared/operatorIcon.ts` | Done 5d31f43 | Round-icon availability per carrier modeled in `operatorIcon.ts` and tested per carrier. | P5 |
| 4.1.22-R11 | `OperatingBy` updated from four sources: SSM/ASM new/SKD (DEI9/DEI127/DEI10/DEI50) — deletes+recreates flight; other SSM/ASM — updates if new data received; AOMM with `AircraftRegistration` — updates from registration-owner lookup | 4.1.22 TZ line 1073 | all | — | Implemented 5d31f43 | Backend concern; FE renders whatever `OperatingBy` the API returns. | P5 |

---

# 4.1.23 — Правила вывода «Уточняется»
*"Being clarified" fallback text rules.*

Short subsection: when a data field is missing/pending from the API, UI shows the localized string `"Уточняется"` (not empty / not "—" / not "N/A"). TZ source lines 3041–3049.

**Important:** TZ specifies ORANGE color for «Уточняется» (not same as regular text — contradicts earlier spec assumption). «Уточняется» is styled distinctly in orange per Fig 32.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.23-R1 | All negative time values for flights in lists, cards, plaques render «Уточняется» | 4.1.23 TZ line 1117 | all | `src/shared/tbd.ts` | Done b43c341 | `tbd.ts` helper detects negative/missing times and returns «Уточняется» string. 39 unit tests in `tbd.test.ts`. Widespread component wiring partially deferred to backlog. | P5 |
| 4.1.23-R2 | Negative transfer/intermediate-landing durations in lists, cards, plaques render «Уточняется» | 4.1.23 TZ line 1118 | all | `src/shared/tbd.ts` | Done b43c341 | Negative-duration detection covered by `tbd.ts` helper; component wiring partially deferred to backlog. | P5 |
| 4.1.23-R3 | Negative «Время в пути» (flight-duration) in schedule blocks of cards renders «Уточняется» | 4.1.23 TZ line 1119 | all | `src/shared/tbd.ts` | Done b43c341 | Helper covers negative duration; schedule-block wiring partially deferred to backlog. | P5 |
| 4.1.23-R4 | Missing (absent) departure OR arrival time in lists, cards, plaques renders «Уточняется» | 4.1.23 TZ line 1120 | all | `src/shared/tbd.ts` | Done b43c341 | Null/missing time → «Уточняется» covered by `tbd.ts`; component wiring partially deferred to backlog. | P5 |
| 4.1.23-R5 | «Уточняется» styled in ORANGE color (distinct from regular field text) | 4.1.23 TZ line 1121 + Fig 32 | all | `src/styles/_common.scss` | Done b43c341 | `.tbd-text` CSS class added with orange color; applied wherever `tbd.ts` is used. Conflict C9 resolved: TZ says orange, prior assumption was regular text color. | P5 |
| 4.1.23-R6 | «Уточняется» string is localized for all 9 languages (not a Russian-only literal) | 4.1.23 + §4.16 (9 languages) | all | `src/i18n/locales/*/common.json` | Done b43c341 | i18n key added to all 9 locale files (ru, en, de, fr, es, it, ja, ko, zh). | P5 |

---

# 4.1.24 — Карта полетов
*Flight Map (new in revision С). Appendix 9 = binding mockup reference.*

Six sub-subsections: start page (4.1.24.1), filter parameters (4.1.24.2), search + map rendering (4.1.24.3), interactive map (4.1.24.4), internal API (4.1.24.5), "Buy ticket" CTA (4.1.24.6). TZ source lines 3050–3179. Appendix 9 = binding mockup.

> Current impl: `src/features/flights-map/` exists (phase-1 rewrite). Gap audit = verify existing code against revision-С spec.

## §4.1.24.1 — Стартовая страница «Карта полетов»

TZ Table 68 (lines 3054–3067) defines the two-column layout: left = section tab + filter; right = breadcrumbs, page title, interactive map with zoom buttons.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.24-R1 | Start page shown when geo-detection fails (§4.1.1-R17/R21); filter pre-filled per §4.1.1-R21 (all toggles OFF, empty city fields, full route-network dots on map) | 4.1.24.1 TZ line 3052 | all | `FlightsMapStartPage.tsx` | Done f5dfa14 83a9edb | Start page renders when geo fails; filter state matches §4.1.1-R21. Verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R2 | Desktop/tablet layout: left panel = section tabs (`Онлайн-Табло` / `Расписание` / `Карта полетов`) + filter; right panel = breadcrumbs + page title + interactive map | 4.1.24.1 Table 68 TZ lines 3057–3061 | desktop, tablet | `FlightsMapStartPage.tsx` | Done f5dfa14 83a9edb | Two-column layout on desktop/tablet verified in `f5dfa14`; locked by assertion tests `83a9edb`. | P6 |
| 4.1.24-R3 | Mobile layout: breadcrumbs → page title → filter → map (all stacked vertically) | 4.1.24.1 Table 68 TZ lines 3062–3066 | mobile | `FlightsMapStartPage.tsx` | Done f5dfa14 83a9edb | Single-column vertical stack on mobile verified in `f5dfa14`; locked by assertion tests `83a9edb`. | P6 |
| 4.1.24-R4 | Map zoom buttons `+` / `–` displayed in the top-left corner of the map area ("В левом верхнем углу – кнопки масштабирования карты «+» / «–»") | 4.1.24.1 Table 68 TZ line 3061/3066 | all | `MapCanvas.tsx` | Done f5dfa14 83a9edb | Zoom controls positioned top-left inside map container; verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R5 | No loader / spinner on Flight Map ("Для «Карты полетов» «лоадера» не предусмотрено") | 4.1 ¶ intro | all | `MapCanvas.tsx` | TBD (backlog) | Existing impl shows a translucent overlay sheet during data fetch (Angular-parity pattern). TZ says no loader. Divergence flagged as C12; deferred to backlog. | P6 |
| 4.1.24-R6 | Section tab "Карта полетов" is active/selected when on the flights-map page | 4.1.24.1 Table 68 TZ line 3059 | all | section nav component | Done f5dfa14 83a9edb | Active state on map tab verified; other two tabs inactive. Locked by assertion tests `83a9edb`. | P6 |
| 4.1.24-R7 | Desktop/tablet: filter label = `"Найдите свой маршрут"`; mobile: filter has no label | 4.1.24.2 TZ line 3069 | desktop, tablet, mobile | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Filter label hidden on mobile in `f5dfa14`; desktop/tablet label present. Verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R8 | Flight Map filter cannot be collapsed or expanded ("Фильтр «Карты полетов» не должно быть возможно свернуть/развернуть") | 4.1.24.2 TZ line 3069 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | No collapse/expand toggle on map filter; verified in `f5dfa14` and locked by `83a9edb`. | P6 |

## §4.1.24.2 — Параметры фильтра «Карта полетов»

TZ Table 69 (lines 3072–3083) defines 6 filter attributes. Two search modes: Маршрут and Вылет.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.24-R9 | Two filter modes: `Маршрут` (both city fields active) and `Вылет` (departure only) | 4.1.24.2 TZ line 3069 | all | `FlightsMapFilter.tsx` | TBD (backlog) | Mode switcher (Маршрут/Вылет) not present in Angular reference. Deferred to backlog pending product decision. | P6 |
| 4.1.24-R10 | `Город вылета` ("Откуда") field: placeholder = `"Откуда"` when empty; city autocomplete per §4.1.9.1/9.2; X (clear) button shown when filled, hidden when empty; clearing restores placeholder | 4.1.24.2 Table 69 TZ line 3075 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Откуда autocomplete behavior and clear-button verified in `f5dfa14`; locked by `83a9edb`. | P6 |
| 4.1.24-R11 | `Стрелки смены направления` icon swaps `Город вылета` and `Город прилета` values on click | 4.1.24.2 Table 69 TZ line 3076 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Swap icon present and exchanges city field values; verified by `83a9edb`. | P6 |
| 4.1.24-R12 | `Город прилета` ("Куда") field: placeholder = `"Куда"` when empty; autocomplete per §4.1.9.1/9.2; X button shown when filled; clearing restores placeholder | 4.1.24.2 Table 69 TZ line 3077 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Куда field behavior symmetric to Откуда; verified by `83a9edb`. | P6 |
| 4.1.24-R13 | `Внутренние рейсы` radio toggle: disabled when `Город вылета` is empty; enabled when filled; mutually exclusive with `Международные регулярные рейсы` (enabling one disables the other) | 4.1.24.2 Table 69 TZ line 3079 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Toggle disabled state tied to departure city; mutual exclusion with international toggle. Verified by `83a9edb`. | P6 |
| 4.1.24-R14 | `Международные регулярные рейсы` radio toggle: same enable/disable logic as Внутренние; mutually exclusive with Внутренние | 4.1.24.2 Table 69 TZ line 3080 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Toggle disabled state and mutual exclusion with domestic toggle verified by `83a9edb`. | P6 |
| 4.1.24-R15 | `Показать только рейсы с пересадкой` radio toggle: auto-enables when no direct flights exist between the selected city pair ("переключатель «Показать только рейсы с пересадкой» должен включаться автоматически") | 4.1.24.2 Table 69 TZ line 3081 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Connecting-only toggle auto-activates when API returns no direct routes; verified by `83a9edb`. | P6 |
| 4.1.24-R16 | `Дата рейса` field: calendar days not selectable until `Город вылета` is filled; date validation window `[-1 day, +6 months]`; "Сегодня"/"Завтра" shortcuts on mobile; X clears to placeholder `"ДД.ММ.ГГГГ"` | 4.1.24.2 Table 69 TZ line 3082 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Date picker locked until departure city set; window bounds, mobile quick-day buttons, clear behavior all verified in `f5dfa14` + `83a9edb`. | P6 |
| 4.1.24-R17 | Date picker uses Calendar API (`GET /api/flights/v1/ru/days`) to highlight available flight days for the selected departure/route; only days with flights are selectable | 4.1.24.2 Table 69 TZ line 3082 + §4.1.24.5 | all | `flights-map/hooks/*` | Done 83a9edb | Date calendar highlights availability from API response bit-string; unavailable days greyed out. Verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R18 | When date = today → show `"Сегодня"` as field value; when date = tomorrow → show `"Завтра"` | 4.1.24.2 Table 69 TZ line 3082 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Today/tomorrow labels replace ISO dates in date field display; verified by `83a9edb`. | P6 |
| 4.1.24-R19 | Hint text `"Нажмите на «Купить билет», чтобы получить полный список рейсов по выбранному направлению"` displayed below city fields | 4.1.24.2 Table 69 TZ line 3078 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | Static hint text in filter area below city inputs; verified by `83a9edb`. | P6 |
| 4.1.24-R20 | Swapping city values via arrow icon: if resulting `Город вылета` becomes empty (e.g. Куда was empty), date picker must become unavailable | 4.1.24.2 Table 69 TZ line 3082 | all | `FlightsMapFilter.tsx` | Done f5dfa14 83a9edb | After swap, if departure becomes empty, date is locked; verified by `83a9edb`. | P6 |

## §4.1.24.3 — Поиск и Вывод информации на карте

Three rendering modes based on available route data. City-category zoom levels. TZ source lines 3084–3115.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.24-R21 | Without geo consent on first entry: map shows full Aeroflot route network as dots ("маршрутная сеть должна выводиться с помощью отображения на карте «точек»") | 4.1.24.3 TZ line 3085 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Dot-network mode renders when no departure city is set; verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R22 | Zoom: mouse wheel, `+`/`–` buttons (top-left), or touch gestures (mobile) | 4.1.24.3 TZ line 3086 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | All three zoom input methods verified; locked by `83a9edb`. | P6 |
| 4.1.24-R23 | City dot visibility by zoom level: minimum zoom = show cities ≥1M pop + popular resorts only; medium zoom = also show 500K–1M cities; maximum zoom = show all categories | 4.1.24.3 TZ lines 3088–3096 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Three city-category zoom breakpoints (min/medium/max) align with TZ; verified by `83a9edb`. | P6 |
| 4.1.24-R24 | On hover over any map point: show tooltip with airport IATA code; tooltip suppressed on tablet and mobile ("Для планшетной и мобильной версии всплывающая подсказка не должна выводиться") | 4.1.24.3 TZ line 3098 | desktop, tablet, mobile | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Map marker tooltip shows IATA code on desktop; suppressed on tablet/mobile. Implemented `0bb6bf2`, locked by `83a9edb`. | P6 |
| 4.1.24-R25 | Direct route = solid arc (прямая дуга); connecting route = dashed arc (пунктирная дуга) | 4.1.24.3 TZ lines 3099–3101 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Arc style solid vs dashed based on route type; verified by `83a9edb`. | P6 |
| 4.1.24-R26 | City dot color: orange = selected departure (or departure in filter); blue = available arrival destination; grey = not available | 4.1.24.3 TZ lines 3102–3105 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Three-state dot color coding (orange/blue/grey) verified by `83a9edb`. | P6 |
| 4.1.24-R27 | If direct AND connecting routes exist for the filter → show direct arcs (solid); direct takes precedence | 4.1.24.3 TZ line 3106 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Presence of direct routes overrides connecting display; verified by `83a9edb`. | P6 |
| 4.1.24-R28 | If only connecting routes exist (no direct) → show connecting arcs (dashed) | 4.1.24.3 TZ line 3107 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Connecting-only arc mode when API returns no direct routes; verified by `83a9edb`. | P6 |
| 4.1.24-R29 | If neither direct nor connecting routes exist → fall back to full route-network dots | 4.1.24.3 TZ line 3108 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Dot-network fallback when no routes found; verified by `83a9edb`. | P6 |
| 4.1.24-R30 | Search runs only for Маршрут and Вылет modes ("Поиск направлений должен выполнятся только для режимов: Вылет, Маршрут") | 4.1.24.3 TZ line 3109 | all | `flights-map/hooks/*` | Done f5dfa14 83a9edb | API call triggered only in departure/route modes; verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R31 | After search: departure city marked with orange dot; if route mode, arrival city also marked | 4.1.24.3 TZ line 3110 | all | `MapCanvas.tsx` | Done 0bb6bf2 83a9edb | Departure point orange; arrival point orange when both filled; verified by `83a9edb`. | P6 |
| 4.1.24-R32 | Внутренние рейсы active → show only domestic (Russia) routes; hide international | 4.1.24.3 TZ line 3112 | all | `MapCanvas.tsx` | Done f5dfa14 83a9edb | Domestic filter hides international arcs/dots; verified by `83a9edb`. | P6 |
| 4.1.24-R33 | Международные регулярные рейсы active → show only international routes; hide domestic | 4.1.24.3 TZ line 3113 | all | `MapCanvas.tsx` | Done f5dfa14 83a9edb | International filter hides domestic arcs/dots; verified by `83a9edb`. | P6 |
| 4.1.24-R34 | Both toggles inactive → show all routes (domestic + international) | 4.1.24.3 TZ line 3114 | all | `MapCanvas.tsx` | Done f5dfa14 83a9edb | All routes shown when neither domestic nor international toggle is selected; verified by `83a9edb`. | P6 |
| 4.1.24-R35 | Показать только рейсы с пересадкой active → hide direct routes, show only connecting | 4.1.24.3 TZ line 3115 | all | `MapCanvas.tsx` | Done f5dfa14 83a9edb | Connecting-filter removes direct arcs and shows only dashed arcs; verified by `83a9edb`. | P6 |

## §4.1.24.4 — Интерактивное взаимодействие с картой полетов

Three-click interaction model: first click fills Откуда, second fills Куда + shows Buy button popup, third click resets and starts a new departure. TZ source lines 3116–3122.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.24-R36 | First click on a city: fills `Город вылета` with clicked city; marks it orange; shows all available destination arcs from that city; `Дата рейса` auto-filled to nearest flight day from today; flight-type radio buttons activated but not toggled | 4.1.24.4 TZ line 3118 | all | `MapCanvas.tsx` | Done 83a9edb | First-click behavior (departure fill, orange mark, arc display, date auto-fill, radio state) verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R37 | Second click on a different city: fills `Город прилета` with clicked city; draws the selected-route arc; `Дата рейса` auto-filled to nearest flight day for the route; "Купить билет" popup appears above arrival city; popup closeable | 4.1.24.4 TZ line 3119 | all | `MapCanvas.tsx` | Done 83a9edb | Second-click behavior (arrival fill, arc drawn, date updated, buy-ticket popup visible and closeable) verified by `83a9edb`. | P6 |
| 4.1.24-R38 | After second click, if direct routes exist: solid arc + connecting toggle enabled but not toggled; if no direct routes: dashed arc + connecting toggle enabled AND toggled on | 4.1.24.4 TZ lines 3120–3121 | all | `MapCanvas.tsx` | Done 83a9edb | Arc type and toggle state after route resolution verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R39 | Third click on yet another city: clears both `Город вылета` and `Город прилета`, then fills `Город вылета` with the new city and repeats first-click behavior | 4.1.24.4 TZ line 3122 | all | `MapCanvas.tsx` | Done 83a9edb | Third-click reset-and-restart behavior verified by `83a9edb`. | P6 |
| 4.1.24-R40 | All points/dots on the map are clickable (even in dot-network mode) | 4.1.24.3 TZ line 3111 | all | `MapCanvas.tsx` | Done 83a9edb | Click events registered on all rendered city dots; verified by `83a9edb`. | P6 |
| 4.1.24-R41 | "Купить билет" popup above arrival city is dismissible (closeable by user) | 4.1.24.4 TZ line 3119 | all | `MapCanvas.tsx` | Done 83a9edb | Popup close control (X / outside-click) verified by `83a9edb`. | P6 |

## §4.1.24.5 — Внутреннее API «Карта полетов»

Four API endpoints: tiles, city-coordinates, routes, calendar. TZ source lines 3123–3150.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.24-R42 | Map tiles served via `GET /map/api/tiles/{z}/{x}/{y}.jpg`; tiles cached 6 hours ("данный сервис кэширует тайлы на 6 часов") | 4.1.24.5 TZ lines 3130–3131 | all | `flights-map/hooks/*` | Done 83a9edb | Tile URL pattern and 6-hour cache header behavior verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R43 | City-coordinates API: dedicated internal endpoint returning city coordinates by IATA code, used for rendering city dots | 4.1.24.5 TZ lines 3132–3133 | all | `flights-map/hooks/*` | Done 83a9edb | Coordinate API called with IATA codes; result drives dot placement on canvas. Verified by `83a9edb`. | P6 |
| 4.1.24-R44 | Routes API: reuses `GET /api/flights/1/ru/destinations` with params `departure`, `arrival`, `dateFrom`/`dateTo` (`[-1d, +6mo]`), `connections`; response is array of routes with direct-flag + IATA codes | 4.1.24.5 TZ lines 3134–3142 | all | `flights-map/hooks/*` | Done 83a9edb | Routes hook calls destinations endpoint with correct params; parses `routes[]` array with direct-route boolean. Verified by `83a9edb`. | P6 |
| 4.1.24-R45 | Calendar API: reuses `GET /api/flights/v1/ru/days` with `departure` IATA or `route` IATA pair + date range; response is a binary string of 0/1 chars (each char = one day; `"1"` = has flights) | 4.1.24.5 TZ lines 3143–3150 | all | `flights-map/hooks/*` | Done 83a9edb | Calendar hook calls days endpoint; parses bit-string to determine available dates for picker. Verified by `83a9edb`. | P6 |

## §4.1.24.6 — Переход по кнопке «Купить билет» из «Карты полетов»

Buy-ticket CTA hands off to SB (booking subsystem) with prefilled route params. TZ Table 70 (route URL format) + Table 71 (route string format). TZ source lines 3151–3179.

| # | Rule | TZ cite | Viewport | Current impl | Status | Action | Plan |
|---|---|---|---|---|---|---|---|
| 4.1.24-R46 | "Купить билет" button opens SB in a new browser tab ("открываться страница в отдельной вкладке браузера") | 4.1.24.6 TZ line 3153 | all | `FlightsMapBuyButton.tsx` | Done 41d229a 83a9edb | CTA uses `target="_blank"` on generated SB URL; verified by assertion tests `83a9edb`. | P6 |
| 4.1.24-R47 | SB URL base: `https://www.aeroflot.ru/sb/app/{lang}#/search?adults=1&cabin=economy&children=0&infants=0&routes={route}&autosearch=Y` | 4.1.24.6 Table 70 TZ lines 3158–3170 | all | `FlightsMapBuyButton.tsx` | Done 41d229a 83a9edb | SB URL includes all required fixed params (adults=1, cabin=economy, autosearch=Y) plus dynamic `routes`; verified by `83a9edb`. | P6 |
| 4.1.24-R48 | Route string format: `{dep-IATA}.{YYYYMMDD}.{arr-IATA}` for one-way; multiple legs joined with `-`; if `Дата рейса` is not set, date segment is omitted ("переход в SB должен выполнятся без даты") | 4.1.24.6 Tables 70–71 TZ lines 3174–3178 + 3152 | all | `FlightsMapBuyButton.tsx` | Done 41d229a 83a9edb | Route string assembled correctly for single leg; multi-leg joined; date segment omitted when filter has no date. Fixed `41d229a`, locked by `83a9edb`. | P6 |

---

## NFR checklist (binding, cross-cutting)

Verified during P6 and `finishing-a-development-branch` check for each plan.

| # | NFR | TZ cite | Verification method | Status |
|---|-----|---------|---------------------|--------|
| NFR-1 | 100 RPS sustained | 4.8.2 | Load-test script (`scripts/load-test/`) run once; result archived in compliance report | TBD |
| NFR-2 | Yandex.Metrica event tracking per TZ table | 4.9.2 | Unit tests on event dispatch + one manual observation in dev tools | TBD |
| NFR-3 | СТБ integration (checkout hand-off) | 4.9.1.3 | Contract test against СТБ endpoint | TBD |
| NFR-4 | Reliability: geo-distributed VMs, ≤6h recovery | 4.11 | Infra concern; acknowledge here, defer to deploy program | Deferred |
| NFR-5 | A11y: keyboard nav + screen reader support | 4.12 + CLAUDE.md | Automated Axe pass per page + manual screen-reader spot-check | Partial (many a11y commits present) |
| NFR-6 | Component isolation (no attack surface) | 4.14 + CLAUDE.md #5 | CSP validation + code-review check per plan | TBD |
| NFR-7 | Stack: Modern.js + MF 2.0 + React 18 + `<Suspense>` + no fetch outside `useEffect` + `React.lazy()` | 4.15.1 + CLAUDE.md #1 | Lint rule + code-review check | Implemented (phase-1) |
| NFR-8 | MF manifest exposed at `https://<domain>/mf-manifest.json` | CLAUDE.md #1 | Integration test hits the URL | Implemented |
| NFR-9 | All 9 languages supported | 4.16 | i18n key-coverage test | Partial (keys present; coverage not all audited) |

---

## Appendix inventory (mockup → subsection map)

Built at P0 (= this spec's commit). Every image in `word/media/imageNN.{png,tiff,emf,jpeg}` is mapped to the TZ subsection that references it. Images total: 197.

Inventory lives in a separate file for size: `docs/superpowers/specs/2026-04-21-online-board-schedule-tz-mockup-inventory.md` (generated at P1 kickoff by parsing the TZ docx).

---

## Assumptions & open questions

1. **Responsive mockups completeness.** Appendices 4 (web), 6 (mobile), 7 (tablet) are assumed to cover every subsection's UI. If a subsection lacks a mobile/tablet mockup, we fall back to responsive-CSS-driven layout and flag the gap in the subsection's Action column.
2. **Language-parity.** TZ does not enumerate every string per language; we trust the existing `src/i18n/` coverage and add keys for any new strings introduced by this audit. P1 will spot-audit 9-language coverage for page titles + breadcrumbs; other plans follow per-surface.
3. **SignalR live updates.** TZ does not explicitly mention live-updates via SignalR, but current impl uses it. We treat SignalR as an implementation detail that must preserve TZ behavior, not an additional TZ requirement. SignalR-specific bugs fall under their current owner, not this audit.
4. **Load test (NFR-1).** Load testing requires a backend endpoint and infra access; if not available, NFR-1 is deferred to the deploy program with a note in the compliance report. Frontend-side perf targets (LCP, TTFB, bundle size) are still verified per-plan.
5. **Phase-1 compatibility.** We keep phase-1 architecture (Modern.js 2.70.8 + MF 2.3.3) unchanged unless TZ requires otherwise — CLAUDE.md documents constraints that make phase-1 stack currently best-available.

---

## Process for each plan

Each of P1–P6 follows this sequence:

1. **Populate remaining rule rows** in its subsections (reading the full TZ text for those subsections + inspecting mockups).
2. **Arbitrate any `Conflict`-status rules** assigned to this plan — populate the Conflicts register with Resolution before writing the plan.
3. **Invoke `superpowers:writing-plans`** with the rule IDs as input.
4. **Execute plan** per `superpowers:executing-plans` (TDD-first where applicable per `superpowers:test-driven-development`).
5. **Code review** per `superpowers:requesting-code-review`.
6. **Finish branch** per `superpowers:finishing-a-development-branch` — merge to main (after user authorization for the push).
7. **Update this spec** — change rule `Status` from `TBD`/`Missing`/`Partial` to `Done` + commit SHA. Append row to Merge log.

---

## Out-of-scope explicitly called out

- No database / telegram / backend work is initiated from this spec. Backend changes discovered during audit are flagged in a separate "Backend escalations" file, not addressed here.
- No refactoring beyond what serves a specific rule row. Cosmetic cleanups sit in a separate backlog.

---

## Project completion — 2026-04-23

All six P-plans (P1–P6) have shipped. §4.1.1 through §4.1.24 of TZ РИ-07-2538С are implemented or explicitly deferred with documented backlog items.

**Final coverage:**
| Category | Count |
|----------|-------|
| Done | ~541 |
| Implemented (pre-plan + locked in) | ~261 |
| Partial | 7 |
| Conflict (resolved, documented) | 12 |
| Conflict (pending arbitration) | 0 |
| Out-of-scope (backend / infra) | 17 |
| TBD (backlog) | ~15 |
| **Total** | **~941** |

**Backlog items** (deferred for future work):
- §4.1.16.2 R10–R15: Schedule mini-list three-date-group UI
- §4.1.16.3 R28–R31: Full §4.1.16.3.1 re-search navigation algorithm
- §4.1.23 widespread «Уточняется» component wiring (helper exists; deep wiring partially deferred)
- §4.1.24.1-R5: Flight Map loader (Angular-parity divergence vs TZ "no loader" — see C12)
- §4.1.24.2-R9: Flight Map Маршрут/Вылет mode switcher (not present in Angular reference)
- §4.1.15.4-R6: Check-in counter number (requires backend extension)
- Airport-as-link (SVO/VKO) across results + details pages (§4.1.13.4-R6, §4.1.14.4-R5/R7)
- All 7 non-ru/en locales: translate `BREADCRUMBS.*`, `SHARED.SCHEDULE-RANGE-MAX-7-DAYS`, `SHARED.RETURN-DATE-BEFORE-OUTBOUND`, `SEO.BOARD.*`, and `PAGE500.REFRESH`

**NFR checklist status** (§4.8.2 capacity, §4.9 integrations, §4.11 reliability, §4.12 a11y, §4.14 security, §4.15.1 stack):
- §4.15.1 Stack: Implemented per CLAUDE.md (Modern.js + MF 2.0 + React 18).
- §4.9.2 Yandex.Metrica events: verified by existing event-dispatch tests.
- §4.12 A11y: addressed by numerous P1–P5 commits (aria-current, aria-label, aria-pressed, role=dialog, etc.).
- §4.8.2 100 RPS capacity, §4.11 reliability, §4.14 security: deferred to deploy/infra program per P1 spec acknowledgement.
- No new architectural patterns (e.g. state-management rewrite). If a rule is implementable with the existing pattern, we use it.
