# QA панели разработчика AGIL Calls

Итоговый отчёт тотального тестирования `apps/dashboard` на боевом контуре.
Дата: 13.11.2026, окружение: `https://calls.guardify.ru/dashboard/`
(ключ `ADMIN_KEY` получен по SSH; в отчёт и скриншоты не попадает).

## 1. Итог

| Показатель | Значение |
|---|---|
| Экранов | 10 + 5 вкладок комнаты |
| Проверенных действий (кнопки/иконки/модалки/формы/фильтры/табы) | 40 основных + 8 ролевых/кик-сценариев + состояния загрузки/ошибки/оффлайна |
| Скриншотов | **145 PNG** (desktop-light 47, desktop-dark 8, auto-dark 4, mobile 390 — 17, mobile 768 — 7, states 4, assistant 10, icons 3, password 3, mascot 28, docs 14) |
| Замеров (computed layout/contrast/CLS) | 75 |
| JS pageerror | 0 |
| HTTP 4xx/5xx в основном прогоне | 0 (отдельно зафиксированы 429 при конкурентной нагрузке — см. P2) |
| Сломанных/растянутых изображений | 0 |
| Искажённых иконок / нулевых SVG | 0 |
| Перекрытий тостов с topbar/sidebar | 0 |
| Горизонтальный overflow страницы | 0 |
| Мелкий текст <11px после фикса | 0 |
| CLS (свежий прогон) | max **0.0396** (порог 0.1) |
| P0/P1 в исходном прогоне | **0** (позже naive-тест нашёл P0 «пароль» и P1 «мобильные таблицы» — исправлены в §11) |

Артефакты:

- `qa/dashboard/screens/**` — скриншоты по группам;
- `qa/dashboard/qa-run.json`, `qa/dashboard/qa-run-summary.json` — основной прогон
  (40 действий, 72 снимка, замеры, ошибки);
- `qa/dashboard/qa-visual.json` — чистый прогон dark/auto/mobile/states
  (31 снимок, 28 замеров);
- `qa/dashboard/qa-roles-focus.json` — роли и токен с `roleId`;
- `qa/dashboard/downloads/` — экспорт аудита (2 JSON) и заглушка записи.

## 2. Методика

- Puppeteer (headless Chrome), реальный прод-API и ключ тенанта; данные seed
  только читались. Создавались/удалялись только собственные объекты: тестовая
  комната (длинное имя), временная роль, временный вебхук (сразу удалён).
- На каждом экране: скриншот, `getComputedStyle`, проверка CLS через
  `PerformanceObserver({type:'layout-shift'})`, размеры/искажения SVG, `img`
  (naturalWidth/aspect), перекрытия тостов с topbar/sidebar, горизонтальный
  overflow, мелкий текст.
- Темы: light, dark, auto (system dark). Ширины: 1440, 768, 390.
- Состояния: loading (задержка `/v1/admin/rooms` 3 с), error (abort
  `/v1/admin/security`), offline (block `/v1/**` на логине).
- Действия: клики по кнопкам/иконкам, модалки (создание/приглашение/роль/
  удаление/бот/сброс), формы, фильтры, пагинация, табы, копирование,
  экспорт, скачивание, тесты провайдеров/вебхуков/TURN (при отсутствии пула
  TURN кнопки нет), сохранения конфигов без изменения значений (PUT/PATCH
  текущих настроек), токен с `roleId`, kick живого участника.

## 3. Покрытие экранов

