diff --git a/docs/superpowers/specs/2026-04-21-online-board-schedule-tz-redesign-design.md b/docs/superpowers/specs/2026-04-21-online-board-schedule-tz-redesign-design.md index a3f639dd..faa85493 100644 --- a/docs/superpowers/specs/2026-04-21-online-board-schedule-tz-redesign-design.md +++ b/docs/superpowers/specs/2026-04-21-online-board-schedule-tz-redesign-design.md @@ -128,15 +128,15 @@ _Updated after each plan merges. Plan task: after every merge, append a row to t | Metric | Count | |---|---| -| Total rules extracted | **853** (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: 6, 4.1.20: 4, 4.1.21: 5, **4.1.22: 11**, **4.1.23: 6**, 4.1.24: 7 skeleton) | +| 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 | **381** (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 (structure/mini-list/day-tabs ×15 `896dea9`, direct-flight gaps ×5 `21b6c90`, timeline ×5 `877cd87`, status ×2 `e33c8c4`, prev-flight ×1 `1740af6`), §4.1.16 ×16 (structure ×7 `896dea9`, day-tabs ×4 `896dea9`, schedule-block ×3 `0485a3b`, connecting ×2 `c49a2a8`), §4.1.22-R1–R10 `5d31f43`, §4.1.23-R1–R6 `b43c341`) | | Implemented | **~308** (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 (direct-flight audit `21b6c90`, multi-seg `7fcb844`, intermediate landing `c0c2d7d`, meals+services pre-existing `21b6c90`, day-change badges `63fc606`); §4.1.16 ×37 (Schedule variants `7fcb844`/`c49a2a8`/`0485a3b`); §4.1.17-R1–R7 `63fc606`; §4.1.22-R11 `5d31f43`) | | 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 | **11** (C1–C11 in Conflicts register; C1–C4 resolved, C5 pending P6, C6–C11 resolved; 1 rule cell still Conflict: 4.1.1-R22 = C5 pending P6; **P5 adds C9** «Уточняется» orange color `b43c341`, **C10** §4.1.22 carrier-icon vs aircraft-type clarification `5d31f43`, **C11** 4.1.17-R4 per-type badge independence `63fc606` — all resolved) | | Out-of-scope (backend) | 13 (§4.1.5-R1..R11, R13, R28 — backend aggregation service rules; tracked separately) | -| TBD | **~143** (P5 backlog: §4.1.15-R10/R25 (mobile tooltip, per-flight operating-day blocking); §4.1.16-R9/R10/R11/R14/R15/R28/R29/R30/R31 (mobile tooltip, three-date-group UI, full §4.1.16.3.1 nav algorithm); §4.1.23-R1–R4 widespread wiring deferred; pre-existing P4 deferred 7: 4.1.13.3-R7, R14; 4.1.13.4-R16; 4.1.14.3-R7, R14, R29; 4.1.14.4-R18; 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.18–4.1.24 skeleton ~25; §4.1.9-R53-R57 P4-tagged) | -| **Check** | 381 Done + 308 Implemented + 7 Partial + 1 Conflict + 13 Out-of-scope + ~143 TBD ≈ **853** | +| TBD | **~231** (P5 backlog: §4.1.15-R10/R25 (mobile tooltip, per-flight operating-day blocking); §4.1.16-R9/R10/R11/R14/R15/R28/R29/R30/R31 (mobile tooltip, three-date-group UI, full §4.1.16.3.1 nav algorithm); §4.1.23-R1–R4 widespread wiring deferred; pre-existing P4 deferred 7: 4.1.13.3-R7, R14; 4.1.13.4-R16; 4.1.14.3-R7, R14, R29; 4.1.14.4-R18; 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 P4-tagged; **P6 newly enumerated**: §4.1.18 ×3, §4.1.19 ×22, §4.1.20 ×6, §4.1.21 ×8 (minus R1/R2 = Implemented), §4.1.24 ×48 — all TBD) | +| **Check** | 381 Done + 308 Implemented + 7 Partial + 1 Conflict + 13 Out-of-scope + ~231 TBD ≈ **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. | @@ -1327,60 +1327,79 @@ Specifies when the `+1`/`+2`/`-1` chip appears next to a departure or arrival ti # 4.1.18 — Кеширование данных *Data caching.* -Single-paragraph section on acceptable client-side caching behavior (session cache of results per §4.1.1-R23, dictionary cache for cities/airports, etc.). +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 | Session-scope cache for search results per §4.1.1-R23/R27 | 4.1.18 | all | `src/shared/state/*` | TBD | Verify TTL + scope. | P6 | -| 4.1.18-R2 | Dictionary cache (cities, airports, aircraft types) — TTL per TZ | 4.1.18 | all | `src/shared/dictionaries/*` | TBD | Verify TTL + refresh policy. | P6 | -| 4.1.18-R3 | SSR-rendered initial payload must not expose stale data older than TZ-specified TTL | 4.1.18 | all | `src/server/*` | TBD | Verify SSR cache headers. | P6 | +| 4.1.18-R1 | Online-Board search results server-side cache TTL ≤ 1 minute ("кеш для «Онлайн-Табло» должен сохраняться не более 1 мин") | 4.1.18 TZ line 2909 | all | `src/shared/state/*` | TBD | Verify SSR response includes `Cache-Control: max-age=60` (or equivalent) for Online-Board result pages. | P6 | +| 4.1.18-R2 | Schedule search results server-side cache TTL ≤ 10 minutes ("кеш для «Расписания» должен сохраняться не более 10 мин") | 4.1.18 TZ line 2909 | all | `src/shared/state/*` | TBD | Verify SSR response includes `Cache-Control: max-age=600` (or equivalent) for Schedule result pages. | 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/*` | TBD | Verify that dictionary fetch responses carry ETag/Last-Modified + correct Cache-Control; browser caches on subsequent loads (304 path). | P6 | --- # 4.1.19 — Микроразметка страниц «Онлайн-Табло и Расписания» *Microdata: JSON-LD + OpenGraph.* -Each page must emit JSON-LD (schema.org types per TZ table) and OpenGraph meta tags. CLAUDE.md contract #6 lists this as binding. +Each unique page in the section must emit a ``, `<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 | Every page emits JSON-LD `<script type="application/ld+json">` in `<head>` | 4.1.19 | all | `src/ui/seo/*` | TBD | Verify presence per page. | P6 | -| 4.1.19-R2 | Start pages JSON-LD schema type per TZ | 4.1.19 | all | — | TBD | Populate schema. | P6 | -| 4.1.19-R3 | Results-list JSON-LD schema (Itemlist?) per TZ | 4.1.19 | all | — | TBD | Populate. | P6 | -| 4.1.19-R4 | Flight-details JSON-LD schema (Flight?) per TZ | 4.1.19 | all | — | TBD | Populate. | P6 | -| 4.1.19-R5 | OpenGraph meta tags (`og:title`, `og:description`, `og:url`, `og:image`, `og:locale`) per page | 4.1.19 | all | `src/ui/seo/*` | TBD | Verify. | P6 | -| 4.1.19-R6 | `og:locale` matches the active language | 4.1.19 | all | — | TBD | Verify. | P6 | - -**Rules to refine from 4.1.19 text at P6 kickoff.** +| 4.1.19-R1 | Online-Board start page `<title>` = `"Онлайн-табло вылетов и прилетов рейсов авиакомпании Аэрофлот \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2916 | all | `src/ui/seo/*` | TBD | Verify 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/*` | TBD | Verify `<meta name="description">` on start page. | P6 | +| 4.1.19-R3 | Online-Board flight-number search page `<title>` = `"Рейс {Номер рейса} – Онлайн-табло прилета и вылета сегодня \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2917 | all | `src/ui/seo/*` | TBD | Verify flight-number result page title format. | 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/*` | TBD | Verify description meta on flight-number result page. | P6 | +| 4.1.19-R5 | Online-Board route search page `<title>` = `"Прибытие и отправление рейсов {город вылета} - {город прилета} сегодня \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2918 | all | `src/ui/seo/*` | TBD | Verify route result page title format (full city names). | 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/*` | TBD | Verify description meta on route result page. | P6 | +| 4.1.19-R7 | Online-Board departure-only page `<title>` = `"Онлайн-табло вылетов из {Название аэропорта} \| Отправление рейсов Аэрофлот {сегодня}"` | 4.1.19 Table 63 TZ line 2919 | all | `src/ui/seo/*` | TBD | Verify departure-only page title (includes airport name). | 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/*` | TBD | Verify description meta on departure page. | P6 | +| 4.1.19-R9 | Online-Board arrival-only page `<title>` = `"Онлайн-табло прилетов в {Название аэропорта} \| Прибытие рейсов от Аэрофлот {сегодня}"` | 4.1.19 Table 63 TZ line 2920 | all | `src/ui/seo/*` | TBD | Verify arrival-only page title. | 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/*` | TBD | Verify description meta on arrival page. | P6 | +| 4.1.19-R11 | Online-Board flight card `<title>` = `"Статус рейса {Номер рейса} {сегодня} \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2921 | all | `src/ui/seo/*` | TBD | Verify flight-card (details) page title; `{сегодня}` = 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/*` | TBD | Verify description meta on details page. | P6 | +| 4.1.19-R13 | Schedule start page `<title>` = `"Расписание рейсов \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2923 | all | `src/ui/seo/*` | TBD | Verify Schedule start page title. | P6 | +| 4.1.19-R14 | Schedule start page `<meta name="description">` = `"Подробное расписание самолетов на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2923 | all | `src/ui/seo/*` | TBD | Verify Schedule start page description. | 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/*` | TBD | Verify Schedule route result page title including multi-city chain. | P6 | +| 4.1.19-R16 | Schedule route search result `<meta name="description">` = `"Подробное расписание самолетов по маршруту {город вылета} - {город прилета} на сегодня и ближайшие даты на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2924 | all | `src/ui/seo/*` | TBD | Verify Schedule route result description. | P6 | +| 4.1.19-R17 | Schedule flight card `<title>` = `"Рейс {Номер рейса} – Расписание рейсов на {сегодня} \| Аэрофлот"` | 4.1.19 Table 63 TZ line 2925 | all | `src/ui/seo/*` | TBD | Verify Schedule flight-card title (date is query date). | P6 | +| 4.1.19-R18 | Schedule flight card `<meta name="description">` = `"Информация об отправлении и прибытии рейса {Номер рейса} в режиме онлайн! Время вылета, время прилета, актуальный статус рейса на официальном сайте авиакомпании Аэрофлот."` | 4.1.19 Table 63 TZ line 2925 | all | `src/ui/seo/*` | TBD | Verify Schedule flight-card description. | 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/*` | TBD | Verify Flight Map title; note no pipe+brand suffix in TZ Table 63. | P6 | +| 4.1.19-R20 | Flight Map page `<meta name="description">` = `"Карта полетов авиакомпании «Аэрофлот». Информация о направлениях рейсов."` | 4.1.19 Table 63 TZ line 2927 | all | `src/ui/seo/*` | TBD | Verify Flight Map description meta. | 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/*` | TBD | Verify shared date-label helper is reused in SEO head generation. | 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` | TBD | Verify i18n keys cover all 9 locales for every page-type SEO string. | P6 | --- # 4.1.20 — Robots.txt *Crawler policy.* -Specifies indexable paths (start pages, results with canonical params) vs disallowed paths (pages with volatile params, search mutations). Actual `robots.txt` lives at the host site, but the app must emit correct `<link rel="canonical">` and `<meta name="robots">` per page. +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 | Start pages emit `<meta name="robots" content="index,follow">` | 4.1.20 | all | — | TBD | Verify. | P6 | -| 4.1.20-R2 | Canonical link per page (normalized URL, no volatile query params) | 4.1.20 | all | — | TBD | Verify per page. | P6 | -| 4.1.20-R3 | Pages TZ marks as non-indexable emit `noindex` | 4.1.20 | all | — | TBD | Populate list. | P6 | -| 4.1.20-R4 | hreflang alternates for all 9 languages per page | 4.1.20 | all | — | TBD | Verify. | P6 | +| 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 | — | TBD | Confirm `robots.txt` at host domain contains the three allow rules from Table 64 with no Disallow. | 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/*` | TBD | Verify canonical tag is present and points to the clean canonical URL on every rendered page. | 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/*` | TBD | Verify that canonical tag href uses the correct locale-format path segment for each language. | 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/*` | TBD | Verify Flight Map page emits canonical tag to the plain `/{lang}/flights-map` URL. | 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/*` | TBD | Verify hreflang block lists all 9 language alternates on every indexable page. | 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]/*` | TBD | Verify noindex meta on `/error/404` and `/error/500`. | P6 | --- # 4.1.21 — Внештатные ситуации, ошибки 404 и 500 *Error pages: 404 + 500.* -Must render the Aeroflot-standard 404/500 pages. Centralized error page at `/error/404` and `/error/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 route → `/error/404` | 4.1.21 | all | `src/routes/error/[code]/*` | Implemented | Verify. | P6 | -| 4.1.21-R2 | Server error → `/error/500` | 4.1.21 | all | `src/routes/error/[code]/*` | Implemented | Verify. | P6 | -| 4.1.21-R3 | 404 page includes link back to section root + top-level breadcrumb | 4.1.21 | all | — | TBD | Verify. | P6 | -| 4.1.21-R4 | 500 page includes refresh CTA + support contact | 4.1.21 | all | — | TBD | Verify. | P6 | -| 4.1.21-R5 | Both pages SEO: `noindex`; translated for all 9 languages | 4.1.21 | all | — | TBD | Verify. | P6 | +| 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]/*` | Implemented | Verify that Modern.js 404 handler does not redirect — URL stays unchanged in address bar. | 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]/*` | Implemented | Verify that unhandled server errors render 500 page with correct HTTP status code 500. | 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 | — | TBD | Add noindex meta tag to both error page components. | P6 | +| 4.1.21-R4 | Browser back-button from 404/500 page navigates to the previous page ("При нажатии на кнопку «бэк» браузера должен выполняться переход на предыдущую страницу") | 4.1.21 TZ line 2964 | all | — | TBD | Verify history is not replaced/cleared when error page renders; back button goes to previous URL. | 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 | — | TBD | Verify 404 page renders image, headline, description text, and functional search field. | 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 | — | TBD | Verify all three CTA links are present and point to correct Aeroflot.ru URLs. | P6 | +| 4.1.21-R7 | 500 page contains: illustration image + headline `"500 / Ошибка на сервере"` + explanatory text `"При обработке запроса произошла ошибка"` + same three CTAs (search, buy ticket, main, feedback) | 4.1.21 Table 66 TZ lines 2977–2982 | all | — | TBD | Verify 500 page structure matches Table 66: image, headline, description, search field, buy-ticket link, home link, feedback link. | 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 | — | TBD | Verify i18n keys exist for 404/500 headlines, body text, and CTA labels across all 9 locales. | P6 | --- @@ -1426,21 +1445,99 @@ Short subsection: when a data field is missing/pending from the API, UI shows th # 4.1.24 — Карта полетов *Flight Map (new in revision С). Appendix 9 = binding mockup reference.* -Six sub-subsections: start page (4.1.24.1), filter (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). Target rule count: **40–60**. +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 | **4.1.24.1** Start page composition (filter + empty map or route network as dots per §4.1.1-R21) | 4.1.24.1 + App. 9 | all | `FlightsMapStartPage.tsx` | TBD | Verify against Appendix 9 mockups. | P6 | -| 4.1.24-R2 | **4.1.24.2** Filter attributes: `Откуда`, `Куда`, `Дата рейса`, toggles (Внутренние / Международные / С пересадкой) | 4.1.24.2 | all | `FlightsMapFilter.tsx` | TBD | Enumerate per-attribute rules. | P6 | -| 4.1.24-R3 | **4.1.24.3** Search and map rendering — spider (direct), connecting lines, dots (full network). §4.1.1-R16 summarizes the three modes | 4.1.24.3 | all | `MapCanvas.tsx` | TBD | Verify rendering logic. | P6 | -| 4.1.24-R4 | **4.1.24.4** Interactive behavior — click on destination, hover tooltip, zoom, pan, route highlight | 4.1.24.4 | all | `MapCanvas.tsx` | TBD | Enumerate interactions. | P6 | -| 4.1.24-R5 | **4.1.24.5** Internal API — endpoints used by map (likely a subset of §4.3/4.5 methods) | 4.1.24.5 | all | `flights-map/hooks/*` | TBD | Verify endpoints match TZ. | P6 | -| 4.1.24-R6 | **4.1.24.6** "Buy ticket" button navigates to booking flow with prefilled from/to/date (URL hand-off to booking subsystem) | 4.1.24.6 | all | — | TBD | Verify CTA target + params. | P6 | -| 4.1.24-R7 | No loader shown on Flight Map (per 4.1 ¶ intro: "Для «Карты полетов» «лоадера» не предусмотрено") | 4.1 ¶ intro | all | — | TBD | Verify loader absence. | P6 | +| 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` | TBD | Verify start page renders when geo fails; filter state matches §4.1.1-R21. | 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` | TBD | Verify two-column layout on desktop/tablet against Appendix 9 mockups. | 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` | TBD | Verify single-column vertical stack on mobile. | 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` | TBD | Verify zoom controls are positioned top-left inside map container. | P6 | +| 4.1.24-R5 | No loader / spinner on Flight Map ("Для «Карты полетов» «лоадера» не предусмотрено") | 4.1 ¶ intro | all | `MapCanvas.tsx` | TBD | Verify no loading indicator is shown while map data fetches; map renders tiles progressively. | 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 | TBD | Verify active state on map tab; other two tabs are inactive. | 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` | TBD | Verify filter header text on desktop vs its absence on mobile. | P6 | +| 4.1.24-R8 | Flight Map filter cannot be collapsed or expanded ("Фильтр «Карты полетов» не должно быть возможно свернуть/развернуть") | 4.1.24.2 TZ line 3069 | all | `FlightsMapFilter.tsx` | TBD | Verify no collapse/expand toggle exists on the map filter. | P6 | -**Rules enumerated at P6 kickoff.** +## §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 | Verify mode switcher renders two modes and updates filter field visibility accordingly. | 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` | TBD | Verify Откуда autocomplete behavior and clear-button per §4.1.9.1/9.2 spec. | P6 | +| 4.1.24-R11 | `Стрелки смены направления` icon swaps `Город вылета` and `Город прилета` values on click | 4.1.24.2 Table 69 TZ line 3076 | all | `FlightsMapFilter.tsx` | TBD | Verify swap icon is present and exchanges city field values. | 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` | TBD | Verify Куда field behavior matches Откуда symmetrically. | 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` | TBD | Verify toggle disabled state tied to departure city; mutual exclusion with international toggle. | 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` | TBD | Verify toggle disabled state and mutual exclusion with domestic toggle. | 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` | TBD | Verify connecting-only toggle auto-activates when API returns no direct routes. | 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` | TBD | Verify date picker disabled until departure city set; window bounds; mobile quick-day buttons; clear behavior. | 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/*` | TBD | Verify date calendar highlights availability from API response bit-string; unavailable days greyed out. | 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` | TBD | Verify today/tomorrow labels replace ISO dates in the date field display. | P6 | +| 4.1.24-R19 | Hint text `"Нажмите на «Купить билет», чтобы получить полный список рейсов по выбранному направлению"` displayed below city fields | 4.1.24.2 Table 69 TZ line 3078 | all | `FlightsMapFilter.tsx` | TBD | Verify static hint text appears in filter area below city inputs. | 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` | TBD | Verify that after swap, if departure becomes empty, date is locked. | 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` | TBD | Verify dot-network mode renders when no departure city is set. | 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` | TBD | Verify all three zoom input methods work. | 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` | TBD | Verify city-category zoom breakpoints align with TZ (3 levels: min/medium/max). | 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` | TBD | Verify IATA code tooltip on desktop hover; no tooltip on tablet/mobile. | P6 | +| 4.1.24-R25 | Direct route = solid arc (прямая дуга); connecting route = dashed arc (пунктирная дуга) | 4.1.24.3 TZ lines 3099–3101 | all | `MapCanvas.tsx` | TBD | Verify arc style: solid vs dashed based on route type. | 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` | TBD | Verify three-state dot color coding (orange/blue/grey). | 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` | TBD | Verify that presence of direct routes overrides connecting display. | 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` | TBD | Verify connecting-only arc mode when API returns no direct routes. | 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` | TBD | Verify dot-network fallback when no routes found. | P6 | +| 4.1.24-R30 | Search runs only for Маршрут and Вылет modes ("Поиск направлений должен выполнятся только для режимов: Вылет, Маршрут") | 4.1.24.3 TZ line 3109 | all | `flights-map/hooks/*` | TBD | Verify API call triggered only in these modes, not for arrival-only. | 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` | TBD | Verify departure point is orange; arrival point orange when both filled. | P6 | +| 4.1.24-R32 | Внутренние рейсы active → show only domestic (Russia) routes; hide international | 4.1.24.3 TZ line 3112 | all | `MapCanvas.tsx` | TBD | Verify domestic filter hides international arcs/dots. | P6 | +| 4.1.24-R33 | Международные регулярные рейсы active → show only international routes; hide domestic | 4.1.24.3 TZ line 3113 | all | `MapCanvas.tsx` | TBD | Verify international filter hides domestic arcs/dots. | P6 | +| 4.1.24-R34 | Both toggles inactive → show all routes (domestic + international) | 4.1.24.3 TZ line 3114 | all | `MapCanvas.tsx` | TBD | Verify all routes shown when neither domestic nor international toggle is selected. | P6 | +| 4.1.24-R35 | Показать только рейсы с пересадкой active → hide direct routes, show only connecting | 4.1.24.3 TZ line 3115 | all | `MapCanvas.tsx` | TBD | Verify connecting-filter removes direct arcs and shows only dashed arcs. | 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` | TBD | Verify first-click behavior: departure fill, orange mark, arc display, date auto-fill, radio state. | 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` | TBD | Verify second-click behavior: arrival fill, arc drawn, date updated, buy-ticket popup visible and closeable. | 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` | TBD | Verify arc type and toggle state after route resolution. | 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` | TBD | Verify third-click resets both fields and starts new departure cycle. | 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` | TBD | Verify click events are registered on all rendered city dots. | P6 | +| 4.1.24-R41 | "Купить билет" popup above arrival city is dismissible (closeable by user) | 4.1.24.4 TZ line 3119 | all | `MapCanvas.tsx` | TBD | Verify popup close control (X or outside-click) works. | 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/*` | TBD | Verify tile URL pattern and that tile responses carry appropriate cache headers (max-age ≥ 21600). | 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/*` | TBD | Verify the coordinate API is called with IATA codes and result drives dot placement on canvas. | 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/*` | TBD | Verify routes hook calls destinations endpoint with correct params; parses `routes[]` array with direct-route boolean. | 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/*` | TBD | Verify calendar hook calls days endpoint; parses bit-string to determine available dates for picker. | 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 | — | TBD | Verify CTA uses `target="_blank"` (or equivalent) on the generated SB URL. | 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 | — | TBD | Verify SB URL includes all required fixed params (adults=1, cabin=economy, autosearch=Y) plus dynamic `routes` value. | 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 | — | TBD | Verify route string is assembled correctly for single leg; multi-leg joined; date omitted when filter has no date. | P6 | ---