fix(ui): объяснить неподтверждённый результат повтора сообщения

This commit is contained in:
Denozordec committed 2026-10-08 14:30:54 +07:00
1 parent e80cfeb4a9
commit b55588f29b
8 files changed
+64 -5

No files matched your search

+6 -1
View File
@@ -30,7 +30,12 @@ export function AlertsOutboxPanel({base,operationsBusy,available,beginOperation,
const requestRevision=++writeRevision.current,abort=new AbortController();writeController.current=abort;setOutboxPending(source,true)
const current=()=>active.current&&requestRevision===writeRevision.current
try{await retryAlertsOutbox(base,selected.id,{expectedVersion:selected.resolutionVersion,acknowledgedDuplicateRisk:true},{signal:abort.signal});if(current()){setOutboxPending(source,false);setSelected(null);setMessage("Сообщение поставлено в очередь. Отправка и доставка ещё не подтверждены.");void refresh()}}
catch(e){if(current()){if(e instanceof ApiClientError&&[400,401,403,409,422].includes(e.status))setOutboxPending(source,false);setMutationError(e instanceof Error?e.message:"Результат повтора неизвестен")}}
catch(e){if(current()){
const rejected=e instanceof ApiClientError&&[400,401,403,409,422].includes(e.status)
if(rejected)setOutboxPending(source,false)
const detail=e instanceof Error?e.message:"Ответ не получен"
setMutationError(rejected?detail:"Результат повтора не подтверждён. Автоматический повтор заблокирован. Проверьте очередь и чат перед новой попыткой. "+detail)
}}
finally{mutationLock.current=false;endOperation();if(current())setMutationBusy(false)}
}
return <section aria-label="Очередь Telegram" className="min-w-0"><OpsPanel title="Очередь Telegram" description="Приём сообщения API и доставка в чат — разные состояния." headerRight={<Button type="button" variant="outline" size="sm" aria-label="Обновить очередь Telegram" disabled={busy||mutationBusy} onClick={()=>void refresh()}>Обновить</Button>} contentClassName="space-y-3 px-5 py-4">
+10
View File
@@ -253,3 +253,13 @@ GET outbox: anonymous, неверные issuer/signature, expired и malformed
Скриншоты просмотрены: [предупреждение после перезагрузки](ux-ui/screens/outbox-retry-checkpoint-real-320-2026-10-08.png), [диалог после HTTP500](ux-ui/screens/outbox-retry-unknown-real-320-2026-10-08.png). Первое тестовое ожидание внутреннего текста исключения исправлено на штатный production текст «Внутренняя ошибка сервера»; приложение не обязано раскрывать исключение. При пересоздании fixture первый прогон очищен с exitCode0. Итоговые контейнеры/сеть/туннель3018 удалены, backend exitCode0, независимый SQL подтвердил ноль fixture-строк. Основной стенд и RouterOS не менялись.
Границы: управляемый отказ получения receipt после фактического COMMIT, а не физический разрыв сети; AUTH_REQUIRED=false; реальных сообщений и проверки чата нет. Найдена UX-недоработка: внутри открытого диалога generic HTTP500 не объясняет неизвестный исход текущего повтора. Следующий блок 12.5.44 — явное пояснение и профильная проверка диалога. Локальный коммит без push.
## 12.5.44 — понятное пояснение неподтверждённого повтора в диалоге (2026-10-08)
По результатам реального теста12.5.43 уточнён catch в components/alerts-outbox-panel.tsx. При HTTP500, потере ответа или некорректном receipt ошибка прямо в открытом диалоге сообщает: «Результат повтора не подтверждён. Автоматический повтор заблокирован. Проверьте очередь и чат перед новой попыткой», затем показывает подробность ошибки. API400/401/403/409/422 остаются подтверждёнными отказами: checkpoint снимается, исходная ошибка сохраняется. Правила повторной отправки, права и API не менялись.
Профильный Playwright alerts-outbox.spec.ts:8/8, включая новый HTTP500 сценарий, сохранённый checkbox, disabled retry, reload guard и320px; прежние success, malformed receipt, reader, conflict и focus/cancel проходят. Eslint изменённых файлов и tsc --project tmp/tsconfig-speed-check.json --noEmit passed. Scoped config исключает старые копии исходников в tmp; это не утверждение о чистоте обычного полного tsc. [Локальный диалог320px](ux-ui/screens/outbox-retry-explained-local-320-2026-10-08.png) просмотрен. HTTP-ответы в этих локальных браузерных тестах подменены; реальные SQL/HTTP результаты43 сохраняются отдельно.
Применён skill ReUI и MCP get_audit_checklist: композиция, установленные primitives, base-nova и semantic tokens сохранены; новых API/стилей/компонентов нет. Ошибка объявляется существующим role=alert, реальные секреты не добавлены; подтверждение риска и управление фокусом покрыты профильными тестами. Изменение пока локальное, удалённый frontend остаётсяf194e72.
Следующий12.5.45 — production Docker build нового frontend, обновление тестового стенда и повтор сценария HTTP500 после COMMIT на отдельной БД. Локальный коммит без push.
+2 -2
View File
@@ -1,12 +1,12 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-08: 12.5.43 проверил успешный browser retry и потерю HTTP receipt после реального COMMIT: POST200/500, обе fixture pending/version1; checkpoint сохраняется после refresh/reload и снимается явно без нового POST. JS errors=0, отправок Telegram нет; стенд очищен. Следующий блок 12.5.44 — понятное сообщение о неподтверждённом повторе в открытом диалоге. Runtime backend8782054/frontendf194e72 прежний; 12.5 открыт.
Актуальное продолжение 2026-10-08: 12.5.44 уточнил ошибку неизвестного исхода в диалоге Telegram retry: пояснение блокировки и необходимости проверки очереди/чата видно сразу вместе с деталями API. Подтверждённые отказы сохраняют прежнее поведение. Playwright8/8, scoped typecheck и lint passed, ReUI checklist проверен. Следующий 12.5.45 — сборка/развёртывание frontend и повтор реального HTTP/PG сценария. Пока runtime backend8782054/frontendf194e72 прежний; 12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-08. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-08
Этапы0–11 завершены в описанных локальных границах. Этап12 остаётся открытым. На161.104.46.222 развёрнуты backend/frontend8782054; два CHR подключены. PG backup/restore/rollback, single-instance quiescence, JWT/RBAC и browser restore приняты в границах журнала. PG-8 проверен с настоящими PG/native и введённой после COMMIT ошибкой подтверждения; новый browser UI сохраняет snapshot и сверяет историю. ROS-8/9 API/SQL/native приняты в описанных границах. Browser-смена пары/длительности и удаление во время native-теста приняты в12.5.24. Реальный отказ bandwidth-аутентификации принят в12.5.25. 12.5.26 обнаружил и локально исправил30s timeout Next proxy. Исправленный frontendaa7dbdf развёрнут и проверен в12.5.27; backend8782054 прежний. ROS-3 reverse2→1/native-afterping принят в12.5.28; forward timeout открыт. Форма frontendf194e72 проверена без моков и развёрнута в12.5.29. Read-only диагностика12.5.30 не установила причину. 12.5.31 выявил текущий отказ авторизацииCHR2 вCLI/REST/SSH. В12.5.32 пользователь восстановил доступCHR2; dashboard/native1→2 passed, блокировка снята. TCP receive/both подтверждены после восстановлениядоступа в12.5.33; UDP transmit3s/API/history/UI подтверждены в12.5.34; Q-6productionenqueue/PG race принят в12.5.35; Q-4 realHTTP/PG race принят в12.5.36; Q-1productionclaim/PG/interceptedsender принят в12.5.37; 12.5.38 подтвердил SQL settlement rollback и сохранение sent после ошибки подтверждения COMMIT; 12.5.39 подтвердил unknown/processing без автоматического повтора при неопределённом ответе; 12.5.40 подтвердил задержку повтора и failed после исчерпания попыток при явном отказе; 12.5.41 подтвердил браузерные состояния очереди на HTTP/PG; 12.5.42 подтвердил production JWT/RBAC очереди на PostgreSQL; 12.5.43 подтвердил браузерный retry и checkpoint при потере HTTP receipt; следующий 12.5.44 — пояснение неподтверждённого результата в диалоге. [Протокол](ux-ui-external-acceptance.md) и [журнал](ux-ui-stage-12.md) сохраняют точные доказательства и ограничения.
Этапы0–11 завершены в описанных локальных границах. Этап12 остаётся открытым. На161.104.46.222 развёрнуты backend/frontend8782054; два CHR подключены. PG backup/restore/rollback, single-instance quiescence, JWT/RBAC и browser restore приняты в границах журнала. PG-8 проверен с настоящими PG/native и введённой после COMMIT ошибкой подтверждения; новый browser UI сохраняет snapshot и сверяет историю. ROS-8/9 API/SQL/native приняты в описанных границах. Browser-смена пары/длительности и удаление во время native-теста приняты в12.5.24. Реальный отказ bandwidth-аутентификации принят в12.5.25. 12.5.26 обнаружил и локально исправил30s timeout Next proxy. Исправленный frontendaa7dbdf развёрнут и проверен в12.5.27; backend8782054 прежний. ROS-3 reverse2→1/native-afterping принят в12.5.28; forward timeout открыт. Форма frontendf194e72 проверена без моков и развёрнута в12.5.29. Read-only диагностика12.5.30 не установила причину. 12.5.31 выявил текущий отказ авторизацииCHR2 вCLI/REST/SSH. В12.5.32 пользователь восстановил доступCHR2; dashboard/native1→2 passed, блокировка снята. TCP receive/both подтверждены после восстановлениядоступа в12.5.33; UDP transmit3s/API/history/UI подтверждены в12.5.34; Q-6productionenqueue/PG race принят в12.5.35; Q-4 realHTTP/PG race принят в12.5.36; Q-1productionclaim/PG/interceptedsender принят в12.5.37; 12.5.38 подтвердил SQL settlement rollback и сохранение sent после ошибки подтверждения COMMIT; 12.5.39 подтвердил unknown/processing без автоматического повтора при неопределённом ответе; 12.5.40 подтвердил задержку повтора и failed после исчерпания попыток при явном отказе; 12.5.41 подтвердил браузерные состояния очереди на HTTP/PG; 12.5.42 подтвердил production JWT/RBAC очереди на PostgreSQL; 12.5.43 подтвердил браузерный retry и checkpoint при потере HTTP receipt; 12.5.44 добавил ясное пояснение в диалоге, проверено локально; следующий 12.5.45 — production frontend build/deploy и реальная HTTP/PG проверка. [Протокол](ux-ui-external-acceptance.md) и [журнал](ux-ui-stage-12.md) сохраняют точные доказательства и ограничения.
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md).
+11 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-08: 12.5.43 проверил успешный browser retry и потерю HTTP receipt после реального COMMIT: POST200/500, обе fixture pending/version1; checkpoint сохраняется после refresh/reload и снимается явно без нового POST. JS errors=0, отправок Telegram нет; стенд очищен. Следующий блок 12.5.44 — понятное сообщение о неподтверждённом повторе в открытом диалоге. Runtime backend8782054/frontendf194e72 прежний; 12.5 открыт.
Актуальное продолжение 2026-10-08: 12.5.44 уточнил ошибку неизвестного исхода в диалоге Telegram retry: пояснение блокировки и необходимости проверки очереди/чата видно сразу вместе с деталями API. Подтверждённые отказы сохраняют прежнее поведение. Playwright8/8, scoped typecheck и lint passed, ReUI checklist проверен. Следующий 12.5.45 — сборка/развёртывание frontend и повтор реального HTTP/PG сценария. Пока runtime backend8782054/frontendf194e72 прежний; 12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -34,6 +34,16 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в описанных границах:12.3.83,main1186/1186,context221/221. Этап остаётся в работе: стенд12.5.1–3 развёрнут,bootstrap/smoke проверены;остальные сценарии12.5 не выполнены. |
## 12.5.44 — понятное пояснение неподтверждённого повтора в диалоге (2026-10-08)
По результатам реального теста12.5.43 уточнён catch в components/alerts-outbox-panel.tsx. При HTTP500, потере ответа или некорректном receipt ошибка прямо в открытом диалоге сообщает: «Результат повтора не подтверждён. Автоматический повтор заблокирован. Проверьте очередь и чат перед новой попыткой», затем показывает подробность ошибки. API400/401/403/409/422 остаются подтверждёнными отказами: checkpoint снимается, исходная ошибка сохраняется. Правила повторной отправки, права и API не менялись.
Профильный Playwright alerts-outbox.spec.ts:8/8, включая новый HTTP500 сценарий, сохранённый checkbox, disabled retry, reload guard и320px; прежние success, malformed receipt, reader, conflict и focus/cancel проходят. Eslint изменённых файлов и tsc --project tmp/tsconfig-speed-check.json --noEmit passed. Scoped config исключает старые копии исходников в tmp; это не утверждение о чистоте обычного полного tsc. [Локальный диалог320px](ux-ui/screens/outbox-retry-explained-local-320-2026-10-08.png) просмотрен. HTTP-ответы в этих локальных браузерных тестах подменены; реальные SQL/HTTP результаты43 сохраняются отдельно.
Применён skill ReUI и MCP get_audit_checklist: композиция, установленные primitives, base-nova и semantic tokens сохранены; новых API/стилей/компонентов нет. Ошибка объявляется существующим role=alert, реальные секреты не добавлены; подтверждение риска и управление фокусом покрыты профильными тестами. Изменение пока локальное, удалённый frontend остаётсяf194e72.
Следующий12.5.45 — production Docker build нового frontend, обновление тестового стенда и повтор сценария HTTP500 после COMMIT на отдельной БД. Локальный коммит без push.
## 12.5.43 — браузерный повтор и потеря подтверждения после COMMIT (2026-10-08)
[Отчёт браузера](ux-ui/acceptance/browser-outbox-retry-2026-10-08.json): production backend8782054/frontendf194e72, отдельная БД mmapp_acceptance_20261007, два синтетических unknown-сообщения. Реальные браузерные HTTP запросы и production retry transaction; scheduler admission закрыт, dispatcher не запускается, внешний Telegram fetch запрещён. Изолированный frontend использует отдельную Docker-сеть и alias backend.
+11 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-08: 12.5.43 проверил успешный browser retry и потерю HTTP receipt после реального COMMIT: POST200/500, обе fixture pending/version1; checkpoint сохраняется после refresh/reload и снимается явно без нового POST. JS errors=0, отправок Telegram нет; стенд очищен. Следующий блок 12.5.44 — понятное сообщение о неподтверждённом повторе в открытом диалоге. Runtime backend8782054/frontendf194e72 прежний; 12.5 открыт.
Актуальное продолжение 2026-10-08: 12.5.44 уточнил ошибку неизвестного исхода в диалоге Telegram retry: пояснение блокировки и необходимости проверки очереди/чата видно сразу вместе с деталями API. Подтверждённые отказы сохраняют прежнее поведение. Playwright8/8, scoped typecheck и lint passed, ReUI checklist проверен. Следующий 12.5.45 — сборка/развёртывание frontend и повтор реального HTTP/PG сценария. Пока runtime backend8782054/frontendf194e72 прежний; 12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -1418,3 +1418,13 @@ GET outbox: anonymous, неверные issuer/signature, expired и malformed
Скриншоты просмотрены: [предупреждение после перезагрузки](ux-ui/screens/outbox-retry-checkpoint-real-320-2026-10-08.png), [диалог после HTTP500](ux-ui/screens/outbox-retry-unknown-real-320-2026-10-08.png). Первое тестовое ожидание внутреннего текста исключения исправлено на штатный production текст «Внутренняя ошибка сервера»; приложение не обязано раскрывать исключение. При пересоздании fixture первый прогон очищен с exitCode0. Итоговые контейнеры/сеть/туннель3018 удалены, backend exitCode0, независимый SQL подтвердил ноль fixture-строк. Основной стенд и RouterOS не менялись.
Границы: управляемый отказ получения receipt после фактического COMMIT, а не физический разрыв сети; AUTH_REQUIRED=false; реальных сообщений и проверки чата нет. Найдена UX-недоработка: внутри открытого диалога generic HTTP500 не объясняет неизвестный исход текущего повтора. Следующий блок 12.5.44 — явное пояснение и профильная проверка диалога. Локальный коммит без push.
## 12.5.44 — понятное пояснение неподтверждённого повтора в диалоге (2026-10-08)
По результатам реального теста12.5.43 уточнён catch в components/alerts-outbox-panel.tsx. При HTTP500, потере ответа или некорректном receipt ошибка прямо в открытом диалоге сообщает: «Результат повтора не подтверждён. Автоматический повтор заблокирован. Проверьте очередь и чат перед новой попыткой», затем показывает подробность ошибки. API400/401/403/409/422 остаются подтверждёнными отказами: checkpoint снимается, исходная ошибка сохраняется. Правила повторной отправки, права и API не менялись.
Профильный Playwright alerts-outbox.spec.ts:8/8, включая новый HTTP500 сценарий, сохранённый checkbox, disabled retry, reload guard и320px; прежние success, malformed receipt, reader, conflict и focus/cancel проходят. Eslint изменённых файлов и tsc --project tmp/tsconfig-speed-check.json --noEmit passed. Scoped config исключает старые копии исходников в tmp; это не утверждение о чистоте обычного полного tsc. [Локальный диалог320px](ux-ui/screens/outbox-retry-explained-local-320-2026-10-08.png) просмотрен. HTTP-ответы в этих локальных браузерных тестах подменены; реальные SQL/HTTP результаты43 сохраняются отдельно.
Применён skill ReUI и MCP get_audit_checklist: композиция, установленные primitives, base-nova и semantic tokens сохранены; новых API/стилей/компонентов нет. Ошибка объявляется существующим role=alert, реальные секреты не добавлены; подтверждение риска и управление фокусом покрыты профильными тестами. Изменение пока локальное, удалённый frontend остаётсяf194e72.
Следующий12.5.45 — production Docker build нового frontend, обновление тестового стенда и повтор сценария HTTP500 после COMMIT на отдельной БД. Локальный коммит без push.
+10
View File
@@ -261,3 +261,13 @@ GET outbox: anonymous, неверные issuer/signature, expired и malformed
Скриншоты просмотрены: [предупреждение после перезагрузки](ux-ui/screens/outbox-retry-checkpoint-real-320-2026-10-08.png), [диалог после HTTP500](ux-ui/screens/outbox-retry-unknown-real-320-2026-10-08.png). Первое тестовое ожидание внутреннего текста исключения исправлено на штатный production текст «Внутренняя ошибка сервера»; приложение не обязано раскрывать исключение. При пересоздании fixture первый прогон очищен с exitCode0. Итоговые контейнеры/сеть/туннель3018 удалены, backend exitCode0, независимый SQL подтвердил ноль fixture-строк. Основной стенд и RouterOS не менялись.
Границы: управляемый отказ получения receipt после фактического COMMIT, а не физический разрыв сети; AUTH_REQUIRED=false; реальных сообщений и проверки чата нет. Найдена UX-недоработка: внутри открытого диалога generic HTTP500 не объясняет неизвестный исход текущего повтора. Следующий блок 12.5.44 — явное пояснение и профильная проверка диалога. Локальный коммит без push.
## 12.5.44 — понятное пояснение неподтверждённого повтора в диалоге (2026-10-08)
По результатам реального теста12.5.43 уточнён catch в components/alerts-outbox-panel.tsx. При HTTP500, потере ответа или некорректном receipt ошибка прямо в открытом диалоге сообщает: «Результат повтора не подтверждён. Автоматический повтор заблокирован. Проверьте очередь и чат перед новой попыткой», затем показывает подробность ошибки. API400/401/403/409/422 остаются подтверждёнными отказами: checkpoint снимается, исходная ошибка сохраняется. Правила повторной отправки, права и API не менялись.
Профильный Playwright alerts-outbox.spec.ts:8/8, включая новый HTTP500 сценарий, сохранённый checkbox, disabled retry, reload guard и320px; прежние success, malformed receipt, reader, conflict и focus/cancel проходят. Eslint изменённых файлов и tsc --project tmp/tsconfig-speed-check.json --noEmit passed. Scoped config исключает старые копии исходников в tmp; это не утверждение о чистоте обычного полного tsc. [Локальный диалог320px](ux-ui/screens/outbox-retry-explained-local-320-2026-10-08.png) просмотрен. HTTP-ответы в этих локальных браузерных тестах подменены; реальные SQL/HTTP результаты43 сохраняются отдельно.
Применён skill ReUI и MCP get_audit_checklist: композиция, установленные primitives, base-nova и semantic tokens сохранены; новых API/стилей/компонентов нет. Ошибка объявляется существующим role=alert, реальные секреты не добавлены; подтверждение риска и управление фокусом покрыты профильными тестами. Изменение пока локальное, удалённый frontend остаётсяf194e72.
Следующий12.5.45 — production Docker build нового frontend, обновление тестового стенда и повтор сценария HTTP500 после COMMIT на отдельной БД. Локальный коммит без push.
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+14
View File
@@ -29,4 +29,18 @@ test("Manual queue retry: confirmed conflict keeps form without unknown checkpoi
await retrySetup(page,r=>r.fulfill({status:409,json:{error:"Fixture queue conflict"}}));const dialog=await openRetry(page);await dialog.getByRole("button",{name:"Поставить в очередь повторно"}).click();await expect(dialog.getByRole("alert")).toHaveText("Fixture queue conflict");await expect(dialog.getByRole("checkbox")).toBeChecked();await expect(dialog.getByRole("button",{name:"Поставить в очередь повторно"})).toBeEnabled()
})
test("Manual queue retry: HTTP500 explains unknown outcome inside the dialog and preserves the guard",async({page})=>{
await page.setViewportSize({width:320,height:1000});let calls=0
await retrySetup(page,r=>{calls++;return r.fulfill({status:500,json:{error:"Внутренняя ошибка сервера"}})})
const dialog=await openRetry(page);await dialog.getByRole("button",{name:"Поставить в очередь повторно"}).click()
await expect(dialog.getByRole("alert")).toContainText("Результат повтора не подтверждён. Автоматический повтор заблокирован. Проверьте очередь и чат перед новой попыткой.")
await expect(dialog.getByRole("alert")).toContainText("Внутренняя ошибка сервера")
await expect(dialog.getByRole("checkbox")).toBeChecked();await expect(dialog.getByRole("button",{name:"Поставить в очередь повторно"})).toBeDisabled()
const bounds=await dialog.boundingBox();expect(bounds!.x).toBeGreaterThanOrEqual(0);expect(bounds!.x+bounds!.width).toBeLessThanOrEqual(320)
await dialog.screenshot({path:"test-results/alerts-outbox-http500-320.png"})
await dialog.getByRole("button",{name:"Отмена"}).click();await page.reload()
const panel=page.getByRole("region",{name:"Очередь Telegram",exact:true});await expect(panel).toContainText("Результат повтора неизвестен")
await expect(panel.getByRole("button",{name:"Повторить сообщение Held retry fixture"})).toBeDisabled();expect(calls).toBe(1)
})
test("Manual queue retry: mobile confirmation fits, cancel restores focus without POST",async({page})=>{await page.setViewportSize({width:320,height:1000});let calls=0;await retrySetup(page,r=>{calls++;return r.fulfill({json:{ok:true}})});const dialog=await openRetry(page);const bounds=await dialog.boundingBox();expect(bounds!.x).toBeGreaterThanOrEqual(0);expect(bounds!.x+bounds!.width).toBeLessThanOrEqual(320);await dialog.screenshot({path:"test-results/alerts-outbox-dialog-320.png"});await page.keyboard.press("Escape");await expect(dialog).toHaveCount(0);await expect(page.getByRole("button",{name:"Повторить сообщение Held retry fixture"})).toBeFocused();expect(calls).toBe(0)})