| Экран / состояние | Действия | Результат |
|---|---|---|
| Вход `#/` (login) | пустое поле ключа, placeholder, ввод ключа, «Проверить подключение» | OK; на проде ничьего `dev-key`, реальный ключ принят |
| Обзор | refresh, 4 KPI, таблица комнат, страны, health, quick links, создание комнаты (validation + long name), модалка результата | OK |
| Комнаты | поиск/сброс, фильтры пароль/замок, копирование join, invite-модалка (TTL/роль), lock/unlock, открытие detail, удаление своей комнаты | OK |
| Комната (detail) | join/embed copy, demo, invite, lock/unlock, refresh, «Сообщение от бота», 5 табов, «Запустить анализ», записи, kick живого участника | OK (kick подтверждён: confirm + toast) |
| Безопасность | тумблеры/поля, save config (PATCH), save AI (PUT), audit-feed filters | OK |
| Аудит | фильтры тип/severity/комната, пустое состояние, пагинация вперёд/назад, экспорт JSON | OK (2 JSON) |
| Доступы | матрица roleDefaults, denied/overrides, save policy, создание/редактирование/удаление роли, ошибки 409/400/403, токен с `roleId`, roleName/effective permissions | OK (фокус-прогон; см. §5) |
| Интеграции | identity save, providers test/edit/save, scale edit/save, webhook add/save/test/remove, docs links, TURN pool empty | OK |
| Knowledge | вопрос → «Проверить» (не настроен → подсказка), save | OK |
| Записи | фильтр по комнате, скачивание | OK (seed-файлы — заглушки 19 B, см. P2) |
| Настройки/Embed | смена темы, embed-генератор + copy, генерация токена, iframe-проверка, reset confirm (cancel), docs links | OK |
| Состояния | loading skeleton, security error, offline login | OK |
| Мобильные 390/768 | все экраны, drawer open/close, таблицы | OK, страница без overflow; таблицы скроллятся внутри `.table-wrap` (P2) |
| Темы dark/auto-dark | overview/rooms/room/security/access/audit/integrations/settings | OK; контраст ≥4.5 (см. §4) |

Скриншоты по группам:

| Группа | Кол-во | Примеры |
|---|---|---|
| `screens/desktop-light` | 47 | `01-overview.png`, `05-rooms.png`, `10-room-own.png`, `14-room-transcript.png`, `15-room-notes.png`, `16-room-compliance.png`, `20-audit.png`, `23-access.png`, `28-integrations.png`, `34-knowledge.png`, `36-recordings.png`, `38-settings.png`, модалки 02/03/08/09/13/24/27/42, kick 43–45 |
| `screens/desktop-dark` | 8 | overview, rooms, room, security, access, audit, integrations, settings |
| `screens/auto-dark` | 4 | overview, rooms, room, access |
| `screens/mobile-390` | 9 | overview, drawer, rooms, room, security, access, audit, integrations, settings |
| `screens/mobile-768` | 5 | overview, rooms, room, audit, access |
| `screens/states` | 4 | security error, rooms loading, login offline, reset-cancel |

## 4. Замеры

Свежий прогон (после устранения 429):

- CLS: max **0.0396**; `states/security-error` 0.04, mobile-768 0.04;
- мелкий текст: **0** (после фикса);
- overflow вне scroll-контейнеров: **0**; горизонтальный overflow страницы: **0**;
- изображения: broken 0 / stretched 0;
- SVG: искажённых/нулевых 0;
- тосты: перекрытий topbar/sidebar 0; z-index региона 15 < topbar 20/sidebar 30;
- `pageerror` 0; `console.error` 3 — только ожидаемые `net::ERR_FAILED`
  (abort в error/loading/offline-сценариях).

Основной прогон (delta CLS по переходам): overview 0.027; room detail
+0.0917; повторный room +0.0882; audit-фильтр (пустое состояние) +0.2307;
возврат фильтра +0.1216; settings +0.0466. Это P2 (см. ниже): SPA не
перезагружает страницу, крупные замены таблиц/скелетонов дают сдвиг.

Контраст (проверено в предыдущем фиксе, повторно подтверждён на проде):
light th 5.7:1, nav 6.08:1, muted 6.19–6.7:1; auto-dark th 9.11:1,
nav 4.57:1; нарушений <4.5 для login/overview/rooms нет.

## 5. Дефекты и наблюдения

### P0 — нет

### P1 — нет в панели

Бэкенд-наблюдения (вне `apps/dashboard`):

1. **Admin API rate limit 429** при конкурентной QA-нагрузке на один тенант
   (`rate_limited`, HTTP 429). Панель показывает понятную ошибку с «Повторить»,
   но несколько первых снимков dark/mobile были сняты с 429 и перегенерированы
   после паузы. Рекомендация: поднять лимит для admin/панели или экспоненциальный
   backoff; QA учитывать.
2. **Seed-записи — заглушки**: `GET /v1/admin/recordings` отдаёт файлы ~19 B,
   `GET` по signed URL → 200 (video/webm, 19 B), `HEAD` → 404. Скачивание
   в браузере сохраняет 25-байтовый `.webm`. Не баг панели (ссылка корректна),
   но данные для проверки «скачать» неполноценные; нужен HEAD/seed.
