Files
ridebus-board/README.md
Zakhar Timoshenko e239de45dd feat: add passenger information and settings components
- Introduced `PassengerInfo.tsx` for displaying trip details, including upcoming trips and fare information.
- Added `PassengerSettings.tsx` for managing user-defined important places and fare settings.
- Implemented validation functions for passenger settings in `passenger-settings.mjs`.
- Created utility functions for transliterating stop names in `transliteration.ts`.
- Developed weather hint functionality in `weather-hint.ts` to provide precipitation probabilities.
- Established a mock server for testing passenger-related features in `passenger-server.mjs`.
- Added comprehensive tests for passenger guide logic and transliteration functionality.
2026-10-12 20:30:49 +03:00

124 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RideBus Board
Табло остановки: React + TypeScript + Vite, сервер Node.js / Express. Тёмная тема с фоном `#000000` и светлая с `#FFFFFF`, локальный шрифт Manrope, контрастная типографика, отдельные акцентные палитры, иконки `@vkontakte/icons` и официальный SVG-логотип из RideBus PWA. По умолчанию — **Полоцк, Швейная фабрика, stopId=1**. Реальный API подтверждает эту остановку и 22 маршрута.
## Запуск
Требуется Node.js 24+ (проверено с Node 24.14.1).
```sh
npm install
cp .env.example .env
# Добавьте RIDEBUS_API_KEY в .env.
npm run build
npm start
```
Откройте http://127.0.0.1:5180. Для разработки — `npm run dev`. В этом workspace ключ уже подключён локально из существующей конфигурации RideBus; `.env` исключён из Git и Docker. При отсутствии ключа первая загрузка включает явно обозначенный деморежим. Демонстрацию также можно открыть по `/?demo=1`; она никогда не подменяет реальные данные при сбое API.
## Возможности
- Главный экран целиком отдан рейсам: ближайший крупно и следующие 2–6 рейсов. Строки рассчитываются по фактической доступной высоте, нижняя панель занимает отдельное место. Если все строки не помещаются с читаемым размером текста, список сменяется страницами с явным счётчиком: каждые 7 секунд для телевизора и 10 секунд для уличного табло. В стандартном профиле на 879×593 и 390×667 шесть рейсов показаны страницами по три; на 855×723 четыре помещаются одновременно. В узкой компоновке время отправления находится под названием и не пересекается с ожиданием.
- Точная отмена выделяется непосредственно в строке рейса: время остаётся видимым, отсчёт заменяется пометкой «Отменён». Ближайшим становится следующий доступный рейс. Общие сообщения без однозначного времени и остановки показывают «Есть изменения» и сохраняют исходный текст предупреждения.
- Под ближайшим отправлением — время следующего рейса того же маршрута в том же направлении. Отменённые отправления пропускаются, завтрашняя дата отмечается один раз. Переключатель находится в настройках → «Экраны».
- При ожидании от 90 минут крупно показывается фактическое время отправления с подписью «Сегодня в» или «Завтра в». При приближении рейса возвращается обычный отсчёт. Режим можно отключить в настройках → «Экраны».
- Автоматический ночной режим включается после завершения движения по полному расписанию, если весь ночной перерыв до первого следующего рейса длиннее двух часов. На чёрном фоне — первый рейс, направление, часы без секунд и актуальные уведомления. Информация и реклама не сменяют этот экран. При оставшихся 60 минутах возвращается обычное табло; в настройках → «Экраны» можно выбрать 45 минут или выключить режим. Дневная тема и палитра восстанавливаются автоматически. По умолчанию режим автоматический, возврат за 60 минут.
- Настройки → «Оформление» → «Читаемость»: профили «Телевизор» / «Уличное табло» и расстояние «Близко» / «Средне» / «Далеко». Для улицы и дальнего просмотра текст крупнее, одновременно видимых строк меньше. Уличный профиль использует спокойное короткое появление вместо вертикальной прокрутки текста. Информационные карточки при дальнем просмотре показываются по одной.
- В шапке — название и направление конкретной площадки из API, часы и дата в `Europe/Minsk`. Поле «основная» не выдаётся за направление; если направления нет, оно не придумывается. Город, подпись «Остановка», «RideBus API» и «Все направления» убраны из шапки.
- Информационный экран: «Дальше по ближайшему рейсу», погода, QR-код и предупреждения. Схема показывает до четырёх следующих остановок ближайшего доступного рейса и время пути от текущей остановки по расписанию. Учитывает выбранный маршрут, отмены, укороченные и обратные рейсы; на конечной не придумывает продолжение. Неизвестное время отмечено «—». Горизонтальная линия плавно сдвигается влево, выделенное название остановки увеличивается, соседние остаются компактными — по мотивам [инфосистемы «Квант»](https://www.artlebedev.ru/kvant/moscow/). Это обзор поездки после посадки, а не положение автобуса: остановки сменяются каждые 4 секунды (6 для уличного профиля), сцена автоматически удлиняется до полного обзора. В узкой карточке название и схема подстраиваются под доступное место. Часы остаются в шапке, ближайший рейс показан в нижней панели; карточка не дублирует номер, направление и время отправления. В вертикальной и короткой компоновке карточки сменяются при следующих посещениях экрана; их также можно выбрать кнопками «Маршрут», «Погода», «RideBus».
- Настройки → «Экраны» → «Транслит названий остановок»: дополнительная строка латиницей над основным названием в схеме ближайшего рейса. Например, «Пушкинская» → «Pushkinskaya». По умолчанию выключен; сохраняется после перезагрузки. Это транслитерация русских и белорусских названий, без перевода. Работает в обеих темах и профилях экрана.
- «До важных мест»: до трёх заданных остановок назначения. Для каждой — ближайший прямой рейс, отправление, время в пути и прибытие по полному расписанию. Учитываются направление, конечная конкретного рейса, отмены и переход через полночь. При неизвестных временных смещениях показывается только отправление; без актуального полного расписания с порядком остановок карточка скрыта. Реальные места по умолчанию не выбраны. Их можно задать в настройках → «Экраны» или в админке устройства. На коротком экране места сменяются страницами каждые 7 секунд (10 для улицы); сцена удлиняется, чтобы показать все страницы.
- Напоминание о последнем рейсе появляется за 30 минут до последнего отправления данного маршрута в данном направлении перед ночным перерывом. Расчёт использует ту же политику ночного перерыва, включая рейсы после полуночи, и полный действующий календарь. Это расписание, а не обещание фактического движения. Напоминание видно и на главном экране, и в нижней панели информации; его можно отключить.
- Погодная подсказка — вероятность осадков в ближайший час из почасового прогноза Open-Meteo. Нулевая вероятность сохраняется; при отсутствии ближайшего часа или устаревшем прогнозе подсказка скрыта. Переключатель — «Вероятность осадков» в настройках экранов.
- «Оплата поездки»: крупная цена и способы оплаты для ближайшего доступного маршрута, с датой подтверждения. По данным пользователя от 11 октября 2026 года для Полоцка: автобус **1,05 BYN**, маршрутка **2,10 BYN**, оплата **QR-кодом или наличными**. Основные тарифы привязаны к городу и типу транспорта; для других городов не подставляются. Можно задать исключения для конкретных маршрутов. Явно удалённые тарифы остаются удалёнными; без подходящего подтверждённого тарифа карточка скрыта. QR-код RideBus открывает расписание; платёжный код не генерируется.
- В настройках → «Экраны» → «Блоки информации» каждый блок можно включить или отключить. Отключённые и незаполненные карточки пропускаются; при четырёх и более доступных блоках они показываются по одной, чтобы сохранить читаемость. При отключении всех блоков информационный экран исключается из обычного цикла; активные предупреждения о движении всё равно показываются. Выбор сохраняется в браузере; «Сбросить цикл» включает три базовых блока, сохраняя пассажирские настройки.
- Уведомления об отменах, объездах, задержках и другие сообщения оформлены простой карточкой с иконкой, номерами маршрутов и сроком действия. Исходные заголовки, тексты и детали отмен сохраняются. Статус предупреждений отображается только при их недоступности.
- Цикл 25 секунд / 8 секунд, настраиваемый. До ближайшего рейса ≤2 минут главный экран удерживается; можно отключить в настройках. Отмены и объезды исключают рекламную сцену из цикла.
- Опциональная рекламная сцена с текстом и длительностью; по умолчанию выключена, помечена как предпросмотр. Загрузка медиа и кампании в MVP не входят.
- Настройки остановки, маршрута, числа строк, интервалов и деморежима сохраняются на устройстве. Новая остановка загружает свои данные и свой кеш.
- Полный экран, пауза, ручная смена сцен. Клавиши: `S` — настройки; `F` — полный экран; `→` — следующий экран; `пробел` — пауза. В настройках `Esc` завершает изменение поля, затем закрывает меню без сохранения.
- Настройки разделены на четыре раздела: «Остановка», «Оформление», «Экраны», «Реклама». Разделы, закрытие и «Сохранить» всегда видны. Для браузеров с курсором от пульта (например, Яндекс Браузер на Mi Box) доступны постоянные кнопки «Выше / Ниже»: наведите курсор и нажмите `OK`. Они прокручивают содержимое выбранного раздела без колеса мыши и клавиатурных событий; у границы кнопка отключается. Черновик сохраняется при переходах между разделами.
- Если браузер передаёт клавиши страницы, стрелки переводят фокус между полями и прокручивают содержимое раздела. `OK / Enter` активирует кнопку, переключатель или выбор цвета; для списка и числа включает изменение значения стрелками. Повторный `OK / Enter` возвращает к навигации. Отключённые и скрытые поля пропускаются, выбранный элемент подсвечен. `Home / End` достигают крайних кнопок, `PageUp / PageDown` прокручивают раздел. `Tab` возвращает обычное клавиатурное управление; мышь и касание сохраняют стандартное поведение.
- Кнопки управления и курсор скрываются после 5 секунд без взаимодействия. Движение мыши, касание или клавиатура возвращают кнопки; открытые настройки и работа с клавиатурным фокусом не скрываются. Высота панели сохраняется, а освободившуюся ширину занимает подпись «Обновлено» справа.
- Изменившиеся цифры часов, времени отправления, ожидания и номеров маршрутов прокручиваются вертикально. Названия рейсов при смене страниц плавно сдвигаются тем же движением. Неизменившиеся позиции чисел остаются на месте; увеличение идёт вверх, уменьшение — вниз. При `prefers-reduced-motion` движение выключено.
- Для чёрного фона доступны лайм, янтарь, голубой, мята, лаванда и белый; для белого — синий, зелёный, кармин, фиолетовый, бирюзовый и графит. Акцент и логотип меняются вместе. Контраст каждой палитры с её фоном не меньше 7:1. Тема и выбранные цвета сохраняются отдельно.
- Длинные номера, включая «16тк», автоматически вписываются с отступами. Центрирование и нижнее выравнивание чисел рассчитаны по границам глифов шрифта. Ожидание от часа отображается как «2 ч 1 мин»; единицы увеличены и выровнены снизу. Завтрашние отправления имеют одну подпись времени с пометкой «завтра».
## Данные и работа без связи
Сервер запрашивает `GET /api/v1/stops`, `/stops/:id`, `/eta/stop/:id`, `/alerts/active`, передавая `X-City-Id` и серверный `X-API-Key`. Интерфейс опрашивает сервер каждые 20 секунд; одновременные запросы объединяются, ответ кешируется на 12 секунд. Ключ, ошибки и сырые ответы upstream не отправляются в браузер. Прокси разрешает только явно заданные GET-операции: `/api/config`, `/api/stops`, `/api/board`, `/api/timetable`, `/api/weather`.
Текущий ETA endpoint вычисляет время из расписания и исторических задержек, а не текущей GPS-позиции. Табло показывает время с пометкой **«По расписанию»**; историческая задержка не выдаётся за прогноз. Используются абсолютные дата и время, поэтому часы не запускают истёкшие рейсы повторно.
Полный кеш **выбранной остановки** загружается независимо через `/api/timetable`: все варианты `/routes/:id/schedules`, остановки маршрутов с временными смещениями и `/holidays/year/:year`. Сохраняются будние, субботние, воскресные и праздничные варианты, сроки действия и ограничения отдельных рейсов. Праздник без своего графика использует воскресный, как в RideBus API. Учитываются укороченные и обратные рейсы; прибытие после полуночи сохраняет настоящую дату и обслуживающий день. Обновление каждые 30 минут, ограниченная параллельная загрузка, одновременные запросы объединяются. Кеш публикуется целиком: сбой отдельного маршрута или календаря не заменяет предыдущую полную копию неполными данными.
При наличии полного кеша табло рассчитывает все прибытия сегодня и завтра локально. Поэтому оно продолжает выбирать рейсы после полуночи без подключения; завтрашние времена явно помечены «завтра». Кеш хранится в IndexedDB и резервно в localStorage отдельно по городу и площадке. Срок использования без обновления ограничен **7 сутками**; по истечении этого срока табло ожидает свежую загрузку. Переводы рабочих дней могут отражаться только в календаре и расписании, которые предоставляет API. Демоданные не сохраняются вместо реального расписания. Если полный кеш ещё не получен, используется прежний снимок до трёх ближайших рейсов каждого маршрута, только для его исходного дня и не старше суток.
При сбое появляется пометка сохранённых данных. Истёкшие рейсы удаляются. Если сохранённые рейсы закончились, табло честно сообщает об этом и продолжает переподключение. Предупреждения фильтруются по городу, маршрутам, срокам действия; сохраняются полный текст и детали отмены. Полный кеш расписания не подтверждает актуальность предупреждений — без свежего ответа API это явно указано.
Для сопоставления отмен используется точный формат действующего Telegram-импортера API: дата, «не выполняются рейсы», название остановки и перечень времён; поддерживаются секции отдельных маршрутов. Сохраняются отправная остановка, время и день обслуживания, чтобы отмена правильно сопоставлялась на следующих остановках и после полуночи. Неоднозначные названия площадок или несколько совпавших рейсов не помечаются отменёнными автоматически. Устаревшие, будущие и истёкшие предупреждения не исключают отправления.
QR-код создаётся локально, без стороннего сервиса генерации, и открывает `https://pwa.ridebus.by/?city=1&stop=1` для текущей остановки (параметры меняются вместе с площадкой). Адрес PWA можно переопределить серверной переменной `BOARD_PWA_URL`. В карточке — большой неподвижный QR, одна инструкция и ссылка. Размер кода рассчитывается по свободному месту. QR использует скруглённые чёрные модули, белый фон и свободное поле шириной 4 модуля. Декодирование нескольких ссылок и QR на снимках страницы проверено автоматически.
Погода запрашивается независимо через `/api/weather` у [Open-Meteo](https://open-meteo.com/en/docs) по публичным координатам остановки из API. В шапке — температура и состояние; на информационном экране — текущая температура, состояние и прогноз температуры с крупными иконками на три следующих часа. Это модельные погодные данные. Обновление раз в 10 минут; сохранённая погода помечается и показывается не дольше часа, затем скрывается. При отсутствии координат или сбое погоды транспорт продолжает работать. Данные сохраняются по городу и остановке; RideBus-ключ не передаётся поставщику погоды. Указаны источник и CC BY 4.0.
Бесплатный Open-Meteo подходит для прототипа; [условия сервиса](https://open-meteo.com/en/terms) требуют коммерческую подписку для приложения с рекламой. Сервер уже поддерживает `OPEN_METEO_API_KEY` и автоматически выбирает коммерческий endpoint, ключ остаётся на сервере. Панель оператора планируется отдельно в `polotsk-transit-api`.
Production-сборка устанавливает Service Worker, сохраняющий оболочку, шрифты и иконки для перезагрузки без сети. Требуются **HTTPS или localhost** и хотя бы одна успешная загрузка; в режиме разработки Service Worker выключен. API-ответы не кешируются Service Worker. В приватном режиме браузера постоянное хранилище может быть недоступно.
## Проверки
```sh
npm test
npm run build
npm run probe
```
Тесты проверяют календарное время, устаревшие снимки, идентичность города/остановки, источники ETA, многомаршрутные предупреждения, сбои отдельных источников, объединение запросов и HTTP-контракт. `probe` — проверка реального API без вывода ключа.
## Развёртывание
### Удалённое управление из polotsk-transit-api
Сначала примените миграцию `backend/migrations/016_board_devices.sql` в базе API и разверните обновлённые backend/frontend `polotsk-transit-api`. Для существующей базы выполняйте именно новую миграцию: старый общий скрипт проекта повторяет все SQL-файлы, включая создание уже существующих таблиц.
В `.env` табло включите `BOARD_REMOTE_SETTINGS=true`. `RIDEBUS_API_BASE_URL` указывает на обновлённый сервер API, `RIDEBUS_API_KEY` остаётся только на сервере Board. Необязательный `BOARD_ADMIN_URL` — полный HTTPS-адрес раздела устройств вашей админки. Compose передаёт все эти параметры в контейнер. После изменения переменных пересоберите/пересоздайте контейнер.
При первом запуске каждый браузер показывает собственный четырёхзначный код, действующий 5 минут. Администратор открывает «Устройства», добавляет табло по коду, выбирает название, город (через переключатель сети), конкретную площадку и настройки. Код одноразовый; после привязки табло запускается автоматически. Смена остановки, маршрута, темы, палитры, профиля, числа строк, экранов, информационных блоков и рекламы применяется в течение следующего опроса, без обновления страницы. Деморежим удалённой конфигурации выключен.
Во время привязки опрос каждые 3 секунды, после — каждые 15. Сигнал работы каждые 30 секунд и отдельное подтверждение после применения новой версии. Админка различает «Ожидают применения» и «Настройки применены»; после 90 секунд без связи устройство считается офлайн. Конкурирующие изменения обнаруживаются по версии, старое сохранение не перезаписывает новое.
Идентичность устройства — отдельный профиль браузера на отдельном origin. Вкладки одного браузера с одним адресом относятся к одному устройству. Токен хранится в HttpOnly cookie и не передаётся в JavaScript или URL; на API сохраняется только SHA-256 хеш. Для сервера с HTTPS нужен `Secure` cookie: передавайте `X-Forwarded-Proto: https` через reverse proxy либо задайте `BOARD_COOKIE_SECURE=true`. Очищенные cookies, смена адреса или профиля требуют новой привязки. Не запускайте табло в приватном режиме.
При потере управления табло использует последнюю проверенную конфигурацию и существующий кеш расписания. Локальная конфигурация автономного режима хранится отдельно. После привязки меню на телевизоре показывает состояние управления, имя устройства и версию; параметры меняются в админке. «Отвязать» отзывает токен; экран прекращает показ при следующей связи с API. Для новой привязки нажмите «Подключить заново» на табло. При отключённом `BOARD_REMOTE_SETTINGS` сохраняется прежняя локальная настройка.
Для локальной совместной проверки после сборки обоих проектов: `node test/fixtures/device-flow-server.mjs`. Это явно обозначенный тестовый стенд с настоящими устройственными обработчиками и UI, но с памятью вместо PostgreSQL и тестовым транспортом. Админка — `http://127.0.0.1:5190/devices`, табло — `http://127.0.0.1:5191`; тестовый вход `board-fixture / fixture-only`. Этот файл не входит в Docker runtime и не предназначен для публикации.
```sh
docker compose up --build -d
```
Контейнер доступен на `127.0.0.1:5180`. Для удалённого плеера поставьте HTTPS reverse proxy перед этим портом. API-ключ передаётся только через environment; не включайте его в URL или frontend-переменные `VITE_*`. `/healthz` проверяет работу процесса, а не доступность RideBus API.
На Linux-плеере откройте страницу Chromium в kiosk mode и настройте автозапуск графической сессии. Пример службы — `deploy/ridebus-kiosk.service`; пути Chromium и переменные дисплея зависят от ОС. `Restart=always` восстанавливает процесс браузера. Docker восстанавливает сервер после перезагрузки. Настройку и испытание на физическом плеере нужно выполнить отдельно: служба не обнаруживает зависший renderer, а удалённый мониторинг устройств в MVP отсутствует.
Изменения кода выкладывайте целой сборкой; Service Worker загружает новую оболочку в фоне, активируя обновление при следующем закрытии/перезапуске табло.
Ночной режим использует полный кеш с действующим сроком и календарём для вчера/сегодня/завтра. Короткий список ETA, истёкший кеш, неизвестные границы движения или отсутствие следующего рейса не включают ночной экран. Прибытия после полуночи сохраняют свой день обслуживания: последний автобус остаётся на обычном экране до окончания минутного запаса посадки. Отменённые рейсы пропускаются только по действующим достоверным уведомлениям. Фильтр маршрута ограничивает ночной режим тем же маршрутом. Длительность всего ночного перерыва определяется по расписанию, поэтому новый запуск или перезагрузка в середине перерыва сохраняет ночной экран до выбранного порога 45/60 минут. Например, после рейса Автовокзала в 00:31 и перед рейсом в 05:15 режим активен в 03:31 и возвращает обычный экран в 04:15 при пороге 60 минут или в 04:30 при пороге 45 минут.
Новые поля конфигурации устройства — `nightMode: "auto" | "off"` и `nightWakeMinutes: 45 | 60`. Они доступны и в админке «Устройства», хранятся в существующем JSON настроек. Старые локальные и удалённые настройки получают значения `auto / 60`; новая SQL-миграция не нужна. Для удалённой настройки обновите API, админку и Board. Тестовый ночной стенд: `node test/fixtures/night-server.mjs`, приложение `http://127.0.0.1:5193/`, сценарии `/__fixture` (явные тестовые расписание и часы).
Для проверки на сохранённом публичном расписании стенд принимает `NIGHT_TIMETABLE_PATH=/tmp/timetable.json` и `NIGHT_SCENARIO=avtovokzal` (03:31), `avtovokzalWake60` (04:15), `avtovokzalWake45` (04:30). Срок кеша обновляется под тестовые часы; в направлении остановки отображается «Тестовые часы». Производственные данные устройств и БД не изменяются.
Если API записывает ранний рейс 00:xx в новый календарный день, ночная логика дополнительно проверяет короткое продолжение предыдущего блока движения и первый длинный перерыв после него. Это позволяет включить режим после реального рейса 00:40 перед первым утренним в 05:44, не скрывая непрерывное ночное движение или дневные паузы. Даты рейсов и условия календаря при этом не изменяются.
Схема маршрута использует порядок и временные смещения остановок из полного кеша (`routes[].stops`) и конечную конкретного рейса (`endStopId`). Метаданные загружаются вместе с расписаниями, дополнительных запросов при смене рейса нет. Старые кеши без этих полей остаются пригодны для табло, но схема появляется после обновления полного расписания. Переключатель «Дальше по ближайшему рейсу» сохраняет прежний ключ `infoServiceEnabled`, поэтому существующие настройки локального и удалённого управления совместимы; миграция БД не требуется. Транслит управляется флагом `journeyTranslitEnabled`; в старой конфигурации он считается выключенным. Для удалённого переключения обновите табло, backend и frontend `polotsk-transit-api`: опция находится в настройках устройства, в блоке информации.
В обоих профилях схема анимирует только `transform` и `opacity`: ячейки имеют постоянную ширину, крупный и компактный варианты названия подбираются заранее и не пересоздаются при каждом переходе. В уличном профиле движение спокойнее. Схема не перерисовывается из-за тиков часов, если её данные не изменились; на конечной линия заканчивается у последнего настоящего маркера.
Для локального сравнения работы подбора шрифтов можно запустить `JOURNEY_PERF=1 node test/fixtures/night-server.mjs`. Параметр `BOARD_FIXTURE_DIST` позволяет использовать сохранённую старую сборку. Наблюдатели пишут `JOURNEY_PERF` в консоль: изменения размеров текстовых блоков, записи размера шрифта и замены заголовков за 1200 мс после смены остановки. Это диагностический стенд, а не замер FPS устройства; он не включён в рабочее табло и Docker runtime.
Пассажирские настройки устройства: `infoPlacesEnabled`, `infoFareEnabled`, `lastTripNoticeEnabled`, `weatherHintEnabled` (по умолчанию `true`); `places: [{stopId, name}]` (до 3); `fareByType: [{cityId, type, price, payment, checkedAt}]` (до 2, `type: bus | minibus`); `fareRules: [{routeId, price, payment, checkedAt}]` (до 32). Дата `checkedAt` — действительная дата `YYYY-MM-DD`, будущая дата не публикует тариф раньше срока. Сервер проверяет принадлежность остановок городу, маршрутов — площадке, основных тарифов — городу устройства. Старые конфигурации получают совместимые значения; миграция SQL не нужна. Для удалённого управления обновите backend/frontend `polotsk-transit-api` и Board вместе.
Локальный стенд для этих блоков: `node test/fixtures/passenger-server.mjs`, затем `http://127.0.0.1:5194/__fixture`. Кнопки выбирают ближайший тип транспорта и открывают табло с явными тестовыми остановками, расписанием и погодой. Стенд использует отдельный origin, не обращается к рабочей БД и не изменяет настройки основного табло.