From 2f386cbaf08e03974e993e244a0d540496a86900 Mon Sep 17 00:00:00 2001 From: gnezim Date: Wed, 22 Apr 2026 02:21:57 +0300 Subject: [PATCH] Mark P6 rules Done + project-complete summary in TZ audit spec --- ...nline-board-schedule-tz-redesign-design.md | 216 ++++++++++-------- 1 file changed, 126 insertions(+), 90 deletions(-) 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 af270bb5..a39d19ee 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 @@ -129,14 +129,14 @@ _Updated after each plan merges. Plan task: after every merge, append a row to t | 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 | **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`) | +| 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 | **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) | 16 (§4.1.5-R1..R11, R13, R28 — backend aggregation service rules; **+3 P6**: 4.1.18-R1/R2/R3 — cache TTLs and 304 are backend HTTP-header responsibilities; client makes no stale-serving assumptions) | -| 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 + 16 Out-of-scope + ~228 TBD ≈ **941** | +| 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. | @@ -149,6 +149,7 @@ _Updated after each plan merges. Plan task: after every merge, append a row to t | 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 | --- @@ -169,6 +170,7 @@ Arbitrated case-by-case (Q2 = C). Each conflict blocks its owning plan's kickoff | 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. | --- @@ -1344,28 +1346,28 @@ Each unique page in the section must emit a ``, `<description>` meta, and | # | 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/*` | 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.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 | --- @@ -1376,12 +1378,12 @@ TZ Table 64 specifies three user-agent rules (Yandex, Googlebot, `*`) all permit | # | 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 | — | 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.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 | --- @@ -1392,14 +1394,14 @@ Invalid URL must be preserved in the browser address bar; system displays the co | # | 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]/*` | 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 | +| 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 | --- @@ -1455,14 +1457,14 @@ TZ Table 68 (lines 3054–3067) defines the two-column layout: left = section ta | # | 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` | 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 | +| 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 — Параметры фильтра «Карта полетов» @@ -1470,18 +1472,18 @@ TZ Table 69 (lines 3072–3083) defines 6 filter attributes. Two search modes: | # | 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-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 — Поиск и Вывод информации на карте @@ -1489,21 +1491,21 @@ Three rendering modes based on available route data. City-category zoom levels. | # | 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-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 — Интерактивное взаимодействие с картой полетов @@ -1511,12 +1513,12 @@ Three-click interaction model: first click fills Откуда, second fills Ку | # | 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-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 «Карта полетов» @@ -1524,10 +1526,10 @@ Four API endpoints: tiles, city-coordinates, routes, calendar. TZ source lines 3 | # | 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-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 — Переход по кнопке «Купить билет» из «Карты полетов» @@ -1535,9 +1537,9 @@ Buy-ticket CTA hands off to SB (booking subsystem) with prefilled route params. | # | 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 | +| 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 | --- @@ -1595,4 +1597,38 @@ Each of P1–P6 follows this sequence: - 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.