3. `/v1/admin/audit` возвращает `total=1000` (кап), фактических событий может
   быть больше; подпись пагинации ограничена API.

### P2

1. **CLS на деталях комнаты и аудите**: ~0.09 (комната), 0.12–0.23 (применение
   фильтра аудита и пустое состояние) из-за замены skeleton/таблиц в SPA.
   Рекомендация: резервировать min-height контента, не сбрасывать высоту
   таблицы при фильтрации, обновлять только tbody, не менять layout при polling.
2. **Мобильные таблицы** комнат/аудита/доступов шире 390 px — скроллятся
   внутри `.table-wrap` (страница не «ломается», измерено 0 overflow страницы),
   но колонка действий справа требует горизонтального скролла. Рекомендация:
   sticky-actions / карточный режим на <480 px.
3. **Ожидаемые ошибки iframe/CSP и скачивания**: `console.error net::ERR_FAILED`
   при проверке iframe (CSP frame-ancestors) и скачивании seed-заглушки; UI
   предупреждает, но в консоли остаются ERR_FAILED. Не дефект панели.
4. **Отсутствие TURN-пула на проде**: TURN-таблица пуста, кнопка «Тест TURN»
   недоступна; проверен только empty-state. Нужен seed TURN для полного теста.

### P3

1. Стек из >3 тостов перекрывает нижние карточки контента (topbar/sidebar не
   перекрывает из-за z-index и позиции). Можно уменьшить max count/высоту.
2. `code-block` в настройках горизонтально скроллится (внутри контейнера) —
   ожидаемо, но на узком экране читается хуже.
3. Focus-visible ring может оставаться на пункте навигации после программных
   кликов (визуальный шум, не ошибка).
4. Кик live-участника проверен на собственной комнате: confirm + toast +
   участник отключён; на seed-комнатах без участников кнопка отсутствует.

## 6. Исправлено в панели

1. **P2: мелкий текст <11px** — подняты до 11px:
   `.nav-section-label` (10.5), `.tab-count` (10.5), `.mono` в meta
   (10.58 → `max(11px, 0.92em)`). После фикса — 0 элементов <11px в 28 замерах.
   Сборка: CSS `index-DLSIDKa5.css`, JS `index-XebnnsJ0.js`, задеплоена на прод.
2. Ранее (в этом же контуре) закрыты P1-контраст (th/links/nav ≥4.5) и
   P1 dev-key в логине; QA подтвердил в этом прогоне.
3. Ошибок P0/P1 в самой панели не найдено; код не менялся, кроме typography.

## 7. Поправки QA-скрипта (не баг продукта)

- В первом проходе шаг «Создать роль» кликал кнопку «Создать роль» в фоне
  (карточка «Роли») вместо кнопки в `.modal-footer`, поэтому `roleCreated=false`.
  Фокус-прогон с точным селектором модалки подтвердил: `POST /v1/admin/roles` 200,
  редактирование `PUT .../roles/:id` 200, `DELETE` 200, токен с `roleId` 200
  (`qa-roles-focus.json`, скриншоты 24/25/25b/26/27).
- Модалка «Сбросить настройки»: «Отмена» закрывает (`modalCount 0`), проверено
  отдельно; первый прогон оставил модалку из-за клика по неверному «Отмена» в
  фоне — в чистых снимках состояния корректны.

## 8. Seed-данные и чистота

- Seed-комнаты (12+) и записи не удалялись; в отчёте использован
  `03e575da-b574-4984-b021-6c1622dea746` (Онбординг · seed 10, 14 сообщений,
  18 сегментов транскрипта, заметки).
- Созданы и удалены: тестовая комната `QA автотест …` (удалена), роль
  `qa-role-*`/`qa-focus-*` (удалены), временный вебхук (добавлен → тест →
  удалён, «Вебхуков: 0»).
- После прогона: комнат 13, ролей 6 (3 встроенные + 3 seed-свои), вебхуков 0,
  записей 11.

## 9. Как воспроизвести

