# Пользовательские истории: Обработка ошибок, граничные случаи и доступность

## US-85: Ошибка 404 — Страница не найдена

**Цель:** Пользователь видит понятное сообщение при переходе на несуществующий URL и может быстро вернуться к рабочему разделу.

**Путь клиента:**

1. **Переход по неверному URL — ввод или клик по битой ссылке** — пользователь вводит `/onlineboard/invalid` или попадает на устаревшую ссылку.
2. **Router сопоставляет маршрут — wildcard redirect** — роутер не находит соответствия и перенаправляет на `/error/404`.
3. **Страница ошибки загружается — ErrorPageComponent** — компонент читает `errorCode`, `title`, `description` из `route.snapshot.data`.
4. **Отображение сообщения — код и текст** — на экране виден код «404», заголовок `PAGE404.HEADER` и описание `PAGE404.DESCRIPTION`.
5. **Скрытие баннеров хоста — hideAflComponents()** — страница скрывает `.afl-component--banners` на время показа ошибки.
6. **Поле поиска на странице ошибки** — на странице отображается текстовое поле с кнопкой поиска; ввод запроса и клик по кнопке открывает результаты поиска на aeroflot.ru в новой вкладке.
7. **Возврат к работе — переход на главную** — пользователь нажимает кнопку и возвращается на `/onlineboard` или в поиск.

**Критерии приёмки:**

- ✅ Маршрут `/error/404` отображает страницу с кодом 404.
- ✅ Любой неизвестный URL приводит к редиректу на `/error/404`.
- ✅ Заголовок и описание берутся из переводов (`PAGE404.*`).
- ✅ Поле поиска позволяет выполнить поиск на aeroflot.ru из страницы ошибки (открывается в новой вкладке).
- ✅ Кнопка «На главную» возвращает на рабочий раздел.
- ✅ Баннеры хост-приложения скрыты на время показа ошибки и восстанавливаются при уходе со страницы (`ngOnDestroy`).

**Примечание:** URL онлайн-табло — `/onlineboard` (без дефиса). Компонент ошибки — общий для 404 и 500, различие задаётся через `route.data`.

---

## US-86: Ошибка 500 — Внутренняя ошибка сервера

**Цель:** Пользователь получает понятное сообщение при серверной ошибке и возможность повторить запрос или вернуться на главную.

**Путь клиента:**

1. **Действие пользователя — поиск или загрузка расписания** — пользователь запускает поиск рейсов.
2. **Ошибка на бэкенде — HTTP 5xx** — API возвращает 500 либо приложение роутится на `/error/500` как default.
3. **Показ страницы ошибки — ErrorPageComponent с errorCode=500** — читаются `PAGE500.HEADER` и `PAGE500.DESCRIPTION`.
4. **Скрытие баннеров — hideAflComponents()** — сторонние компоненты хоста временно скрываются.
5. **Действия пользователя — повтор или возврат** — пользователь может повторить запрос или перейти на главную.
6. **Восстановление — ngOnDestroy** — при уходе со страницы баннеры возвращаются в прежнее состояние.

**Критерии приёмки:**

- ✅ При неизвестной ошибке приложение показывает `/error/500` (дефолтный редирект с `/error`).
- ✅ На странице отображается код «500» и локализованные `PAGE500.*` строки.
- ✅ Кнопка «На главную» доступна и работает.
- ✅ Баннеры хоста скрываются и корректно восстанавливаются.
- ✅ Страница не ломает верстку хост-приложения.

**Примечание:** URL онлайн-табло — `/onlineboard` (без дефиса). В Angular-источнике ErrorPage один компонент на оба кода; дифференциация — через `route.data`.

---

## US-88: Timeout при загрузке данных

**Цель:** Пользователь видит корректное состояние, если ответ сервера долго не приходит, и может отменить или повторить запрос.

**Путь клиента:**

