diff --git a/app/(main)/alerts/page.tsx b/app/(main)/alerts/page.tsx
index aa209043..4ae9b718 100644
--- a/app/(main)/alerts/page.tsx
+++ b/app/(main)/alerts/page.tsx
@@ -2592,7 +2592,7 @@ function AlertsContent() {
>
+
diff --git a/app/(main)/certificates/page.tsx b/app/(main)/certificates/page.tsx
index 4930cdce..5363d6a9 100644
--- a/app/(main)/certificates/page.tsx
+++ b/app/(main)/certificates/page.tsx
@@ -199,7 +199,7 @@ function CertPartReference() {
{b.title}
-
+
{b.lines.join("\n")}
diff --git a/app/(main)/containers/page.tsx b/app/(main)/containers/page.tsx
index cda03d14..87de557c 100644
--- a/app/(main)/containers/page.tsx
+++ b/app/(main)/containers/page.tsx
@@ -498,7 +498,7 @@ function ContainersContent() {
-
Контейнеры доступны с RouterOS 7.4+
+
Контейнеры доступны с RouterOS 7.4+
Поддерживаются Docker-совместимые образы. Требуется установка пакета container.
Сеть veth настраивается на устройстве. Создание контейнеров в интерфейсе пока недоступно.
diff --git a/app/(main)/filters/page.tsx b/app/(main)/filters/page.tsx
index eda9eb2b..41cbc9e4 100644
--- a/app/(main)/filters/page.tsx
+++ b/app/(main)/filters/page.tsx
@@ -256,7 +256,7 @@ function TypeChip({ type }: { type: "jump-host" | "exit-node" | "home-router" })
return (
- Не удалось загрузить Filters: {liveLoadError}
+ Не удалось загрузить Filters: {liveLoadError}
setLoadAttempt(n => n + 1)}>Повторить загрузку
}
diff --git a/app/(main)/uptime/page.tsx b/app/(main)/uptime/page.tsx
index 57c6a018..f49c782f 100644
--- a/app/(main)/uptime/page.tsx
+++ b/app/(main)/uptime/page.tsx
@@ -2441,7 +2441,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
{pingCatalogKnown ? filtered.length + " из " + probes.length + " проб" : "Каталог проб пока неизвестен"}
·
-
+
звезда — блок «Активные пробы»
@@ -2655,7 +2655,7 @@ function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId
/>
Подробный график RTT
-
+
({p.series.length} точ.)
diff --git a/components/app-switcher.tsx b/components/app-switcher.tsx
index d859788f..210e0820 100644
--- a/components/app-switcher.tsx
+++ b/components/app-switcher.tsx
@@ -46,7 +46,7 @@ export function AppSwitcher() {
{current.name}
{current.subtitle ? (
-
+
{current.subtitle}
) : null}
diff --git a/components/certificates/certificate-renew-settings.tsx b/components/certificates/certificate-renew-settings.tsx
index 074dd08b..4c34f957 100644
--- a/components/certificates/certificate-renew-settings.tsx
+++ b/components/certificates/certificate-renew-settings.tsx
@@ -122,6 +122,7 @@ export function CertificateRenewSettingsPanel({
{!confirmed ? "Неизвестно" : enabled ? "Включено" : "Выключено"}
diff --git a/components/data-grids/ospf-routes-data-grid.tsx b/components/data-grids/ospf-routes-data-grid.tsx
index 7a7fd7b2..febc98ea 100644
--- a/components/data-grids/ospf-routes-data-grid.tsx
+++ b/components/data-grids/ospf-routes-data-grid.tsx
@@ -136,12 +136,12 @@ function OspfRoutesDataGrid({ routes, emptyTitle }: OspfRoutesDataGridProps) {
accessorKey: "area",
header: ({ column }) => ,
cell: ({ row }) => (
- {row.original.area}
+ {row.original.area}
),
meta: {
headerTitle: "Область",
- headerClassName: DATA_GRID_CELL_PAD_LAST,
- cellClassName: DATA_GRID_CELL_PAD_LAST,
+ headerClassName: cn(DATA_GRID_CELL_PAD_LAST, "text-right"),
+ cellClassName: cn(DATA_GRID_CELL_PAD_LAST, "text-right"),
},
},
],
diff --git a/components/nav-user.tsx b/components/nav-user.tsx
index c18dc277..ae5531e5 100644
--- a/components/nav-user.tsx
+++ b/components/nav-user.tsx
@@ -152,7 +152,7 @@ export function NavUser() {
{name}
- {email}
+ {email}
diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md
index 7abf91a6..1d3b8ea9 100644
--- a/docs/ux-ui-external-acceptance.md
+++ b/docs/ux-ui-external-acceptance.md
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
-Актуальное продолжение 2026-10-10: завершены12.5.93–100, включая99 main1207/1208 с одним выявленным OSPF geometry failure, исправленным в101 delta5/5; context221/221 и backend171 node cases passed.101 frontend accessibility/geometry исправлен и развернут, live20/20; завершить его профильную запись/commit. Runtime backend ux-response100/frontend ux-a11y101. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
+Актуальное продолжение 2026-10-10: завершены12.5.93–101. Backend ux-response100/frontend ux-a11y101; main1207/1208 исходно, единственный OSPF geometry failure исправлен/delta5/5; context221/221, backend171 node cases, accessibility24 local/20 live passed в точных границах. Старые evidence восстановлены, credentials не менялись. Следующий12.5.102 — malformed nonempty PATCH confirmation не должен приниматься как{}, затем финальный контроль ограничений. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
@@ -951,3 +951,23 @@ Context221/221/0flaky/0skipped,7,6min на frontend101 working source/local swit
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
+
+## 12.5.101 — доступность общей оболочки, текста и OSPF-колонки (2026-10-10)
+
+| Поверхность / референс | До | Доработка |
+| --- | --- | --- |
+| Sidebar / [App Shell12](https://reui.io/preview/base/app-shell-12?ref=mcp) | Light muted token на тёмной rail:3,55:1 | Subtitle AppSwitcher/NavUser использует sidebar foreground/70, popup style сохранён |
+| Информация / [Settings16](https://reui.io/preview/base/settings-16?ref=mcp) | Alerts empty text opacity40; enabled badge3,62:1; containers/uptime hints<4,5 | Семантические foreground tokens без лишней прозрачности; локальный override badge, общий theme/vendor не менялись |
+| Справка CLI / Settings16 | Horizontal pre нельзя достичь клавиатурой |3 named regions/tabIndex0/visible focus; Tab/Shift+Tab/ArrowRight проверены |
+| Filters | HR light badge3,28:1; initial API error4,4:1 | Existing status-online/offline foreground для этих конкретных элементов |
+| OSPF / [Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Last area/backbone за pinned180px CIDR на320px | Last-column alignment вправо иtitle полной области; цельный CIDR/пиннинг/сортировка сохранены |
+
+Схема: общий context → читаемый status/empty text → таблица с pinned CIDR и доступной последней областью → named code region с keyboard scroll. Структура Frame/base-nova/ReUI Data Grid/Badge сохранена; не установлены новые компоненты, не изменены vendor internals/shared theme. ReUI adapting/quality и MCP Data Grid audit checklist проверены, props/types взяты из установленного API.
+
+[Протокол](ux-ui/acceptance/operational-a11y-2026-10-10.json).24 unique accessibility cases:5 sections +Filters503×dark/light×1280/320; violations0/document fit/no writes. Первые4 alerts failures были неверным fixture endpoint(/api/alerts/overview вместо/api/alerts),2 light Filters выявили реальный contrast initial error. Исправлены fixture/локальный error foreground; повторены только alerts/filters12 cases, остальные12 accepted cases не повторялись. Geometry5/5: исправленный failed main OSPF scenario +4 CIDR sizes. Context221/221 из99 работает на101 source, без регрессии source/permissions/checkpoints.
+
+Scoped TypeScript/новые components+test lint passed; старый NavUser set-state-in-effect error подтверждён наHEAD, whole-file clean не заявлен. Одна remote frontend Docker production build/full TypeScript/entry/node --check/compose passed, backend/lockfile/миграции прежние. Frontend ux-a11y101/version1.0.0-test.a11y101/image52b50784b914…; clean context ospf91 +9 source overrides, archive SHA в отчёте, label неGitSHA. Приватный .env backup, изменён толькоMMAPP_IMAGE_TAG.
+
+[Live20/20](ux-ui/acceptance/live-a11y-2026-10-10.json) настоящего frontend/backend безHTTP mocks:5 routes×2 themes×2 widths, axe0/pageerror0/overflow0/API writes0. Сертификатные3 blocks во всех4 combinations доступны Shift+Tab/Tab иArrowRight. [Светлая CLI/фокус320](ux-ui/screens/certificates-a11y-light-320-2026-10-10.png), [тёмные Filters320](ux-ui/screens/filters-a11y-dark-320-2026-10-10.png) просмотрены; остальные dark certificate/light filters snapshots сохранены. Code screenshot показывает горизонтально прокрученный focused block, его содержимое не обрезано в данных.
+
+Это scoped accessibility delta, не полная WCAG/все состояния/повтор всегоmain. Два CHR/credentials/configuration не менялись, только3 compose containers/backend/PG healthy. Следующий102 — клиент PATCH сейчас принимает nonempty malformed JSON как{}, что может выглядеть ACK; проверить/исправить его без изменения правила empty successful response, затем ограниченная профильная фиксация. Один локальный коммит без push; этап12.5 остаётся открытым.
diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md
index 8d4707ea..56917efa 100644
--- a/docs/ux-ui-plan.md
+++ b/docs/ux-ui-plan.md
@@ -1,12 +1,12 @@
# План реализации UX/UI по пятичасовым окнам
-Актуальное продолжение 2026-10-10: завершены12.5.93–100, включая99 main1207/1208 с одним выявленным OSPF geometry failure, исправленным в101 delta5/5; context221/221 и backend171 node cases passed.101 frontend accessibility/geometry исправлен и развернут, live20/20; завершить его профильную запись/commit. Runtime backend ux-response100/frontend ux-a11y101. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
+Актуальное продолжение 2026-10-10: завершены12.5.93–101. Backend ux-response100/frontend ux-a11y101; main1207/1208 исходно, единственный OSPF geometry failure исправлен/delta5/5; context221/221, backend171 node cases, accessibility24 local/20 live passed в точных границах. Старые evidence восстановлены, credentials не менялись. Следующий12.5.102 — malformed nonempty PATCH confirmation не должен приниматься как{}, затем финальный контроль ограничений. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-10
-Актуальное продолжение 2026-10-10: завершены12.5.93–100, включая99 main1207/1208 с одним выявленным OSPF geometry failure, исправленным в101 delta5/5; context221/221 и backend171 node cases passed.101 frontend accessibility/geometry исправлен и развернут, live20/20; завершить его профильную запись/commit. Runtime backend ux-response100/frontend ux-a11y101. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
+Актуальное продолжение 2026-10-10: завершены12.5.93–101. Backend ux-response100/frontend ux-a11y101; main1207/1208 исходно, единственный OSPF geometry failure исправлен/delta5/5; context221/221, backend171 node cases, accessibility24 local/20 live passed в точных границах. Старые evidence восстановлены, credentials не менялись. Следующий12.5.102 — malformed nonempty PATCH confirmation не должен приниматься как{}, затем финальный контроль ограничений. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md
index 8be88f93..7efb1e9d 100644
--- a/docs/ux-ui-progress.md
+++ b/docs/ux-ui-progress.md
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
-Актуальное продолжение 2026-10-10: завершены12.5.93–100, включая99 main1207/1208 с одним выявленным OSPF geometry failure, исправленным в101 delta5/5; context221/221 и backend171 node cases passed.101 frontend accessibility/geometry исправлен и развернут, live20/20; завершить его профильную запись/commit. Runtime backend ux-response100/frontend ux-a11y101. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
+Актуальное продолжение 2026-10-10: завершены12.5.93–101. Backend ux-response100/frontend ux-a11y101; main1207/1208 исходно, единственный OSPF geometry failure исправлен/delta5/5; context221/221, backend171 node cases, accessibility24 local/20 live passed в точных границах. Старые evidence восстановлены, credentials не менялись. Следующий12.5.102 — malformed nonempty PATCH confirmation не должен приниматься как{}, затем финальный контроль ограничений. Матрица docs/ux-ui-remaining-acceptance.md; этап12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -36,7 +36,27 @@
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
-| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–100 и ограничения ниже. Runtime backend ux-diagnostics89/frontend ux-ospf91. |
+| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–101 и ограничения ниже. Runtime backend ux-diagnostics89/frontend ux-ospf91. |
+
+## 12.5.101 — доступность общей оболочки, текста и OSPF-колонки (2026-10-10)
+
+| Поверхность / референс | До | Доработка |
+| --- | --- | --- |
+| Sidebar / [App Shell12](https://reui.io/preview/base/app-shell-12?ref=mcp) | Light muted token на тёмной rail:3,55:1 | Subtitle AppSwitcher/NavUser использует sidebar foreground/70, popup style сохранён |
+| Информация / [Settings16](https://reui.io/preview/base/settings-16?ref=mcp) | Alerts empty text opacity40; enabled badge3,62:1; containers/uptime hints<4,5 | Семантические foreground tokens без лишней прозрачности; локальный override badge, общий theme/vendor не менялись |
+| Справка CLI / Settings16 | Horizontal pre нельзя достичь клавиатурой |3 named regions/tabIndex0/visible focus; Tab/Shift+Tab/ArrowRight проверены |
+| Filters | HR light badge3,28:1; initial API error4,4:1 | Existing status-online/offline foreground для этих конкретных элементов |
+| OSPF / [Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Last area/backbone за pinned180px CIDR на320px | Last-column alignment вправо иtitle полной области; цельный CIDR/пиннинг/сортировка сохранены |
+
+Схема: общий context → читаемый status/empty text → таблица с pinned CIDR и доступной последней областью → named code region с keyboard scroll. Структура Frame/base-nova/ReUI Data Grid/Badge сохранена; не установлены новые компоненты, не изменены vendor internals/shared theme. ReUI adapting/quality и MCP Data Grid audit checklist проверены, props/types взяты из установленного API.
+
+[Протокол](ux-ui/acceptance/operational-a11y-2026-10-10.json).24 unique accessibility cases:5 sections +Filters503×dark/light×1280/320; violations0/document fit/no writes. Первые4 alerts failures были неверным fixture endpoint(/api/alerts/overview вместо/api/alerts),2 light Filters выявили реальный contrast initial error. Исправлены fixture/локальный error foreground; повторены только alerts/filters12 cases, остальные12 accepted cases не повторялись. Geometry5/5: исправленный failed main OSPF scenario +4 CIDR sizes. Context221/221 из99 работает на101 source, без регрессии source/permissions/checkpoints.
+
+Scoped TypeScript/новые components+test lint passed; старый NavUser set-state-in-effect error подтверждён наHEAD, whole-file clean не заявлен. Одна remote frontend Docker production build/full TypeScript/entry/node --check/compose passed, backend/lockfile/миграции прежние. Frontend ux-a11y101/version1.0.0-test.a11y101/image52b50784b914…; clean context ospf91 +9 source overrides, archive SHA в отчёте, label неGitSHA. Приватный .env backup, изменён толькоMMAPP_IMAGE_TAG.
+
+[Live20/20](ux-ui/acceptance/live-a11y-2026-10-10.json) настоящего frontend/backend безHTTP mocks:5 routes×2 themes×2 widths, axe0/pageerror0/overflow0/API writes0. Сертификатные3 blocks во всех4 combinations доступны Shift+Tab/Tab иArrowRight. [Светлая CLI/фокус320](ux-ui/screens/certificates-a11y-light-320-2026-10-10.png), [тёмные Filters320](ux-ui/screens/filters-a11y-dark-320-2026-10-10.png) просмотрены; остальные dark certificate/light filters snapshots сохранены. Code screenshot показывает горизонтально прокрученный focused block, его содержимое не обрезано в данных.
+
+Это scoped accessibility delta, не полная WCAG/все состояния/повтор всегоmain. Два CHR/credentials/configuration не менялись, только3 compose containers/backend/PG healthy. Следующий102 — клиент PATCH сейчас принимает nonempty malformed JSON как{}, что может выглядеть ACK; проверить/исправить его без изменения правила empty successful response, затем ограниченная профильная фиксация. Один локальный коммит без push; этап12.5 остаётся открытым.
## 12.5.100 — RouterOS: обрыв тела HTTP ответа больше не зависает (2026-10-10)
diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md
index 8ed83200..cb6e53e3 100644
--- a/docs/ux-ui-remaining-acceptance.md
+++ b/docs/ux-ui-remaining-acceptance.md
@@ -1,8 +1,8 @@
# Оставшаяся внешняя приёмка — 2026-10-10
-Это актуальная матрица после 12.5.100 (99 main/context в работе). Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
+Это актуальная матрица после 12.5.101. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
-Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-response100`, frontend `ux-ospf91`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты последнего сценария удалены, исходные11 коллекций×2 восстановлены; пароли не менялись.
+Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-response100`, frontend `ux-a11y101`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты последнего сценария удалены, исходные11 коллекций×2 восстановлены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -25,7 +25,7 @@
## Следующая последовательность
1. **12.5.95 завершён:** [source/context/compiled audit](ux-ui/acceptance/deployment-source-audit-2026-10-10.json), production source drift не выявлен; нормализация и исключения явно указаны.
-2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context в работе;100 partial-response hang исправлен/развёрнут. 101 — реальные a11y нарушения после main.
+2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 — nonempty malformed PATCH confirmation.
3. **Заключительный профильный контроль:** после последнего исправления собрать только затронутую часть, проверить её live и обновить матрицу. Общую регрессию выполнить один раз на согласованном runtime.
4. **Внешние зависимости:** Telegram/Portal/client OS/multi-WAN/scale проходят отдельную приёмку при наличии нужных аккаунтов, устройств и разрешений. Лабораторный результат двух CHR не закрывает их автоматически.
diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md
index f673233b..44a0877e 100644
--- a/docs/ux-ui-stage-12.md
+++ b/docs/ux-ui-stage-12.md
@@ -2256,3 +2256,23 @@ Context221/221/0flaky/0skipped,7,6min на frontend101 working source/local swit
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
+
+## 12.5.101 — доступность общей оболочки, текста и OSPF-колонки (2026-10-10)
+
+| Поверхность / референс | До | Доработка |
+| --- | --- | --- |
+| Sidebar / [App Shell12](https://reui.io/preview/base/app-shell-12?ref=mcp) | Light muted token на тёмной rail:3,55:1 | Subtitle AppSwitcher/NavUser использует sidebar foreground/70, popup style сохранён |
+| Информация / [Settings16](https://reui.io/preview/base/settings-16?ref=mcp) | Alerts empty text opacity40; enabled badge3,62:1; containers/uptime hints<4,5 | Семантические foreground tokens без лишней прозрачности; локальный override badge, общий theme/vendor не менялись |
+| Справка CLI / Settings16 | Horizontal pre нельзя достичь клавиатурой |3 named regions/tabIndex0/visible focus; Tab/Shift+Tab/ArrowRight проверены |
+| Filters | HR light badge3,28:1; initial API error4,4:1 | Existing status-online/offline foreground для этих конкретных элементов |
+| OSPF / [Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Last area/backbone за pinned180px CIDR на320px | Last-column alignment вправо иtitle полной области; цельный CIDR/пиннинг/сортировка сохранены |
+
+Схема: общий context → читаемый status/empty text → таблица с pinned CIDR и доступной последней областью → named code region с keyboard scroll. Структура Frame/base-nova/ReUI Data Grid/Badge сохранена; не установлены новые компоненты, не изменены vendor internals/shared theme. ReUI adapting/quality и MCP Data Grid audit checklist проверены, props/types взяты из установленного API.
+
+[Протокол](ux-ui/acceptance/operational-a11y-2026-10-10.json).24 unique accessibility cases:5 sections +Filters503×dark/light×1280/320; violations0/document fit/no writes. Первые4 alerts failures были неверным fixture endpoint(/api/alerts/overview вместо/api/alerts),2 light Filters выявили реальный contrast initial error. Исправлены fixture/локальный error foreground; повторены только alerts/filters12 cases, остальные12 accepted cases не повторялись. Geometry5/5: исправленный failed main OSPF scenario +4 CIDR sizes. Context221/221 из99 работает на101 source, без регрессии source/permissions/checkpoints.
+
+Scoped TypeScript/новые components+test lint passed; старый NavUser set-state-in-effect error подтверждён наHEAD, whole-file clean не заявлен. Одна remote frontend Docker production build/full TypeScript/entry/node --check/compose passed, backend/lockfile/миграции прежние. Frontend ux-a11y101/version1.0.0-test.a11y101/image52b50784b914…; clean context ospf91 +9 source overrides, archive SHA в отчёте, label неGitSHA. Приватный .env backup, изменён толькоMMAPP_IMAGE_TAG.
+
+[Live20/20](ux-ui/acceptance/live-a11y-2026-10-10.json) настоящего frontend/backend безHTTP mocks:5 routes×2 themes×2 widths, axe0/pageerror0/overflow0/API writes0. Сертификатные3 blocks во всех4 combinations доступны Shift+Tab/Tab иArrowRight. [Светлая CLI/фокус320](ux-ui/screens/certificates-a11y-light-320-2026-10-10.png), [тёмные Filters320](ux-ui/screens/filters-a11y-dark-320-2026-10-10.png) просмотрены; остальные dark certificate/light filters snapshots сохранены. Code screenshot показывает горизонтально прокрученный focused block, его содержимое не обрезано в данных.
+
+Это scoped accessibility delta, не полная WCAG/все состояния/повтор всегоmain. Два CHR/credentials/configuration не менялись, только3 compose containers/backend/PG healthy. Следующий102 — клиент PATCH сейчас принимает nonempty malformed JSON как{}, что может выглядеть ACK; проверить/исправить его без изменения правила empty successful response, затем ограниченная профильная фиксация. Один локальный коммит без push; этап12.5 остаётся открытым.
diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md
index 0ee253c9..d7a9ac9b 100644
--- a/docs/ux-ui-test-server.md
+++ b/docs/ux-ui-test-server.md
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
-Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-response100/version1.0.0-test.response100, frontend ux-ospf91/version1.0.0-test.ospf91; backend/PG healthy, frontend HTTP/browser passed. Основной блок IPsec77 принят в границах журнала, own fixtures/fake DB rows удалены; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние.
+Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-response100/version1.0.0-test.response100, frontend ux-a11y101/version1.0.0-test.a11y101; backend/PG healthy, frontend HTTP/browser passed. Основной блок IPsec77 принят в границах журнала, own fixtures/fake DB rows удалены; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -14,7 +14,7 @@
| PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed |
| Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data |
| Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска |
-| Образы приложения | mmapp-test/backend:ux-response100 и mmapp-test/frontend:ux-ospf91; запущены |
+| Образы приложения | mmapp-test/backend:ux-response100 и mmapp-test/frontend:ux-a11y101; запущены |
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
@@ -767,3 +767,23 @@ API502, history error/TXRX/Pingnull, probe error с прежними42/17/15/0;
**Live.** [Native/API/PG](ux-ui/acceptance/speed-response-body-2026-10-10.json), [browser](ux-ui/acceptance/browser-speed-response-body-2026-10-10.json). Relay сначала получил actual RouterOS done testing, затем передал headers/первые16 bytes настоящего JSON и оборвал body. Теперь API502/понятный неизвестный результат вместо pending, история/прежний synthetic snapshot42/17/15/0 согласованы. Один capped10Mbit/s TCP3s test, native1/noauto retry, PC/3202/2/new message/reload/document fits/pageerror0. Свои disabled probe/history удалены, original IDs совпали, credentials/configuration RouterOS не менялись. Это controlled HTTP socket loss после headers, не independent WAN failure/cancelled native command.
**Runtime.** Backend ux-response100/version1.0.0-test.response100/image797979da82d6…; одна сборка изspeed98 +client/test/package-script files, source archive SHA в отчёте. Entry/node --check/pg_restore18.6/compose --wait passed, приватный .env backup; обновлён только BACKEND_IMAGE_TAG, frontend ux-ospf91/dependencies/migrations прежние. Label response100 — имя блока, не Git SHA. Backend/PG healthy,3 compose containers. Одна локальная фиксация без push; main UI99 всё ещё выполняется на неизменённом frontend source, его итог не заявлен. Квота37% использовано. Следующий101 — подтверждённые нарушения контраста/keyboard code после main99, затем context/финальная сводка.
+
+## 12.5.101 — доступность общей оболочки, текста и OSPF-колонки (2026-10-10)
+
+| Поверхность / референс | До | Доработка |
+| --- | --- | --- |
+| Sidebar / [App Shell12](https://reui.io/preview/base/app-shell-12?ref=mcp) | Light muted token на тёмной rail:3,55:1 | Subtitle AppSwitcher/NavUser использует sidebar foreground/70, popup style сохранён |
+| Информация / [Settings16](https://reui.io/preview/base/settings-16?ref=mcp) | Alerts empty text opacity40; enabled badge3,62:1; containers/uptime hints<4,5 | Семантические foreground tokens без лишней прозрачности; локальный override badge, общий theme/vendor не менялись |
+| Справка CLI / Settings16 | Horizontal pre нельзя достичь клавиатурой |3 named regions/tabIndex0/visible focus; Tab/Shift+Tab/ArrowRight проверены |
+| Filters | HR light badge3,28:1; initial API error4,4:1 | Existing status-online/offline foreground для этих конкретных элементов |
+| OSPF / [Data Grid Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Last area/backbone за pinned180px CIDR на320px | Last-column alignment вправо иtitle полной области; цельный CIDR/пиннинг/сортировка сохранены |
+
+Схема: общий context → читаемый status/empty text → таблица с pinned CIDR и доступной последней областью → named code region с keyboard scroll. Структура Frame/base-nova/ReUI Data Grid/Badge сохранена; не установлены новые компоненты, не изменены vendor internals/shared theme. ReUI adapting/quality и MCP Data Grid audit checklist проверены, props/types взяты из установленного API.
+
+[Протокол](ux-ui/acceptance/operational-a11y-2026-10-10.json).24 unique accessibility cases:5 sections +Filters503×dark/light×1280/320; violations0/document fit/no writes. Первые4 alerts failures были неверным fixture endpoint(/api/alerts/overview вместо/api/alerts),2 light Filters выявили реальный contrast initial error. Исправлены fixture/локальный error foreground; повторены только alerts/filters12 cases, остальные12 accepted cases не повторялись. Geometry5/5: исправленный failed main OSPF scenario +4 CIDR sizes. Context221/221 из99 работает на101 source, без регрессии source/permissions/checkpoints.
+
+Scoped TypeScript/новые components+test lint passed; старый NavUser set-state-in-effect error подтверждён наHEAD, whole-file clean не заявлен. Одна remote frontend Docker production build/full TypeScript/entry/node --check/compose passed, backend/lockfile/миграции прежние. Frontend ux-a11y101/version1.0.0-test.a11y101/image52b50784b914…; clean context ospf91 +9 source overrides, archive SHA в отчёте, label неGitSHA. Приватный .env backup, изменён толькоMMAPP_IMAGE_TAG.
+
+[Live20/20](ux-ui/acceptance/live-a11y-2026-10-10.json) настоящего frontend/backend безHTTP mocks:5 routes×2 themes×2 widths, axe0/pageerror0/overflow0/API writes0. Сертификатные3 blocks во всех4 combinations доступны Shift+Tab/Tab иArrowRight. [Светлая CLI/фокус320](ux-ui/screens/certificates-a11y-light-320-2026-10-10.png), [тёмные Filters320](ux-ui/screens/filters-a11y-dark-320-2026-10-10.png) просмотрены; остальные dark certificate/light filters snapshots сохранены. Code screenshot показывает горизонтально прокрученный focused block, его содержимое не обрезано в данных.
+
+Это scoped accessibility delta, не полная WCAG/все состояния/повтор всегоmain. Два CHR/credentials/configuration не менялись, только3 compose containers/backend/PG healthy. Следующий102 — клиент PATCH сейчас принимает nonempty malformed JSON как{}, что может выглядеть ACK; проверить/исправить его без изменения правила empty successful response, затем ограниченная профильная фиксация. Один локальный коммит без push; этап12.5 остаётся открытым.
diff --git a/docs/ux-ui/acceptance/live-a11y-2026-10-10.json b/docs/ux-ui/acceptance/live-a11y-2026-10-10.json
new file mode 100644
index 00000000..6572fcc3
--- /dev/null
+++ b/docs/ux-ui/acceptance/live-a11y-2026-10-10.json
@@ -0,0 +1,172 @@
+{
+ "date": "2026-10-10",
+ "runtime": {
+ "backend": "ux-response100",
+ "frontend": "ux-a11y101"
+ },
+ "httpMocks": false,
+ "cases": [
+ {
+ "route": "alerts",
+ "theme": "dark",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "alerts",
+ "theme": "dark",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "certificates",
+ "theme": "dark",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 3
+ },
+ {
+ "route": "certificates",
+ "theme": "dark",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 3
+ },
+ {
+ "route": "containers",
+ "theme": "dark",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "containers",
+ "theme": "dark",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "filters",
+ "theme": "dark",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "filters",
+ "theme": "dark",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "uptime",
+ "theme": "dark",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "uptime",
+ "theme": "dark",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "alerts",
+ "theme": "light",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "alerts",
+ "theme": "light",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "certificates",
+ "theme": "light",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 3
+ },
+ {
+ "route": "certificates",
+ "theme": "light",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 3
+ },
+ {
+ "route": "containers",
+ "theme": "light",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "containers",
+ "theme": "light",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "filters",
+ "theme": "light",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "filters",
+ "theme": "light",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "uptime",
+ "theme": "light",
+ "width": 1280,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ },
+ {
+ "route": "uptime",
+ "theme": "light",
+ "width": 320,
+ "violations": [],
+ "documentFits": true,
+ "keyboardBlocks": 0
+ }
+ ],
+ "errors": [],
+ "writes": []
+}
\ No newline at end of file
diff --git a/docs/ux-ui/acceptance/operational-a11y-2026-10-10.json b/docs/ux-ui/acceptance/operational-a11y-2026-10-10.json
new file mode 100644
index 00000000..4e946b87
--- /dev/null
+++ b/docs/ux-ui/acceptance/operational-a11y-2026-10-10.json
@@ -0,0 +1,162 @@
+{
+ "date": "2026-10-10",
+ "runtime": {
+ "backend": "ux-response100",
+ "frontend": "ux-a11y101",
+ "frontendVersion": "1.0.0-test.a11y101",
+ "frontendImageId": "sha256:52b50784b914e3a7f224b9c8f491855831ea93cae9b0403fc07dc12602bfbccd"
+ },
+ "local": {
+ "uniqueAcceptedCases": 24,
+ "cases": [
+ {
+ "title": "Operational accessibility certificates dark 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility containers dark 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters dark 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility uptime dark 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility certificates dark 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility containers dark 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters dark 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility uptime dark 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility certificates light 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility containers light 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility uptime light 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility certificates light 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility containers light 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility uptime light 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility alerts dark 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters-error dark 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility alerts dark 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters-error dark 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility alerts light 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters light 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters-error light 1280",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility alerts light 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters light 320",
+ "status": "expected"
+ },
+ {
+ "title": "Operational accessibility filters-error light 320",
+ "status": "expected"
+ }
+ ],
+ "initial": {
+ "startTime": "2026-10-09T19:22:44.504Z",
+ "duration": 72349.357,
+ "expected": 14,
+ "skipped": 0,
+ "unexpected": 6,
+ "flaky": 0,
+ "explanation": "4 alerts cases used incorrect fixture path;2 light Filters cases exposed actual low-contrast initial error text;14 passed"
+ },
+ "repair": {
+ "startTime": "2026-10-09T19:27:49.049Z",
+ "duration": 29249.906,
+ "expected": 12,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0,
+ "explanation": "Correct alerts endpoint, valid Filters DTO, explicit503 error cases; reran only alerts/filters"
+ },
+ "ospfGeometry": {
+ "startTime": "2026-10-09T19:19:38.810Z",
+ "duration": 15471.285,
+ "expected": 5,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ },
+ "scopedTypeScript": true,
+ "lintNewComponentsAndTest": true,
+ "existingNavUserSetStateEffectError": 1
+ },
+ "live": {
+ "report": "live-a11y-2026-10-10.json",
+ "cases": 20,
+ "violations": 0,
+ "pageErrors": 0,
+ "apiWrites": 0,
+ "keyboard": "Certificate named regions: Shift+Tab/Tab, focus, ArrowRight scroll;3 blocks×4 theme/viewport combinations"
+ },
+ "deploy": {
+ "context": "build-ospf91 +9 working-tree source files",
+ "archiveSha256": "b6a6ff3056725f1527a4b739a64b43fe8f7248842500415d9abbe76758a27bc2",
+ "revisionLabel": "a11y101 (stage label, not Git SHA)",
+ "productionBuildAndTypeScript": true,
+ "entryNodeCheck": true,
+ "composeConfigAndFrontendStart": true,
+ "backendDependenciesAndMigrationsUnchanged": true
+ },
+ "limits": [
+ "Original main1207/1208 +scoped5/5 geometry delta, not entire main on late101 CSS",
+ "Context221/221 on101 working source accepted separately",
+ "Scoped axe/keyboard does not establish full WCAG certification or every possible error/dataset state",
+ "Fixed initial Filters503 text contrast; other unrelated semantic error surfaces not globally restyled"
+ ]
+}
diff --git a/docs/ux-ui/screens/certificates-a11y-dark-320-2026-10-10.png b/docs/ux-ui/screens/certificates-a11y-dark-320-2026-10-10.png
new file mode 100644
index 00000000..004e9ae2
Binary files /dev/null and b/docs/ux-ui/screens/certificates-a11y-dark-320-2026-10-10.png differ
diff --git a/docs/ux-ui/screens/certificates-a11y-light-320-2026-10-10.png b/docs/ux-ui/screens/certificates-a11y-light-320-2026-10-10.png
new file mode 100644
index 00000000..acba6d3d
Binary files /dev/null and b/docs/ux-ui/screens/certificates-a11y-light-320-2026-10-10.png differ
diff --git a/docs/ux-ui/screens/filters-a11y-dark-320-2026-10-10.png b/docs/ux-ui/screens/filters-a11y-dark-320-2026-10-10.png
new file mode 100644
index 00000000..72f1af1e
Binary files /dev/null and b/docs/ux-ui/screens/filters-a11y-dark-320-2026-10-10.png differ
diff --git a/docs/ux-ui/screens/filters-a11y-light-320-2026-10-10.png b/docs/ux-ui/screens/filters-a11y-light-320-2026-10-10.png
new file mode 100644
index 00000000..57064244
Binary files /dev/null and b/docs/ux-ui/screens/filters-a11y-light-320-2026-10-10.png differ
diff --git a/tests/e2e/operational-a11y.spec.ts b/tests/e2e/operational-a11y.spec.ts
new file mode 100644
index 00000000..73cca7cc
--- /dev/null
+++ b/tests/e2e/operational-a11y.spec.ts
@@ -0,0 +1,64 @@
+import { test, expect } from "@playwright/test"
+import axe from "axe-core"
+import { emptyAlerts, emptyOutbox } from "./fixtures/alerts"
+import { certificateState } from "./fixtures/certificates"
+import { state as containerState } from "./fixtures/containers"
+import { server } from "./fixtures/users"
+
+for (const theme of ["dark", "light"] as const) for (const width of [1280, 320]) {
+ for (const path of ["alerts", "certificates", "containers", "filters", "filters-error", "uptime"]) {
+ test(`Operational accessibility ${path} ${theme} ${width}`, async ({ page }) => {
+ await page.setViewportSize({ width, height: 1000 })
+ await page.addInitScript(theme => localStorage.setItem("rl-theme", theme), theme)
+ const writes: string[] = []
+ await page.route("**/health", route => route.fulfill({ json: { ok: true } }))
+ await page.route("**/api/**", route => {
+ const url = new URL(route.request().url()).pathname
+ if (route.request().method() !== "GET") writes.push(url)
+ if (path === "filters-error" && url === "/api/filters/rules") return route.fulfill({ status: 503, json: { error: "Fixture unavailable" } })
+ const json = url === "/api/auth/config" ? { required: false }
+ : url === "/api/servers" ? [{ ...server, type: "home-router" }]
+ : url === "/api/certificates/state" ? certificateState([])
+ : url === "/api/certificates/renew-settings" ? { enabled: true, intervalSec: 300, renewBeforeDays: 30 }
+ : url === "/api/containers/state" ? containerState([])
+ : url === "/api/alerts" ? emptyAlerts
+ : url === "/api/alerts/outbox" ? emptyOutbox
+ : url === "/api/uptime/speed-probes" ? { probes: [] }
+ : url === "/api/uptime/speed-test/runs" ? { runs: [] }
+ : url === "/api/uptime/probes" ? { probes: [] }
+ : url === "/api/filters/rules" ? { rulesets: [{ serverId: "1", rules: [] }], greTunnels: [], live: true }
+ : url === "/api/filters/gre-tunnels" ? { tunnels: [] }
+ : url === "/api/recursive-routes" ? { routes: [] }
+ : {}
+ return route.fulfill({ json })
+ })
+ await page.goto("/" + (path === "filters-error" ? "filters" : path))
+ await expect(page.locator("main").first()).toBeVisible()
+ if (path === "certificates") await expect(page.getByText("Включено", { exact: true })).toBeVisible()
+ if (path === "alerts") await expect(page.getByText("Правила не найдены", { exact: true })).toBeVisible()
+ if (path === "filters-error") await expect(page.getByRole("alert").filter({ hasText: "Не удалось загрузить Filters" })).toBeVisible()
+ await page.evaluate(() => document.fonts.ready)
+ // Audit settled colours, after provider hydration and transitions.
+ await page.waitForTimeout(900)
+ await page.addScriptTag({ content: axe.source })
+ const violations = await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document, {
+ runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21aa"] },
+ }))
+ expect(violations.violations).toEqual([])
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
+ if (path === "certificates") {
+ const blocks = page.getByRole("region", { name: /^Команды RouterOS:/ })
+ await expect(blocks).toHaveCount(3)
+ for (const block of await blocks.all()) {
+ await block.focus()
+ await expect(block).toBeFocused()
+ if (await block.evaluate(el => el.scrollWidth > el.clientWidth + 1)) {
+ await block.press("ArrowRight")
+ await expect.poll(() => block.evaluate(el => el.scrollLeft)).toBeGreaterThan(0)
+ }
+ }
+ }
+ expect(writes).toEqual([])
+ })
+ }
+}