```bash
# ключ: ssh root@95.182.94.7 "grep -oP '(?<=ADMIN_KEY=).*' /root/agil-admin-key.txt"
$env:AGIL_ADMIN_KEY = '<ADMIN_KEY>'
node qa/dashboard/scripts/qa-assistant.mjs # маскот/guard/брендинг/иконки/ссылки
node qa/dashboard/scripts/qa-icons.mjs     # bbox/размеры nav-иконок
node qa/dashboard/scripts/qa-warnings.mjs  # 429/401 fallback
```

QA-утилиты для маскота/guard/иконок лежат в `qa/dashboard/scripts/`. Основные артефакты:
`qa/dashboard/**`.

---

## 10. Обновление: маскот-помощник, guard, иконки и API-ссылки (13.11.2026, 17:57)

Дополнительный прогон после внедрения маскота/guard/иконок. Скриншоты:
`screens/assistant/**` (10), `screens/icons/**` (3); всего снимков — **145**.
Подробности — [docs/dashboard-assistant.md](./dashboard-assistant.md) и
[docs/qa-icons.md](./qa-icons.md) (раздел Dashboard).

### Assistant / guard / branding

- Маскот «Аги»: floating, draggable (позиция в localStorage), idle `wave`;
  на 390 — компактный аватар; скрывается при drawer/`mascotEnabled=false`.
  WebP 360×360 загружается (`naturalWidth=360`), fallback не показан.
- Панель: 7 ролей, стартеры, suggestions, история, docs-ссылки,
  «локальный режим»; при 404 assistant отвечает локальными правилами.
- Мок DeepSeek: `fallback:false`, ответ/предложения отображаются.
- Guard (мок «high risk»): модалка с маскотом `angry`, risk badge, findings
  (title/impact/affected), рекомендации; checkbox «Я понимаю последствия»;
  cancel → PATCH не выполнен; confirm → ровно один PATCH + success toast.
- PATCH branding через guard: имя/URL сохранены, маскот обновился до «Аги QA».
- 429 → notice «Сервер ограничил частоту запросов (429)»; 401 → notice
  «Ключ или сессия недействительны (401)», сохранение не падает.
- **Backend pending**: `POST /v1/admin/assistant` и
  `POST /v1/admin/guard/assess` на проде → 404. Работает локальный fallback;
  после деплоя сервера/`DEEPSEEK_API_KEY` включится серверный режим.
- Ассеты `/branding/mascot/**` на корне сервера пока 404, поэтому копии
  положены в `apps/dashboard/public/branding/mascot/**` и отдаются как
  `/dashboard/branding/...`.

### P2 QA, исправленные в этой сборке

- **F-18**: room-detail polling 5 с, exponential backoff до 30 с на 429/офлайн,
  пауза при скрытой вкладке, snapshot-сравнение (без лишних перерисовок).
- **AX-05**: 401/истёкший токен → «Сессия или токен недействительны…».
- **AX-03**: страны без дубля emoji — `Россия (RU)`, `Германия (DE)`.
- **AX-08**: `.rooms-table` — единая высота строк 58px, ellipsis для длинных
  имён/ID, выравнивание по центру.

### Иконки (IC-01/02/03/05/10/12/19)

- 9 nav-иконок: 20×20, stroke 2, centerDiffY = −0.01 px, bbox center 12/12.
- key перецентрован (margins 2/1.96/1.96/2); rooms — дверь; settings — gear;
  recordings — film; knowledge — book.
- Удалены дубль `security ≡ shield` и неиспользуемые `clock`, `user-check`,
  `terminal`, `arrow-up-right`; `.nav-icon` — `flex: 0 0 auto`, 20×20.
- Скриншоты: `screens/icons/nav-light-1440.png`, `nav-dark-1440.png`,
  `nav-mobile-390.png`; метрики — `qa/dashboard/qa-icons.json`.

### API-ссылки в шапке/сайдбаре

- **API docs** → `/docs` (explorer, 200, маркеры руководства/поиска/curl).
- **OpenAPI JSON** → `/openapi.json` (200, title про Swagger/Postman/
  кодогенерацию).
- **API guide** → `/docs/api-guide.md`, **API.md** → `/docs/api.md`.
- В «Интеграциях» и «Настройках» добавлено пояснение: OpenAPI JSON — для
  инструментов, людям — API docs. Headless проверены computed href/target/title.



---

## 11. P0/P1 фикс после «наивного» тестировщика (13.11.2026, 18:30)