1. **Запуск запроса — пользователь выполняет поиск** — отправляется запрос на API рейсов/расписания.
2. **Отображение спиннера — loading state** — UI показывает индикатор загрузки до получения ответа.
3. **Ожидание превышает лимит — длительное отсутствие ответа** — сеть медленная или сервер не отвечает.
4. **Обработка таймаута — http-cancel / error handler** — запрос завершается с ошибкой, приложение переходит в состояние ошибки.
5. **Сообщение пользователю — понятный текст** — отображается сообщение о проблеме соединения и предложение повторить.
6. **Повтор или отмена — пользователь управляет ситуацией** — можно повторить поиск либо отказаться.

**Критерии приёмки:**

- ✅ Во время загрузки виден индикатор прогресса.
- ✅ При длительном отсутствии ответа приложение не «зависает».
- ✅ После таймаута пользователь видит понятное сообщение об ошибке.
- ✅ Доступно действие «Повторить».
- ✅ Отменённые запросы не обновляют UI задним числом.

**Примечание:** Сценарий тестовый — описывает ожидаемое поведение; в Angular-источнике явного лимита таймаута нет, обработка опирается на общий error handler и `HttpCancelService`.

---

## US-89: Некорректный ввод (специальные символы)

**Цель:** Система безопасно обрабатывает ввод со специальными символами и не допускает XSS.

**Путь клиента:**

1. **Ввод спецсимволов — пользователь печатает `<script>`, кавычки, HTML-теги** — данные попадают в поле поиска/фильтра.
2. **Санитизация — Angular/React встроенная защита** — фреймворк экранирует значения при выводе.
3. **Валидация — проверка на валидные коды IATA или названия** — некорректные значения не проходят в запрос к API.
4. **Безопасное отображение — текст как текст** — символы показываются экранированными, без исполнения.
5. **Ответ пользователю — подсказка об ошибке** — при невалидном вводе показывается сообщение.

**Критерии приёмки:**

- ✅ Ввод `<script>`-тегов не приводит к исполнению JS.
- ✅ HTML-теги отображаются как текст, а не как разметка.
- ✅ Невалидные IATA/названия отклоняются до отправки на сервер.
- ✅ Ответ сервера также экранируется при выводе.
- ✅ Атрибуты DOM не собираются из пользовательского ввода напрямую.

**Примечание:** Тестовый сценарий безопасности; в кодовой базе обеспечивается framework-уровнем (автоэкранирование).

---

## US-90: Невалидная комбинация параметров

**Цель:** Система валидирует комбинации параметров поиска и предотвращает бессмысленные запросы.

**Путь клиента:**

1. **Пользователь заполняет форму — города, даты, направление** — вводятся параметры поиска.
2. **Обнаружение конфликта — валидаторы формы** — пользователь выбирает одинаковый город для «Откуда» и «Куда», либо прошлую дату, либо недопустимый диапазон.
3. **Блокировка отправки — disable submit** — кнопка «Найти» остаётся неактивной или поиск не запускается.
4. **Отображение ошибок — подсказки под полями** — рядом с полем выводится причина ошибки.
5. **Исправление — пользователь меняет параметры** — после корректного ввода ошибки очищаются и поиск становится доступен.

**Критерии приёмки:**

- ✅ Совпадающие города «Откуда»/«Куда» отклоняются.
- ✅ Даты в прошлом недопустимы для выбора.
- ✅ Недопустимый диапазон дат показывает сообщение об ошибке.
- ✅ Пока форма невалидна, кнопка поиска неактивна.
- ✅ Сообщения об ошибках локализованы.

**Примечание:** Тестовый сценарий; в Angular-источнике правила валидации частично присутствуют в `shared/services/validators`.

---

## US-91: Пустой результат поиска

**Цель:** Пользователь получает понятное сообщение, когда по запросу нет рейсов.

**Путь клиента:**

1. **Поиск — пользователь задаёт параметры** — выбирает города и даты, запускает поиск.
2. **Запрос к API — корректные параметры** — валидация пройдена, запрос уходит на сервер.
3. **Пустой ответ — нет рейсов** — API возвращает пустой список для указанных условий.
4. **Отображение empty state — сообщение «Рейсов не найдено»** — вместо списка показывается информационный блок.
5. **Подсказки пользователю — альтернативы** — предлагается изменить дату/направление или сбросить фильтры.

