- Implemented StatusWidget component for embedding RideBus service status. - Created widget-client.js for client-side interaction with the status widget. - Developed install-widget-integrations script to automate integration into PWA, website, and admin projects. - Introduced server-side widget rendering in widget.mjs for generating status widget HTML. - Added comprehensive tests for widget functionality and integration.
227 lines
37 KiB
Markdown
227 lines
37 KiB
Markdown
# RideBus Status
|
||
|
||
Независимый сайт статуса RideBus в визуальном стиле `ridebus-pwa` и `ridebus-website`: Manrope, общая палитра, логотип, округлые карточки, иконки `@vkontakte/icons`, светлая и тёмная темы, русский и английский языки.
|
||
|
||
## Запуск
|
||
|
||
Нужен **Node.js 24+**. SQLite встроен в Node; внешняя база для первого запуска не требуется.
|
||
|
||
```sh
|
||
npm ci
|
||
cp .env.example .env
|
||
npm run dev
|
||
```
|
||
|
||
Для браузерных проверок нужен Chromium: `npm run browsers:install`. На macOS автоматически используется установленный Google Chrome; на другом хостинге можно указать `STATUS_BROWSER_EXECUTABLE_PATH`. Dockerfile устанавливает Chromium и системные зависимости. Если браузер недоступен, сценарий становится непроверенным, а доступность страницы остаётся отдельным показателем.
|
||
|
||
Страница: `http://localhost:5188`. Все пять реальных адресов уже настроены. Проверки выполняются сервером даже без открытого браузера. История начинается с первого запуска; пропущенные дни остаются без данных.
|
||
|
||
```sh
|
||
npm run build
|
||
npm start
|
||
```
|
||
|
||
В production один Node-процесс обслуживает собранный интерфейс, публичный API и мониторинг. При завершении процесса очередная проверка дожидается окончания, затем закрывается база.
|
||
|
||
## Сервисы и настройки
|
||
|
||
### Встраиваемый виджет
|
||
|
||
Виджет показывает текущую доступность и состояние сценариев выбранных сервисов. Используются те же правила свежести и плановых работ, что и на основной странице. Зелёный статус исчезает при истечении измерений, сетевой ошибке или отсутствии ответа сайта мониторинга. История и транспортные данные для виджета не загружаются.
|
||
|
||
Публичный iframe: `/embed?services=pwa,api&lang=ru&theme=auto`; параметры `services`, `lang` (`ru`/`en`) и `theme` (`light`/`dark`/`auto`) необязательны. Без списка проверяются все сервисы. Публичный `/api/widget?services=pwa,api` возвращает компактную сводку. Демо-режим виджетом не поддерживается. Обновление — каждые 30 секунд, в скрытой вкладке запросы приостанавливаются.
|
||
|
||
В исходники трёх проектов добавлен компонент из `integrations/StatusWidget.tsx`: основной сайт (`website,api`), PWA (`pwa,api`), админка (`admin,api`). Он проверяет origin и источник сообщений iframe и показывает запасную ссылку, если мониторинг недоступен. Iframe не получает ключи API или токены приложения; запросы данных идут с `credentials: omit`, referrer отключён.
|
||
|
||
После выбора хостинга задайте URL сайта статуса перед сборкой клиентов:
|
||
|
||
| Проект | Переменная |
|
||
| --- | --- |
|
||
| ridebus-website | `NEXT_PUBLIC_STATUS_URL=https://your-status-domain.example` |
|
||
| ridebus-pwa | `VITE_STATUS_URL=https://your-status-domain.example` |
|
||
| polotsk-transit-api/frontend | `VITE_STATUS_URL=https://your-status-domain.example` |
|
||
|
||
Без адреса production-виджет скрыт; в development используется `http://127.0.0.1:5188`. Значения URL публичные и не являются секретами. После изменения необходима новая сборка. Если у хостинга задан CSP, разрешите домен мониторинга в `frame-src`. Виджет также можно вставить в другой сайт обычным iframe шириной 260px и высотой 44px с `title`, `referrerpolicy="no-referrer"` и sandbox `allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox`.
|
||
|
||
| Сервис | Проверка по умолчанию |
|
||
| --- | --- |
|
||
| API | `https://api.ridebus.by/health`: HTTP 200 и JSON `status: healthy` |
|
||
| Сайт | `https://ridebus.by/`: HTTP 200 и HTML |
|
||
| PWA | `https://pwa.ridebus.by/`: HTTP 200 и HTML |
|
||
| Табло | `https://board.ridebus.by/`: HTTP 200 и HTML |
|
||
| Админка | `https://admin.ridebus.by/`: HTTP 200 и HTML |
|
||
|
||
`config/services.json` позволяет добавлять сервисы, задавать переводы, публичные ссылки и правила проверки. Поддерживаются виды `html`, `json`, `http`; для JSON — `expectedJson: { "path": "status", "value": "healthy" }`. Для защищённого запроса укажите `headerEnv: { "X-API-Key": "MY_SECRET_VARIABLE" }`, а значение храните в переменной окружения.
|
||
|
||
URL можно переопределить через `STATUS_API_URL`, `STATUS_WEBSITE_URL`, `STATUS_PWA_URL`, `STATUS_BOARD_URL`, `STATUS_ADMIN_URL`. Значение `off` отключает проверки сервиса и показывает отсутствие данных. Публичная ссылка остаётся из конфигурации. Любые URL проверок и секретные заголовки остаются на сервере; публичный API возвращает только безопасную сводку. При перенаправлении на другой origin пользовательские заголовки удаляются.
|
||
|
||
Для дополнительной проверки данных API задайте `RIDEBUS_API_KEY`. Тогда кроме `/health` проверяется непустой список городов по `STATUS_API_DATA_URL` (по умолчанию `/api/v1/cities`). Без ключа проверяется только здоровье API: это **не проверка подключения к БД**. HTML-проверки подтверждают загрузку страницы, а не полный пользовательский сценарий, авторизацию или получение отправлений.
|
||
|
||
### Пользовательские сценарии
|
||
|
||
В конфигурации для API, PWA и табло задан `scenario`. Их проверки выполняются отдельным циклом раз в 300 секунд и не задерживают обычные проверки доступности:
|
||
|
||
- **API:** с серверным `RIDEBUS_API_KEY` получить города, остановки выбранного города и ETA контрольной остановки. `X-City-Id` отправляется только городским запросам.
|
||
- **PWA:** через публичный `/api/data` получить города, остановки и отправления, затем открыть приложение с выбранной остановкой в новом браузерном контексте и дождаться отображения данных.
|
||
- **Табло:** получить `/api/config`, остановки и `/api/board`, затем проверить отображение в браузере. Если табло требует сопряжения, нужна cookie **отдельного существующего тестового устройства** в `STATUS_BOARD_TEST_COOKIE`. С этой cookie монитор также получает `/api/device/status` и использует город и остановку, назначенные устройству в админке. Общая `/api/config` содержит настройки автономного табло и может указывать другую остановку. Отсутствие доступа, незавершённое сопряжение или отозванное устройство отмечаются как непроверенный сценарий, не как сбой табло.
|
||
|
||
Контрольный город выбирается автоматически (Полоцк при наличии), остановка — из каталога. Можно закрепить их через `STATUS_TEST_CITY_ID` и `STATUS_API_TEST_STOP_ID`, `STATUS_PWA_TEST_STOP_ID`, `STATUS_BOARD_TEST_STOP_ID`. Для сопряжённого табло город и остановка всегда берутся из настроек устройства; `STATUS_BOARD_TEST_STOP_ID` применяется только к автономному табло. Пустые ближайшие отправления, в том числе ночью, допустимы. Некорректный ответ, ошибки маршрутов или неполные данные проваливают проверку.
|
||
|
||
В `STATUS_BOARD_TEST_COOKIE` нужно значение заголовка Cookie целиком: `"имя=value; другое_имя=value"`, без префикса `Cookie:`. Храните его в кавычках в `.env`; в чат и публичную конфигурацию cookie не добавляйте. После добавления или обновления перезапустите сервер. Истёкший тестовый сеанс снова отображается как неподключённая проверка.
|
||
|
||
Переход к проверке настроек сопряжённого устройства начинает отдельную историю сценария табло. Результаты прежней проверки общей остановки сохраняются в базе, но не считаются сбоями нового сценария; история обычной доступности сохраняется.
|
||
|
||
Браузерные контексты каждый раз новые: сохранённое расписание и service worker не маскируют проблему. Сеанс тестового табло загружается через `browserContext.addCookies()`. POST/PUT/PATCH/DELETE заблокированы, сопряжение новых устройств не выполняется; при использовании cookie запросы на другой origin также блокируются. Проверки браузера используют `prefers-reduced-motion: reduce` и включённый sandbox Chromium. PWA/табло проверяются без авторизации администратора; вход и CRUD админки не тестируются.
|
||
|
||
Интерфейс отдельно показывает доступность и результат сценария, этапы, время и причину неподключённой проверки. Подтверждённая проблема сценария создаёт инцидент. Проценты и дневные графики по-прежнему описывают обычные проверки доступности; успешный HTTP не делает сценарий автоматически рабочим.
|
||
|
||
`STATUS_BROWSER_CHECKS=off` оставляет проверки данных без проверки отображения и явно обозначается в подробностях. Настройки интервала, таймаута браузера и порога медленного полного сценария: `STATUS_SCENARIO_INTERVAL_SECONDS`, `STATUS_SCENARIO_TIMEOUT_MS`, `STATUS_SCENARIO_SLOW_MS`. Изменение `.env` требует перезапуска процесса.
|
||
|
||
По умолчанию: интервал 60 секунд, таймаут 5 секунд, замедление выше 1500 мс, сбой после 3 неудачных проверок подряд, восстановление после 2 успешных. Первая неудача отображается как перебои. При отсутствии свежей проверки более 3 интервалов статус становится неизвестным, в том числе в уже открытом браузере.
|
||
|
||
Проверки выполняются параллельно, новый цикл не накладывается на предыдущий. Следующий цикл запускается через заданный интервал после окончания текущего. Кнопка обновления перечитывает сводку, не позволяет посетителям запускать дополнительные запросы к сервисам. Общий статус становится полностью рабочим только при свежих успешных данных по всем сервисам.
|
||
|
||
## История и инциденты
|
||
|
||
SQLite находится в `data/status.sqlite` (настраивается `STATUS_DB_PATH`). Проверки хранятся 90 дней. Графики показывают 7/30/90 календарных дней по UTC; конкретное время отображается в часовом поясе посетителя. Процент доступности — **доля успешных выполненных проверок**, а не SLA по длительности: периоды без мониторинга исключены. Частично покрытые дни заштрихованы. Изменение URL или правил создаёт новую историю для этого сервиса.
|
||
|
||
Подтверждённый сбой автоматически создаёт инцидент, восстановление закрывает его. Реальные инциденты не означают, что команда уже взяла проблему в работу. Ручные сообщения доступны через CLI на хостинге, без публичного административного API:
|
||
|
||
```sh
|
||
npm run incident -- list
|
||
npm run incident -- create service=api "title-ru=Проверяем задержки API" "title-en=Investigating API delays" "message-ru=Команда проверяет причину задержек." "message-en=The team is investigating response delays."
|
||
npm run incident -- resolve 1
|
||
```
|
||
|
||
Параметр `service` можно опустить для общего сообщения. CLI использует ту же БД и переменные окружения. При запуске в контейнере: `docker compose exec status node server/incidents-cli.mjs list`.
|
||
|
||
### Влияние и хронология
|
||
|
||
Для сбоев карточка и подробности объясняют, что ограничено для пассажира: загрузка приложения, расписания или отправлений. Тексты RU/EN задаются в `impact` у сервиса в `config/services.json`. Сохранённые данные описаны как возможная помощь, с предупреждением об их актуальности. Альтернативный сервис предлагается только при его текущем рабочем статусе. Непроверенный сценарий и отсутствие тестового доступа не считаются подтверждённым сбоем.
|
||
|
||
Инцидент сохраняет начальное сообщение и все последующие обновления. Автоматическое обнаружение и восстановление подписаны как результаты мониторинга. Стадии работы команды добавляются явно через CLI:
|
||
|
||
```sh
|
||
npm run incident -- update 1 stage=investigating "message-ru=Проверяем загрузку расписания." "message-en=Investigating timetable loading."
|
||
npm run incident -- update 1 stage=identified "message-ru=Причина найдена, готовим исправление." "message-en=The cause is identified; preparing a fix."
|
||
npm run incident -- update 1 stage=monitoring "message-ru=Исправление применено, наблюдаем." "message-en=The fix is applied; monitoring."
|
||
```
|
||
|
||
Обновить можно ручной или автоматически созданный инцидент. Автоматический инцидент закрывается только после подтверждённого восстановления проверок; ручные стадии не изменяют результаты мониторинга. Ручной инцидент можно закрыть с пояснением:
|
||
|
||
```sh
|
||
npm run incident -- resolve 1 "message-ru=Работа восстановлена. Причина — ошибка источника данных." "message-en=Service restored. The cause was a data source error."
|
||
```
|
||
|
||
После закрытия любого инцидента можно добавить отдельный разбор, не меняя время восстановления:
|
||
|
||
```sh
|
||
npm run incident -- update 1 stage=postmortem "message-ru=Причина и меры для предотвращения повторения." "message-en=The cause and measures to prevent recurrence."
|
||
```
|
||
|
||
Публичного API записи нет. Старая история получает только события, уже зафиксированные в БД; промежуточные действия команды не придумываются.
|
||
|
||
### Расширенная статистика
|
||
|
||
Блок «Работа в цифрах» на главной странице позволяет выбрать сервис. Период 7/30/90 календарных дней задаётся общим переключателем истории. В подробностях сервиса также показаны длительность сбоев и последнее восстановление.
|
||
|
||
- **Время сбоев:** измеренная длительность автоматически подтверждённых инцидентов доступности и пользовательских сценариев в выбранном периоде. Начало — момент подтверждения, окончание — подтверждённое восстановление. Пересечения двух видов проверок считаются один раз.
|
||
- **Самый долгий инцидент:** наибольшая измеренная длительность одного инцидента в выбранном периоде. Ручные сообщения не являются измеренными сбоями.
|
||
- **Последнее восстановление:** последнее автоматическое восстановление по текущим настройкам проверок, в том числе за пределами выбранного периода. Указано, восстановилась доступность или сценарий. При отсутствии записи показано «Не зафиксировано».
|
||
- **Покрытие времени:** доля периода со свежими результатами всех подключённых видов проверок. Для сценария без тестового доступа покрытие отсутствует. Непроверенный сценарий не получает фиктивные `0` сбоев.
|
||
|
||
Длительность оценивается по сохранённым результатам: состояние действует до следующей проверки, максимум до границы свежести в 3 интервала. Плановые окна и более длинные пропуски исключены. Если часть инцидента не покрыта проверками, `≥` обозначает минимальную измеренную длительность. Это не SLA и не точное время начала сетевой аварии.
|
||
|
||
Для сценариев добавлена отдельная история `scenario_checks`. Старые последние состояния и инциденты сохраняются; отсутствовавшие измерения не восстанавливаются предположениями. Эта часть статистики начинает накапливаться после обновления. История сценариев очищается с той же политикой хранения, что и обычные проверки.
|
||
|
||
График отклика переключается между 24 часами и выбранным периодом. Ползунок показывает конкретный час или день, среднее время ответа и число проверок; пробелы остаются пробелами. Среднее и 95-й процентиль рассчитаны по успешным обычным проверкам. Изменение относительно предыдущего периода показывается только при достаточном количестве измерений и заполнении обоих окон. Для сравнения двух полных 90-дневных окон потребуется хранить не менее 180 дней (`STATUS_RETENTION_DAYS=180`); текущая политика по умолчанию остаётся 90 дней.
|
||
|
||
Сводка имеет внутренний кэш до 5 секунд, сбрасываемый при новых проверках и изменениях SQLite через CLI. Публичные HTTP-ответы по-прежнему `Cache-Control: no-store`.
|
||
|
||
### RSS для посетителей
|
||
|
||
На странице «Подписаться на обновления» выберите сервисы, язык и типы событий, затем скопируйте ссылку в своё RSS-приложение. Лента включает каждое обновление инцидента, разборы, анонсы работ, начало и окончание планового окна, отмену и завершение командой. Окончание окна не означает фактического завершения работ.
|
||
|
||
- `/feed.xml` — все сервисы и оба типа событий, RU по умолчанию.
|
||
- `/feed.xml?lang=en&services=api,pwa&types=incidents` — инциденты API/PWA на английском.
|
||
- `/feed.xml?lang=ru&services=board&types=maintenance` — работы табло.
|
||
|
||
Общие сообщения без конкретного сервиса включаются в любую выбранную ленту. Возвращаются последние 100 событий с постоянными GUID; повторное чтение не создаёт новое событие. Неизвестные сервисы, типы и пустой выбор отклоняются. RSS всегда содержит реальные данные; `demo=1` в ленте не поддерживается. Регистрации и серверной базы подписчиков нет. Частоту получения определяет RSS-приложение посетителя.
|
||
|
||
Задайте `STATUS_PUBLIC_URL` после выбора домена: он используется для постоянных ссылок в RSS и Telegram. За reverse proxy это должен быть публичный HTTPS-адрес сайта статуса. XML экранируется, тексты внутри описаний также экранируются для HTML в RSS-приложениях.
|
||
|
||
### Плановые работы
|
||
|
||
Работы хранятся отдельно от инцидентов. Укажите тексты RU/EN, сервисы и точное плановое окно с часовым поясом:
|
||
|
||
```sh
|
||
npm run maintenance -- schedule services=api,pwa starts=2026-10-13T02:00:00+03:00 ends=2026-10-13T02:30:00+03:00 "title-ru=Обслуживание API" "title-en=API maintenance" "message-ru=Возможны короткие перерывы в загрузке расписания." "message-en=Brief interruptions to timetable loading are expected."
|
||
npm run maintenance -- list
|
||
npm run maintenance -- finish 1
|
||
npm run maintenance -- cancel 2
|
||
```
|
||
|
||
Это пример команды, а не созданный анонс. Если `services` опущен, окно относится ко всем сервисам. `finish` завершает уже начавшиеся работы, `cancel` отменяет ещё не истёкшее окно. По окончании планового времени показывается «Плановое окно закончилось»; это не заявление команды о фактическом завершении работ.
|
||
|
||
Во время активного окна соответствующие карточки получают статус обслуживания. Проверки продолжаются, неудачные результаты остаются в истории и влияют на её процент. Ожидаемая недоступность не открывает новый инцидент и не создаёт уведомление. После окна подтверждение нового сбоя начинается заново. Уже существовавший инцидент закрывается только при настоящем восстановлении. Старая БД обновляется автоматически, без удаления истории.
|
||
|
||
### Уведомления владельцу в Telegram
|
||
|
||
Отправка выключена, пока не заданы **обе** переменные `TELEGRAM_BOT_TOKEN` и `TELEGRAM_CHAT_ID` в `.env` или секретах хостинга. Бот должен иметь доступ к выбранному чату; для личного чата сначала откройте бота и нажмите Start. Добавьте `STATUS_PUBLIC_URL` после выбора хостинга, чтобы сообщения содержали ссылку на сайт. Язык и часовой пояс: `STATUS_NOTIFY_LANG`, `STATUS_TIMEZONE`.
|
||
|
||
```sh
|
||
npm run notify -- status
|
||
npm run notify -- test
|
||
```
|
||
|
||
`test` **отправляет реальное тестовое сообщение** в настроенный чат. Проверка настройки самостоятельно ничего не отправляет. Уведомления создаются только для подтверждённого сбоя и восстановления доступности/сценария. Новых подписок посетителей этот этап не добавляет.
|
||
|
||
Очередь сохраняется в SQLite и продолжает доставку после перезапуска. При сетевых ошибках, Telegram 5xx или ограничении частоты отправка повторяется с задержкой, с учётом `retry_after`; восстановление не обгоняет уведомление о своём сбое. После 8 попыток запись помечается как неотправленная. Неактуальные события старше суток или от прежней конфигурации проверки не отправляются. Сбой доставки не останавливает мониторинг. Сетевой разрыв после приёма сообщения Telegram может привести к повторной доставке.
|
||
|
||
Секреты, cookie, ответы транспортного API и детали ошибок Telegram не попадают в публичную сводку, логи или сообщения. Автоматические тесты Telegram используют подставной транспорт.
|
||
|
||
`/?demo=1` — явно обозначенный пример с тестовыми данными. Он не пишет в рабочую БД и не выдаёт показатели за реальные. Основная страница всегда открывает реальные статусы. Настройки языка и темы сохраняются только локально в браузере; аналитики и сторонних трекеров нет.
|
||
|
||
Нижняя панель повторяет разметку, типографику, отступы и адаптацию `ridebus-pwa`; подпись — «Город ближе, когда всё работает». В обычной production-странице ссылка на пример скрыта. В разработке она доступна, а в явно открытой демонстрации остаётся возврат к реальным статусам. RSS и публичная JSON-сводка доступны из раздела «О мониторинге»; подписка также доступна из раздела инцидентов.
|
||
|
||
## Другой хостинг
|
||
|
||
Проект не привязан к Sites или инфраструктуре RideBus. Нужен хостинг, который поддерживает **постоянно работающий Node.js 24+ или Docker**, Chromium с sandbox, исходящий HTTPS и **постоянный диск**. Задайте команду сборки `npm ci && npm run build`, установите Chromium (`npx playwright install --with-deps chromium`, если он не установлен), команду запуска `npm start`, платформенный `PORT`, `HOST=0.0.0.0` и путь `STATUS_DB_PATH` внутри постоянного диска. HTTPS обеспечивается хостингом.
|
||
|
||
Используйте **один экземпляр** процесса для этой SQLite-БД. Не размещайте базу на сетевой файловой системе. Бесплатный тариф, который усыпляет процесс, оставит пробелы в мониторинге. Чисто статический или обычный serverless-хостинг потребует другого backend/планировщика и базы: фоновые проверки этой реализации там не выполняются.
|
||
|
||
### Netlify
|
||
|
||
Для **всего текущего проекта** Netlify не является прямой заменой Node/Docker-хостингу. [Functions выполняются во временной среде](https://docs.netlify.com/build/functions/overview/): текущий цикл таймеров и локальный SQLite-файл не являются постоянными сервисом и хранилищем. [Scheduled Functions ограничены 30 секундами](https://docs.netlify.com/build/functions/scheduled-functions/); [Background Functions — 15 минутами](https://docs.netlify.com/build/functions/background-functions/), поэтому они также требуют отдельного запуска и архитектуры мониторинга.
|
||
|
||
Netlify подходит для интерфейса `dist`, если мониторинг, SQLite и Chromium работают на отдельном независимом хостинге. Потребуется направить `/api/status` и `/feed.xml` на этот backend; простая публикация `dist` без него не даст работающие статусы и RSS. Полный вариант на Netlify возможен после переноса хранилища, например в [Netlify Database / PostgreSQL](https://docs.netlify.com/build/data-and-storage/netlify-database/), и адаптации проверок, блокировок и очереди к отдельным функциям. Такая миграция в этот этап не входит.
|
||
|
||
Для первого полноценного запуска проще разместить весь проект на независимом хостинге с постоянным Node-процессом/контейнером и диском. Netlify-конфигурация и публикация пока не создавались.
|
||
|
||
Docker:
|
||
|
||
```sh
|
||
docker compose up -d --build
|
||
```
|
||
|
||
История сохраняется в volume `status-data`. Не удаляйте volume при обновлении. Для резервной копии используйте SQLite backup либо остановите процесс и скопируйте БД вместе с WAL/SHM-файлами. Размещайте мониторинг на независимом от сервисов RideBus хостинге.
|
||
|
||
Compose запускает контейнер от пользователя `node`, использует init, отдельную shared memory и [официальный seccomp-профиль Playwright](https://github.com/microsoft/playwright/blob/v1.63.0/utils/docker/seccomp_profile.json) для sandbox Chromium. Профиль и его лицензия лежат в `deploy/`. На Linux-хосте должны быть разрешены непривилегированные user namespaces; запуск контейнера и браузера нужно проверить на выбранном хостинге. При недоступном sandbox сайт продолжит обычные HTTP-проверки и покажет непроверенный сценарий.
|
||
|
||
## API и проверка
|
||
|
||
- `GET /healthz`: здоровье самого приложения статуса, HTTP 200 независимо от сбоев RideBus.
|
||
- `GET /api/status?range=30`: реальная сводка без секретов, `Cache-Control: no-store`.
|
||
- `GET /api/status?range=30&demo=1`: явно помеченный пример. Поддерживаются только 7/30/90 дней.
|
||
- `GET /feed.xml?lang=ru&services=api,pwa&types=incidents,maintenance`: RSS 2.0 реальных событий, только GET/HEAD, `Cache-Control: no-store`.
|
||
|
||
```sh
|
||
npm test
|
||
npm run typecheck
|
||
npm run build
|
||
```
|
||
|
||
Серверные тесты проверяют реальные локальные HTTP-маршруты, подтверждение сбоев и восстановления, сохранение SQLite, отсутствие секретов в публичном ответе, таймауты и пробелы в истории. Для проверки интерфейса можно использовать реальные статусы и явно обозначенный пример `/?demo=1`.
|
||
|
||
Дополнительно проверяются плановые окна и миграция БД, сценарии данных, очередь и повторы Telegram. Браузерный тест использует настоящий Chromium/Chrome, локальный стенд, запрет записывающих запросов и CSS reduced motion; без установленного браузера он явно пропускается. Микроанимации интерфейса повторяют длительности и кривую движения из `ridebus-pwa`: появления, отклик на нажатие, стрелки ссылок и диалоги. При уменьшенном движении анимации и смещения отключены.
|