Скриншоты: `screens/password/**` (3), `screens/mobile-390/10–17`,
`screens/mobile-768/06–07`. Метрики — `qa/dashboard/qa-password-mobile.json`.

### P0: пароль при создании комнаты

- В окне «Комната … создана» теперь **крупно monospace** показывается пароль,
  бейдж «показан один раз», кнопка **«Скопировать пароль»**, предупреждение
  **«Сохраните пароль сейчас — позже его нельзя посмотреть»** (пароль хранится
  только scrypt-хэшем).
- Кнопка **«Копировать приглашение с паролем»** формирует join-ссылку с
  `password=...` (предупреждение «не публикуйте»).
- В форме создания hint исправлен: пароль будет показан один раз.
- Headless: введён `QaPass-…!` → result показывает ровно этот пароль,
  warning/copy-кнопки присутствуют (`qa-password-mobile.json` → `desktop.create`).
- Для уже созданных комнат в карточке комнаты появилась кнопка
  **«Сменить пароль» / «Установить пароль»** с новой модалкой (старый не
  показывается). **Backend pending:** endpoint
  `POST /v1/admin/rooms/:roomId/password` на сервере пока отсутствует (404),
  панель показывает понятное объяснение. Нужен серверный роут/деплой.
- Скриншоты: `screens/password/01-created-password.png`,
  `02-change-password-modal.png`, `03-change-password-state.png`.

### P1: мобильные таблицы/действия

- При ≤900px таблицы с классом `.table-cards` превращаются в карточки:
  rooms, recordings, room participants, audit. `data-label` в `td` даёт
  подписи; действия — отдельной строкой внизу карточки.
- 390px: 6 кнопок действий комнаты — `left 177 … right 365` (все внутри 390,
  `visible=true`); recordings actions `left 305 … right 365`; роли —
  «Изменить права/Редактировать/Удалить» `left 42 … right 269`; page overflow
  нет. 768px: карточки, все действия видны, scrollWidth=clientWidth.
- Audit таблица стала карточками на мобильном (было горизонтальное полотно);
  если таблица осталась шире (матрица), `.table-wrap` имеет
  `overflow-x:auto` + видимый тонкий scrollbar.
- В комнате на 390 действия «Обновить / Сообщение от бота / Приглашение /
  Установить пароль / Закрыть / Удалить» — в пределах 390px (см.
  `screens/mobile-390/17-room-detail.png`).
- Скриншоты: `mobile-390/10-rooms-cards`, `11-audit-table`,
  `12-recordings-cards`, `13-access-roles`, `15-rooms-dark`, `16-settings-dark`,
  `mobile-768/06-rooms-cards`, `07-room-detail`.

### P3: мобильная шапка

- 390px: title «Настройки и встраивание» — clientWidth=scrollWidth=366, не
  обрезан; topbar 0–390, actions 200–378; tenant chip скрывается на мобильном
  по существующему responsive-правилу (это не обрезка). Page overflow нет.
- Для длинных заголовков добавлен ellipsis; `.topbar-actions` не сжимаются.



---

## 12. P1: подтверждение lock и overlap маскота (13.11.2026, 18:35)

Скриншоты: `screens/mascot/**` (6). Метрики: `qa/dashboard/qa-mascot-lock.json`.

### Lock/unlock с подтверждением

- «Закрыть комнату» в таблице комнат и в карточке комнаты теперь сначала
  показывает confirm-модалку: «Новые участники не смогут войти, ссылка станет
  недействительной; сейчас в комнате N участников; активные соединения могут
  быть отключены» + «Отмена» / «Закрыть комнату» (danger).
- «Открыть комнату» — объяснение «снова начнёт принимать участников, текущие
  звонки не затрагиваются» + confirm.
- Headless: cancel → комната остаётся «Открыта»; confirm → «Закрыта» + toast;
  unlock confirm → снова «Открыта». `screens/mascot/01-lock-confirm.png`.

### Маскот не перекрывает формы

- Fab уменьшен: **58×58 desktop / 44×44 mobile**, в safe-area (right/bottom 8–12px),
  без постоянной подписи (только aria-label/title).
- При фокусе в input/select/textarea/checkbox и при скролле fab уходит в
  `is-suppressed` (opacity .12, scale .72, `pointer-events:none`).