**Критерии приёмки:**

- ✅ Пустой массив результатов не показывает «сломанный» UI.
- ✅ Отображается локализованное сообщение об отсутствии рейсов.
- ✅ Пользователь может легко изменить параметры поиска.
- ✅ Активные фильтры не блокируют новый поиск.
- ✅ Состояние empty отличается визуально от состояния ошибки.

---

## US-92: Unicode символы в вводе

**Цель:** Система корректно отображает и обрабатывает строки в Unicode (кириллица, CJK, арабский, emoji).

**Путь клиента:**

1. **Ввод Unicode — пользователь печатает нелатинские символы** — в поле поиска появляется текст в разных алфавитах.
2. **Передача в API — UTF-8 кодирование** — запрос корректно кодирует параметры.
3. **Ответ сервера — Unicode в названиях** — города и аэропорты возвращаются с корректной кодировкой.
4. **Отображение — шрифты и вёрстка** — названия видны без «кракозябр».
5. **Фильтрация и поиск — регистронезависимое сравнение** — совпадения находятся вне зависимости от регистра и алфавита.

**Критерии приёмки:**

- ✅ Кириллица, CJK и арабский отображаются корректно.
- ✅ Emoji не ломают верстку (допустимо отклонить при валидации IATA).
- ✅ Поиск работает с Unicode-запросами.
- ✅ Параметры корректно кодируются в URL.
- ✅ Ответ API с Unicode-строками рендерится без повреждений.

---

## US-93: Очень длинные имена городов и аэропортов

**Цель:** Интерфейс не ломается при очень длинных названиях городов, аэропортов и авиакомпаний.

**Путь клиента:**

1. **Загрузка данных — длинное название** — API возвращает запись с длинным именем (например, полное название аэропорта).
2. **Отображение в списке — ограниченная ширина** — карточка или строка имеет фиксированную ширину.
3. **Обрезка текста — CSS ellipsis** — длинный текст усекается с многоточием.
4. **Полное имя — tooltip/title** — при наведении доступно полное название.
5. **Адаптив — разные breakpoints** — поведение сохраняется на мобильных и десктопе.

**Критерии приёмки:**

- ✅ Длинные названия не выходят за границы контейнера.
- ✅ Текст усекается с видимым многоточием.
- ✅ Полное название доступно через tooltip или аналогичный механизм.
- ✅ Верстка соседних элементов не сдвигается.
- ✅ Поведение работает на всех breakpoint.

**Примечание:** Тестовый сценарий устойчивости; в кодовой базе опирается на CSS-классы текстовых ячеек.

---

## US-94: Быстрая последовательность поисков

**Цель:** Система корректно обрабатывает быстро выполняемые подряд запросы и показывает результат последнего.

**Путь клиента:**

1. **Пользователь запускает поиск — первый запрос** — уходит HTTP-запрос на сервер.
2. **Смена параметров — новый запрос до завершения предыдущего** — пользователь меняет город/дату и снова нажимает поиск.
3. **Отмена предыдущего — HttpCancelService** — незавершённые запросы отменяются через cancel-механизм.
4. **Обработка последнего — применение свежего ответа** — в UI показывается результат только последнего запроса.
5. **Защита от гонок — state не перезаписывается старым ответом** — отменённые запросы не обновляют список.

**Критерии приёмки:**

- ✅ Отменённый запрос не обновляет UI.
- ✅ В результатах отображается ответ последнего запроса.
- ✅ Индикатор загрузки виден до завершения последнего запроса.
- ✅ Быстрые клики не приводят к миганию результатов.
- ✅ Нет утечек подписок/обработчиков.

**Примечание:** В Angular-источнике есть `shared/services/http-cancel.service.ts`, обеспечивающий отмену предыдущих запросов.

---

## US-95: Клавиатурная навигация

**Цель:** Пользователь может полностью управлять приложением только с клавиатуры.

**Путь клиента:**

1. **Навигация по Tab — обход интерактивных элементов** — фокус проходит по полям, кнопкам и ссылкам в логичном порядке.
2. **Активация — Enter/Space** — кнопки и ссылки активируются клавиатурой.
3. **Отправка формы — Enter в поле поиска** — запускает поиск без использования мыши.
4. **Закрытие попапов — Escape** — выпадающие списки и диалоги закрываются по Esc.
5. **Навигация по спискам — стрелки** — в dropdown и списках работает перемещение стрелками.
6. **Видимый фокус — focus ring** — текущий элемент визуально выделен.

**Критерии приёмки:**

- ✅ Tab-порядок соответствует визуальному порядку элементов.
- ✅ Нет «ловушек» фокуса вне диалогов.
- ✅ Enter отправляет форму поиска.
- ✅ Escape закрывает выпадающие меню и диалоги.
- ✅ На всех интерактивных элементах виден focus-индикатор.

**Примечание:** Тестовый сценарий доступности; поведение собирается из встроенной семантики и ARIA.

---

## US-96: Читатели экрана (ARIA-метки)

**Цель:** Приложение корректно работает со screen reader (NVDA, VoiceOver, JAWS).

**Путь клиента:**

1. **Загрузка страницы — screen reader озвучивает заголовок** — `<title>` и `h1` корректно читаются.
2. **Формы — label и aria-label** — каждое поле связано с текстовой меткой.
3. **Кнопки и иконки — aria-label для icon-only** — иконочные кнопки имеют текстовую аннотацию.
4. **Динамический контент — aria-live** — появление результатов поиска или ошибок объявляется.
5. **Списки и таблицы — корректные роли** — расписание использует семантическую структуру.
6. **Изображения — alt** — декоративные скрыты (`alt=""`), смысловые имеют описание.

**Критерии приёмки:**

- ✅ Все поля форм имеют связанный label или aria-label.
- ✅ Icon-only кнопки озвучиваются со смысловой подписью.
- ✅ Динамические обновления (результаты, ошибки) объявляются screen reader.
- ✅ Используется семантический HTML (`button`, `nav`, `main`, `h1`–`h3`).
- ✅ Изображения имеют корректный alt.

**Примечание:** Тестовый сценарий WCAG; конкретная реализация ARIA — часть компонентного слоя.

---

## US-98: Фокусное кольцо (focus-visible)

**Цель:** Пользователь, навигирующий с клавиатуры, всегда видит, какой элемент находится в фокусе.

**Путь клиента:**

1. **Навигация Tab — пользователь переходит между элементами** — каждое нажатие Tab смещает фокус.
2. **Рендеринг focus ring — CSS `:focus-visible`** — обводка появляется только при клавиатурной навигации.
3. **Скрытие при клике мышью — `:focus:not(:focus-visible)`** — кольцо не отображается при обычных кликах.
4. **Контрастность — видимое кольцо** — цвет и толщина заметны на фоне.
5. **Единообразие — стиль одинаков для всех интерактивных элементов** — кнопки, ссылки, поля ввода.

**Критерии приёмки:**

- ✅ При Tab-навигации фокус виден на всех интерактивных элементах.
- ✅ При кликах мышью кольцо не появляется (используется `:focus-visible`).
- ✅ Контраст кольца соответствует WCAG AA.
- ✅ Стиль фокуса согласован между компонентами.
- ✅ Ни один интерактивный элемент не остаётся без индикатора фокуса.

**Примечание:** Тестовый сценарий доступности, реализуется через глобальные CSS-стили.

---

## US-99: Масштабирование текста (zoom)

**Цель:** Пользователь может увеличивать текст/страницу до 200% без потери функциональности.

**Путь клиента:**

1. **Запуск zoom — Ctrl + / Cmd +** — браузер увеличивает масштаб страницы.
2. **Адаптация текста — относительные единицы** — `rem`/`em` корректно пересчитываются.
3. **Адаптивный layout — reflow** — сетка перестраивается под новую ширину.
4. **Отсутствие горизонтального скролла — контент остаётся в viewport** — до 200% без обрезки.
5. **Интерактив доступен — все кнопки и поля видны** — форма поиска и результаты пригодны к использованию.