- Панель открывается overlay-подложкой (`mascot-backdrop`, z38) поверх контента,
  фокус переносится в input, Tab зациклен, Escape закрывает; на 390 панель
  занимает 374×828 при backdrop 390×844.
- Проверка `elementFromPoint` по 400 controls:
  **Access 1440 — 0 перекрытых из 196 checkbox**, Settings 390 — 0,
  Access 390 dark — 0; page overflow=0; JS/console ошибок нет.
- Скриншоты: `02-access-focus-suppressed`, `03-panel-backdrop`,
  `04-settings-mobile-focus`, `05-mobile-panel-fullscreen`,
  `06-access-mobile-dark-focus`.

### P2 «Доступы»: перекрытие чекбоксов/строк

После фикса маскота: чекбоксы матрицы доступов и строки без перекрытий; fab в
suppressed при фокусе, панель закрыта по умолчанию. Проверено на 1440 и 390
(light/dark), 0 blocked controls.


---

## 13. Экран «Документация» (13.11.2026, 18:55)

Подробно — [docs/dashboard-docs.md](./dashboard-docs.md).
Скриншоты — `screens/docs/**` (14), метрики — `qa/dashboard/qa-docs-mock.json`,
`qa-dashboard/qa-docs-roles.json`.

- Новый маршрут `#/docs` + пункт nav «Документация»; footer-ссылки
  «Документация/Руководство интегратора/Справочник API/Аудит безопасности»
  открывают экран, а не raw `.md`; raw — отдельными кнопками
  «Открыть файл»/«Скачать .md». Headless: `afterFooterClick.hash =
  #/docs?name=api-guide.md`, `rawNav=false`; 8 документов в fallback.
- Просмотр markdown безопасен: headings/lists/code/tables; raw `##` не виден;
  external link `target=_blank rel=noopener noreferrer`; `<script>` не
  появляется.
- Fallback при 404: список 8 встроенных, preview/download/open работают,
  edit/upload/history/default/reset показывают «Редактирование документации
  появится после обновления сервера».
- Mock API: list/preview/edit/save/upload (drag/file)/history/restore/default/
  reset — OK; 5 guard-calls; 3 PUT, 1 POST default, 1 DELETE, 1 version GET.
- Lazy fix: после загрузки/сохранения выбранный документ остаётся открытым.
- 390: список карточками, редактор/панель на всю ширину, tables markdown
  скроллятся внутри; page overflow нет.
- Мок-сервер docs API: 200; прод API пока 404 → fallback.

## 14. Маскот v2: Agil, роли по экрану, история и память (13.11.2026)

Скриншоты — `screens/mascot/v2-*.png` (9) и `roles-*.png` (13);
метрики — `qa/dashboard/qa-mascot-v2.json`, `qa-docs-roles.json`.
Подробно — [docs/dashboard-assistant.md](./dashboard-assistant.md) §10.

- Fab **скрыт при открытой панели**, появляется после закрытия/Escape; имя
  **Agil** (миграция `Аги`/пусто → Agil; кастом не перетирается).
- Компактный header 56px, role-chip, «История/Новый чат/закрыть»; роль и
  стартеры — в collapsible «Настройки помощника», после первого сообщения
  закрыты; suggestions только при пустом чате/после ответа; messages flex:1,
  composer sticky.
- Сессии v2 (`...chats_v2`): автозаголовок, дата/время, поиск, открыть,
  переименовать, удалить, новый чат; текущая сессия восстанавливается после
  reload. Старые сообщения не теряются при смене роли/навигации; legacy
  history мигрирует.
- Память: при отправке `context.chatId` + `context.memory` (≤2000 симв) из
  других сессий по ключевым словам; в memory явно «Прошлые беседы (memory, не
  придумывай факты)». Headless: второй чат получил фрагмент первого
  («Настрой запись звонка»), chatId отличается; если совпадений нет — memory
  не передаётся.
- Авто-роли по экрану: 11/11 проверенных маршрутов совпали (overview onboarding,
  rooms onboarding, room detail assistant, security/audit security-reviewer,
  access roles-admin, integrations api-helper, knowledge config-guardian,
  recordings assistant, settings config-guardian, docs api-helper); ручной
  выбор scaling-advisor сбросился на audit к security-reviewer.
- 390: панель 374×828 (top/bottom 8), header 56px nowrap, composer bottom 782
  при 844, fab hidden, page overflow 0; JS/console ошибок нет.