**Критерии приёмки:**

- ✅ Увеличение до 200% не ломает layout.
- ✅ Нет горизонтального скролла на типовых breakpoint.
- ✅ Текст не перекрывается и не обрезается.
- ✅ Все интерактивные элементы остаются доступными.
- ✅ Relative units используются для размеров текста и отступов.

**Примечание:** Тестовый сценарий WCAG 1.4.4; реализация зависит от CSS.

---

## US-100: Сенсорная навигация (touch)

**Цель:** Мобильные пользователи могут полностью управлять приложением пальцами.

**Путь клиента:**

1. **Touch-жесты — tap, swipe, pinch** — пользователь взаимодействует с экраном.
2. **Размер целей — минимум 44×44px** — кнопки и ссылки достаточны для попадания пальцем.
3. **Интервалы — достаточные отступы** — соседние цели не кликаются случайно.
4. **Альтернативы жестам — обычные кнопки** — любое действие через swipe дублируется кнопкой.
5. **Отсутствие hover-only — tap доступен всегда** — функциональность не скрыта за hover.

**Критерии приёмки:**

- ✅ Touch-таргеты не меньше 44×44px.
- ✅ Расстояние между целями предотвращает случайные нажатия.
- ✅ Все функции доступны без hover.
- ✅ Жесты (где используются) имеют альтернативу обычным тапом.
- ✅ Скролл и pinch-zoom работают штатно.

**Примечание:** Тестовый сценарий мобильной доступности.

---

## US-101: Персистентное состояние (StateService)

**Цель:** Ключевое состояние приложения сохраняется между переходами и переживает перезагрузку страницы там, где это предусмотрено.

**Путь клиента:**

1. **Сохранение — StateService.set(key, data)** — страница кладёт состояние в Map по ключу.
2. **Переход между разделами — навигация** — пользователь уходит с экрана и возвращается.
3. **Чтение — StateService.get(key)** — экран восстанавливает данные из сервиса.
4. **Параметры поиска в URL — query params** — часть состояния синхронизируется через URL.
5. **Перезагрузка страницы — refresh** — параметры из URL позволяют восстановить форму и запустить поиск.
6. **Очистка — StateService.delete(key)** — при необходимости состояние сбрасывается.

**Критерии приёмки:**

- ✅ `StateService.set`/`get`/`delete` работают как in-memory Map хранилище.
- ✅ При возврате на страницу её состояние восстанавливается из сервиса.
- ✅ Параметры поиска присутствуют в URL и читаются при старте.
- ✅ После refresh форма заполняется из URL-параметров.
- ✅ Данные не «текут» между независимыми ключами.

**Примечание:** В Angular-источнике `StateService` — простой in-memory Map (`shared/services/state.service.ts`), он не использует localStorage; персистенция между перезагрузками достигается через URL.

---

## US-102: История браузера (back/forward)

**Цель:** Кнопки браузера «Назад» и «Вперёд» корректно перемещают пользователя по состояниям приложения.

**Путь клиента:**

1. **Переход по разделам — пользователь открывает страницы** — роутер добавляет записи в history.
2. **Обновление URL — query params отражают фильтры** — параметры поиска и сортировки видны в URL.
3. **Нажатие Back — popstate** — роутер восстанавливает предыдущее состояние.
4. **Нажатие Forward — возврат к следующему состоянию** — аналогично, без потери данных.
5. **Синхронизация UI — чтение query params** — фильтры и форма обновляются под URL.

**Критерии приёмки:**

- ✅ Каждая значимая навигация создаёт запись в history.
- ✅ Back возвращает на предыдущий URL с корректным состоянием.
- ✅ Forward работает симметрично.
- ✅ Query params читаются при навигации и обновляют UI.
- ✅ Глубокие ссылки открывают нужный раздел напрямую.

**Примечание:** В Angular-источнике навигация — стандартный Angular Router; синхронизация через query params.

---

## US-103: Обработка больших объёмов данных

**Цель:** Приложение остаётся отзывчивым при отображении длинных списков рейсов/расписания.

**Путь клиента:**

1. **Загрузка данных — большой ответ API** — приходит расписание с сотнями рейсов.
2. **Рендер списка — оптимизированный вывод** — используется виртуализация либо разумная пагинация/ограничение.
3. **Скролл — плавная прокрутка** — браузер не проседает по FPS.
4. **Фильтрация и сортировка — мгновенная реакция** — действия не вызывают полной перерисовки.
5. **Освобождение памяти — unsubscribe при уходе** — подписки и кэши корректно очищаются.

**Критерии приёмки:**

- ✅ Список из сотен элементов рендерится без ощутимых задержек.
- ✅ Скролл остаётся плавным на типовых устройствах.
- ✅ Фильтрация и сортировка не «замораживают» UI.
- ✅ При уходе со страницы не остаётся зомби-подписок.
- ✅ Память приложения не растёт неограниченно при повторных поисках.

**Примечание:** Тестовый сценарий производительности; конкретный механизм (виртуализация/пагинация) — на выбор реализации.

---

## US-104: Кэширование ответов (CacheService 30s)

**Цель:** Повторные одинаковые запросы к API обслуживаются из кэша, чтобы снизить нагрузку и ускорить отклик.

**Путь клиента:**

1. **Первый запрос — CacheService.get(key, options)** — кэш пуст, запрос уходит на сервер.
2. **Сохранение ответа — CacheService.set(key, options, result)** — результат кладётся в `requestsMap` по ключу.
3. **Таймер инвалидации — setTimeout 30s** — через `INVALID_TIMEOUT = 30 * 1000` запись удаляется.
4. **Повторный запрос в пределах окна — быстрый ответ** — `get()` возвращает закэшированный `result`.
5. **Истечение 30 секунд — удаление ключа** — следующий запрос снова идёт на сервер.
6. **Ручная инвалидация — CacheService.delete(key)** — позволяет сбросить запись принудительно.

**Критерии приёмки:**

- ✅ `CacheService.set/get/delete` работают по in-memory Map.
- ✅ Таймаут инвалидации — 30 секунд (`INVALID_TIMEOUT = 30 * 1000`).
- ✅ Повторный запрос в пределах окна не порождает сетевой вызов.
- ✅ После таймаута запись удаляется и следующий запрос уходит на сервер.
- ✅ `delete(key)` очищает указанную запись вручную.

**Примечание:** Источник истины — `shared/services/cache.service.ts`. Таймер на каждую `set()` перезапускается (`clearTimeout`), поэтому окно отсчитывается от последнего обновления записи.

---

## US-105: Обновление данных в реальном времени (SignalR)

**Цель:** Данные онлайн-табло автоматически обновляются при поступлении серверных событий, чтобы пользователь видел актуальную информацию без ручной перезагрузки.

**Путь клиента:**

1. **Подключение к хабу** — при загрузке страницы результатов или деталей рейса `RefreshBoardService` / `RefreshService` инициализирует SignalR-соединение с `environment.urlForTrackerHub`.
2. **Подписка на события** — сервис подписывается на события `RefreshDate` (для списка рейсов) или `Refresh` (для деталей рейса по ID).
3. **Тихое обновление** — при поступлении события данные перезагружаются с флагом `silent`, без показа индикатора загрузки, чтобы не отвлекать пользователя.
4. **Оверлей устаревших данных** — `FadeService` запускает таймер; при бездействии пользователя в течение `environment.refreshPauseMin` минут поверх страницы появляется полупрозрачный оверлей с сообщением «Данные устарели, обновите страницу!».
5. **Автоматический редирект** — если бездействие превышает `environment.refreshStopMin` минут, приложение перенаправляет пользователя на главную страницу (`/`).
6. **Очистка** — при уходе со страницы (`ngOnDestroy`) подписки SignalR и таймеры `FadeService` очищаются.

**Критерии приёмки:**