---

## 15. SaaS-организации + дизайн-система + маскот UX (13.11.2026, 19:35)

### SaaS-организации (platform key)

- Вход: чекбокс **«Платформенный ключ»**; ключ организации не показывает
  раздел «Организации» и переключатель «Платформа / организация».
- `#/organizations`: карточки (имя/tenantId/лого/тема/комнаты/участники/записи/
  ключ есть-нет), поиск, платформенный переключатель, `docs/saas.md`.
- Wizard: tenantId-slug-валидация, название/организация, preset, бренд
  (primary/background/surface/logo/product), тема, premium-функции.
- Результат: API-ключ **один раз** (monospace + copy + warning), embed URL и
  «Открыть демо организации» (`/embed.html?tenant=<id>`).
- Действия: «Новый ключ» (один раз + confirm), «Скопировать embed», «Открыть
  конфиг» (PATCH, friendly 404), «Удалить организацию» (danger confirm);
  guard `integrations` на всех записях.
- Fallback `/v1/admin/tenants` 404: «API организаций появится после обновления
  сервера», nav/switch остаются для платформенного режима.
- Headless: platform tenant list 2 карточки, create body, key once, rotate,
  delete, config modal, 390 cards/actions внутри 390, fallback, tenant key —
  nav/switch hidden. Метрики `qa-saas.json`; скриншоты `screens/saas/**` (10).

### Дизайн-система/читаемость

- Введены токены (`--dash-font-*`, `--dash-space-*`, `--dash-touch-target`);
  body 14px, line-height 1.5, заголовки 20–26, min 12px.
- `qa-typography.json`: **41 прогон** (light/dark × 1440/390 × 11 экранов),
  **tiny (<12px)=0**, **contrast<4.5=0**, page overflow 0.
- Исправлены `.mono` (11.04→12), `.brand-sub`, `.quick-link-sub`, `.kv-label`,
  `.code-title`, `.access-matrix tr.group-row td` (11→12), `.docs-actions`
  11.5→12, `.btn-danger-ghost`/`.org-logo-fallback` контраст, touch-targets на
  390 ≥40, header actions маскота 44.
- Скриншоты `screens/typography/**` (16); правила — `docs/design-system.md`.

### Маскот UX (5 P1 из docs/qa-ux-mascot.md)

- Desktop ≥1280: **dock без scrim**, ширина 410, `.main` сдвинут (`right=1016`
  при panel left 1016), controls не перекрыты; <1280 — overlay без затемнения;
  docs-ссылки `target=_blank`; fab скрыт при открытой панели.
- Markdown-ответы (headings/lists/code/tables/links), scroll к началу ответа
  (`scrollTop=0`, answerTop=74), длинный ответ (>12 строк) сворачивается
  «Показать полностью», кнопки «К началу»/«↓ Новый ответ».
- Fallback-banner: «DeepSeek недоступен. Отвечаю локальными подсказками.» +
  причина + «Повторить», `role=status`, бейдж 12px; retry скрывает баннер.
- Meta-intent («что обсуждали/прошлые чаты») → `context.memory` с последними
  3 чатами (title + последнее user-сообщение 200), `context.chatId`; если
  истории нет — локальный список тем, без «не храню».
- Mobile: панель **390×844 inset 0** (100dvh), без backdrop и рамок, header 61,
  actions 44×44, composer bottom 787; fab hidden.
- Скриншоты `screens/mascot/ux-*.png` (5); метрики `qa-mascot-ux.json`.

---

## 16. Фикс шума platform-эндпоинта (13.11.2026, 20:10)

- `detectPlatformMode()` больше не вызывает `GET /v1/admin/tenants` без
  явного платформенного режима (`agil_platform_mode` / чекбокс на входе).
  Для обычного tenant-ключа запроса нет, 403/console.error отсутствуют,
  «Организации»/switch скрыты.
- С платформенным флагом — ровно один `GET /v1/admin/tenants`, раздел и
  переключатель видимы; при 403/404 fallback остаётся, запрос не повторяется.
- Headless (`qa/dashboard/qa-platform-network.json`):
  tenant key — `requests=[]`, `consoleErrors=[]`, orgHidden/switchHidden=true;
  platform — `requests=['GET /v1/admin/tenants']`, consoleErrors=[],
  orgHidden/switchHidden=false; page errors 0.

---

## 17. «Настроить по скриншоту» (13.11.2026, 20:48)

- В composer маскота — скрепка; PNG/JPEG/WebP ≤5 МБ. Анализ локальный
  (canvas 64×64), изображение не отправляется на сервер.
- Тёмный кадр: сцена dark, яркость 2%, фон `#181830`, primary `#3078f0`,
  accent `#304860`; светлый: сцена light, яркость 91%, фон `#ffffff`,
  primary `#d8d8f0`, accent `#d8f0f0`. Свотчи/приватность/кнопки на месте.
- Layout-рекомендация `compact` + `immersiveVideo` показана для светлого кадра
  с пустой шапкой; применяется только отдельной кнопкой.
- «Применить тему» → guard (`config`) → PATCH `/v1/tenants/demo/config` с
  `theme.colors`; preview закрывается, toast «Тема из скриншота применена…».
  «Другой вариант» меняет primary/accent/фон; «Отмена» закрывает без записи.
- Assistant получил `context.screenshot` с dominantColors/brightness/scene/
  suggestedTheme/layoutRecommendation на каждом из 3 тестов.
- 390: preview 374 внутри 390, actions 40×40, overflow 0.
- QA: `qa-screenshot-theme.json`, screenshots `screens/mascot/screenshot-*.png`
  (4), page/console errors 0.

---

## 18. Retest P1/P2/P3 (13.11.2026, 20:55)

- **Platform login**: при включённом «Платформенном ключе» форма проверяет
  `GET /v1/admin/tenants` (200 + `platform:true`) и создаёт platform-сессию;
  `GET /v1/admin/overview` не вызывается. Headless: overview=0, tenants с ключом,
  hash `#/organizations`, nav/switch видимы. Tenant login: overview как раньше,
  tenants=0, org/switch скрыты. 403 platform — русский alert без английских
  сообщений и без «консоли».
- **Docs**: «Сделать основным» → `PUT /v1/admin/docs/default` (body
  `{name}`), не POST. После сохранения история содержит 2 версии, restore
  создаёт новую (mock: 3 версии). Для загруженного документа кнопка
  «Удалить загруженный», confirm честно говорит об удалении без встроенной
  версии; DELETE `/docs/uploaded.md`; 404 обрабатывается как «уже удалён».
- **390 docs**: вкладки уходят в grid 2×2, все четыре (в т.ч. «Загрузить»)
  видны в viewport, page overflow=false.
- **390 fab**: заголовок карточки комнаты не пересекается с fab
  (`overlap=false`), content padding-bottom 88px; fab 44×44.
- Ошибки 401/403: русские заголовки/сообщения, без кодов-английских и
  «подробностей в консоли»; `console.error` в refresh убран.
- QA: `qa-retest.json`, screenshots `screens/retest-*.png` (7).

---

## 19. Опросы/Q&A + лобби в комнате (13.11.2026, 21:25)

- Room detail tabs: **«Опросы»** и **«Ожидают входа»**; список опросов с
  вопросом/статусом/вариантами/голосами/долями/всего/создателем/временем,
  фильтр «Все/Активные/Закрытые», создание (2–10 вариантов, multiple,
  anonymous, poll/qna), закрытие, результаты bar-chart, копирование JSON и
  Markdown. Polling каждые 10 с с backoff; 404/405/501 → «Опросы появятся после
  обновления сервера»; 409 закрытия — «уже закрыт».
- Лобби: pending-карточки (имя, страна, время, TTL), «Впустить»/«Отклонить»/
  «Отклонить всех», счётчик, polling каждые 5 с с backoff; 404/405/501 →
  «Лобби появится после обновления сервера»; 409 — «уже обработана».
- Обзор: бейдж **«Ожидают: N»** (сумма pending по активным комнатам),
  скрыт при 0; headless mock — «Ожидают: 3».
- Headless mock: polls list 2 (open/closed), 4 bar-сегмента, create body,
  close toast; lobby 3 cards/admit/reject, count 3, overview badge; fallback
  оба сообщения; 390 карточки без overflow, кнопки ≥40; ошибок JS 0.
- QA: `qa-polls-lobby.json`, screenshots `screens/polls/**` (4) и
  `screens/lobby/**` (5).