- ✅ На страницах результатов онлайн-табло устанавливается SignalR-соединение для получения обновлений.
- ✅ На странице деталей рейса подписка привязана к конкретному ID рейса.
- ✅ Обновление данных происходит без видимого индикатора загрузки (silent reload).
- ✅ После `refreshPauseMin` минут бездействия показывается полноэкранный оверлей (z-index 9999, полупрозрачный фон).
- ✅ После `refreshStopMin` минут бездействия происходит автоматический редирект на главную.
- ✅ SignalR-подписки и таймеры корректно очищаются при уничтожении компонентов.

**Примечание:** Функция актуальна только для онлайн-табло (реальное время). Расписание и карта полётов не используют SignalR. `FadeService` создаёт overlay программно через DOM API (`document.createElement`).

---

## US-106: Встроенный чат-бот

**Цель:** Пользователь может воспользоваться чат-ботом для получения справочной информации.

**Путь клиента:**

1. **Инициализация** — компонент `chat-bot` в `ngAfterViewInit` динамически создаёт `<script>` элемент с URL из `environment.chatBotScript`.
2. **Загрузка виджета** — скрипт монтируется к `#app-chat-bot-container`, загружая внешний чат-виджет.
3. **Использование** — после загрузки пользователь видит виджет чата и может задавать вопросы.

**Критерии приёмки:**

- ✅ Чат-бот загружается динамически через внешний скрипт из `environment.chatBotScript`.
- ✅ Виджет монтируется в контейнер `#app-chat-bot-container`.
- ✅ Ошибки загрузки скрипта не ломают остальной интерфейс.

**Примечание:** URL скрипта настраивается через конфигурацию окружения.

---

## US-107: Canonical URL и SEO мета-теги по страницам

**Цель:** Каждая страница приложения имеет корректные canonical URL и уникальные SEO мета-теги для правильной индексации поисковыми системами.

**Путь клиента:**

1. **Установка canonical** — `CanonicalService` формирует `<link rel="canonical">` для текущей страницы.
2. **Мета-теги по разделам** — каждый раздел (онлайн-табло, расписание, карта полётов) имеет собственные компоненты мета-тегов с уникальными ключами `SEO.*`:
   - Онлайн-табло: стартовая, результаты (по номеру/маршруту/отправлению/прибытию), детали рейса
   - Расписание: стартовая, результаты, детали рейса
   - Карта полётов: стартовая
3. **Динамические значения** — для страниц результатов и деталей мета-теги включают названия городов, даты и номера рейсов.
4. **OpenGraph** — для страниц деталей формируются `og:title`, `og:description`, `og:image` теги.
5. **noRobots** — на страницах с динамическими параметрами (результаты, детали) установлен `noRobots: true` для предотвращения индексации.

**Критерии приёмки:**

- ✅ Canonical URL корректен для каждой страницы.
- ✅ Мета-теги `title` и `description` уникальны для каждого типа страницы.
- ✅ Динамические страницы включают контекст (город, дата, номер рейса) в мета-теги.
- ✅ Стартовые страницы разделов индексируются (`noRobots: false`), страницы результатов — нет.
- ✅ OpenGraph-теги формируются для страниц деталей.

**Примечание:** Компоненты мета-тегов наследуют от `MetaBaseComponent` и переопределяют `translateTitle()`/`translateDescription()`.

---

## US-108: Отображение версии API (debug-режим)

**Цель:** В debug-режиме разработчик может видеть текущую версию API для диагностики.

**Путь клиента:**

1. **Проверка флага** — `AppVersionComponent` проверяет `settings.showDebugVersion` из `APP_SETTINGS`.
2. **Запрос версии** — если флаг включён, `NetworkService.getApiVersion()` загружает номер версии.
3. **Отображение** — версия выводится компонентом `app-version` рядом с навигацией.
4. **Скрытие баннера** — при включённом debug-режиме скрывается элемент `.banner--top`.

**Критерии приёмки:**

- ✅ В обычном режиме версия не отображается.
- ✅ При `showDebugVersion = true` показывается номер версии API.
- ✅ Компонент `app-show-debug` условно рендерит debug-информацию.

**Примечание:** Предназначено для разработки и тестирования, не для конечных пользователей.

---
