# UX/QA-ревью панели-маскота «Agil» в AGIL Calls

**Дата:** 13.11.2026, 19:25–19:27 MSK  
**Стенд:** https://calls.guardify.ru/dashboard/  
**Проверенный билд:** `/dashboard/assets/index-CqXNGdp8.js` (sha256 `ca903736…`), CSS `index-C30S2F_i.css`  
**Ключ:** получен по SSH, только в памяти; в отчёт, файлы и скриншоты не попадал.  
**Артефакты:** `qa/ux-mascot/screens/**` — **34 PNG**, `qa/ux-mascot/metrics-v2.json`, `qa/ux-mascot/results-v2.json`, `qa/ux-mascot/live-bundle-v2.json`.

> Live пересобирался несколько раз во время ревью (`DpwhhSyL` → `…` → `CqXNGdp8` → `tf7MYnsu`). Все 34 скриншота и цифры ниже — **один билд `CqXNGdp8`**, в ходе прогона хеш не менялся. В билде `tf7MYnsu` (появился уже после прогона) возможны дальнейшие правки; перед релизом стоит перепроверить P2-находки на финальном билде.

---

## 0. Вердикт

**Почти готово к бете: один P1 (статус fallback) и набор P2 по плотности/истории/доступности.**

Панель сильно выросла относительно исходной жалобы:

- **fab скрывается** при открытой панели (`display:none`), больше не «торчит» под ней;
- имя **Agil**, миграция старых «Аги»;
- панель **докается справа** (410×872, `right:14; top:14; bottom:14`), **scrim убран**, контент сдвигается на 424 px — рабочий стол больше не блокируется, docs открываются в новой вкладке;
- ответы **рендерятся как markdown**, длинные сворачиваются до 220 px, есть «Показать полностью»/«Свернуть», «К началу»/«↓ Новый ответ», автоскролл к началу нового ответа;
- **mobile full-screen** 390×844 без 8 px рамки; header-действия 44×44, история 44×44;
- появились **история** (автозаголовки, поиск, rename/delete, новый чат) и **память**, включая общий вопрос «что обсуждали» — ответ перечисляет темы прошлых чатов;
- fallback-баннер с «Повторить» **есть в коде**, но в первом рендере ошибки не появляется — это единственный P1.

Главный оставшийся разрыв — **первый экран**: на пустом состоянии настройки занимают 290 px из 872 (33%) на desktop и 320 из 844 (38%) на mobile, примеры продублированы, а под сообщения остаётся 247 px / **146 px**. Пользователь видит «форму про роль», а не приглашение к разговору.

**P0 не найдено:** утечек admin-ключа, потери чатов, блокирующих ошибок, падения панели нет.

---

## 1. Что и как проверяли

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

- Puppeteer + headless Chrome, реальный прод-API и реальный ключ тенанта (ключ только в памяти, нигде не сохранён).
- **1440×900** и **390×844**, темы **light/dark**; дополнительно **390×500** как симуляция открытой экранной клавиатуры.
- Для каждого состояния: скриншот, `getBoundingClientRect`, `getComputedStyle`, контраст WCAG с композитингом полупрозрачных фонов, `elementFromPoint` по контролам, перекрытия, z-index, скролл, `aria-*`, AX-дерево, Tab/Shift+Tab/Escape.
- Интерактив: открытие/закрытие, роль, отправка, typing, ответ, длинный ответ, длинный чат (60 сообщений), история, поиск, rename, delete, «Новый чат», память, навигация с открытой панелью, docs-ссылки (открываются в новой вкладке), 500-ответ сервера, resize, mobile.
- Длинные чаты и историю дополнительно готовили через `localStorage` — это штатное хранилище панели.
- Продуктовый код **я не менял**; созданы только `qa/ux-mascot/**` и этот отчёт.

### 1.2. Покрытие

| Группа | Что снято |
|---|---|
| 1440 light | closed fab, пустая панель, роль/настройки, typing, ответ, новый чат, история, поиск без результатов, rename, delete, дубли пустых чатов, «Что я заметил», навигация, docs-клик, длинный чат (низ/верх), память, fallback |
| 1440 dark | closed, длинный чат, история |
| 390 light | closed, пустая панель, история, ответ, длинный чат, клавиатура, rename |
| 390 dark | пустая панель, история |
| Кропы | панель целиком: empty / answer / history |

Все 34 PNG — в `qa/ux-mascot/screens/`; карта — в §11.3.

### 1.3. Референсы

- Nielsen, [10 usability heuristics](https://www.nngroup.com/articles/ten-usability-heuristics/) — статус, контроль, консистентность, recognition, минимализм, восстановление после ошибок.
- Siemens Element, [AI chat history pattern](https://github.com/siemens/element/blob/main/docs/patterns/ai/ai-chat-history.md) — auto titles, preview, temporal groups, same-panel history, действия в overflow.
- Luke Wroblewski, [Usable Chat Interfaces to AI Models](https://www.lukew.com/ff/2093/usable-chat-interfaces-to-ai-models) — «люди теряются в потоке ответов»; collapse/expand, home session, навигация по длинным ответам.

### 1.4. Сверка с эвристиками

| Эвристика | Наблюдение | Оценка |
|---|---|---|
| 1. Visibility of system status | typing есть; fallback-баннер не появляется сразу; память использована, но не показана | ⚠️ |
| 2. Match real world | «роль вручную» vs «Роль ассистента (авто по экрану)»; иконки gear/key/audit | ⚠️ |
| 3. User control | X/Esc/новый чат/rename/delete; Esc из истории закрывает панель | ⚠️ |
| 4. Consistency | docked-панель, markdown, история — единообразно; но 3 индикатора роли | ⚠️ |
| 5. Error prevention | confirm на удаление; «+» создаёт бесконечные пустые чаты | ⚠️ |
| 6. Recognition over recall | список чатов, автозаголовки, поиск; нет превью/групп/счётчика | ✅/⚠️ |
| 7. Flexibility | «Показать полностью», «К началу», «Новый ответ»; нет stop/regenerate/copy | ✅/⚠️ |
| 8. Minimalism | пустой экран перегружен настройками и дублями; в чате — хорошо | ⚠️ |
| 9. Error recovery | fallback-баннер есть, но с задержкой; badge 3.57:1 | ❌ |
| 10. Help/docs | docs в новой вкладке; память описана только в свёрнутых настройках | ✅/⚠️ |

---

## 2. Топ находок по severity

### P0 — нет

Утечки ключа, потери истории, блокирующего краша нет. В запросе `/v1/admin/assistant` admin-ключа нет (`containsSecret:false`), в DOM и на скриншотах ключа нет.

### P1 — высокий приоритет

| ID | Находка | Доказательство | Что делать |
|---|---|---|---|
| **P1-1** | Fallback-баннер не показывается сразу после ошибки. При 500 сервер вернул локальный ответ: `badgeVisible=true`, но `.mascot-fallback-banner` **нет**; после закрытия/открытия панели баннер появляется. | `immediate.banner=false`, `reopened.banner=true`; `1440-light-19-error.png` показывает только badge «локальные подсказки» (12 px, контраст **3.57:1**); в коде баннер добавляется в `renderMessages` до того, как `localMode` становится `true`. | В `catch` сначала выставить `localMode=true`/`localReason`, затем `addMessage`/`renderMessages`; либо явно вставить баннер после ответа. Показывать: «DeepSeek недоступен. Отвечаю локальными подсказками.» + «Повторить» + «Настройки → Подключение»; badge 12 px и контраст ≥4.5; `role=status`. |

### P2 — средний приоритет

| ID | Находка | Доказательство | Что делать |
|---|---|---|---|
| **P2-1** | Первый экран перегружен: настройки **290.3 px** (33% панели 872), messages **247.3 px** (28%), примеры продублированы — 3 стартера в настройках и те же 3 чипа внизу. На mobile настройки **320.5 px** (38%), messages **146.5 px** (17%). | `1440-light-02-empty.png`, `390-light-02-empty.png`, `panel-1440-light-empty.png`; `settings=290.3`, `messages=247.3`, `duplicate=3`; mobile `settings=320.5`, `messages=146.5`. | Настройки по умолчанию **свернуты всегда**; примеры показывать **один раз** в empty-state; нижние suggestions скрыть до первого ответа; messages min 320/260. |
| **P2-2** | Длинный чат: в store 60 сообщений, рендерятся только **20**; нет «Показать предыдущие»/счётчика. Открывается **с начала последних 20** (`scrollTop=0`, `atBottom=false`), а не с последнего сообщения. | `long.store.count=60`, `long.messages.count=20`, `scrollTop=0`; `1440-light-16-long-top.png`, `390-light-05-long.png` (начинается с «Вопрос 21»/«Вопрос 21», хотя есть ответы 60). | «Показать предыдущие 20» + «20 из 60»; при открытии — к последнему сообщению (или к первому непрочитанному); `↓ Новый ответ` оставить. |
| **P2-3** | История: строки **38 px** (mobile 50), действия **28×28** (mobile 44), длинный title обрезается **без ellipsis** (desktop 368/245, mobile 393/195); нет заголовка «История чатов», счётчика и групп «Сегодня/Вчера». | `1440-light-07-history.png`, `390-light-03-history.png`; `titleScrollWidth=368`, `titleClientWidth=245`; `row.h=38`. | Строка 56–64 desktop/64 mobile; title 14/1.3 one-line ellipsis; meta 12; actions в overflow `⋯`; заголовок «История чатов · 3»; группы по дате. |
| **P2-4** | «+» создаёт новый пустой чат каждый раз: 2 клика → 2 строки «Новый чат»; Esc из истории **закрывает всю панель**, а при повторном открытии панель снова в истории. | `1440-light-11-history-empty-chats.png`; `emptyCount=2`; `ESCAPE_HISTORY {"panel":false,"history":false}`; `escapeFromHistory`; reopened.history=true. | Если текущий чат пуст — не создавать новый, фокусировать composer/переиспользовать; Esc: history → chat, chat → close; `historyView` не сохранять между закрытиями. |
| **P2-5** | При открытии панели контент сдвигается на 424 px: 4 KPI-карточки превращаются в 3+1, «События безопасности» уезжает на второй ряд; заметный reflow/layout shift. | `1440-light-02-empty.png` — 4-я карточка под рядом; `.app.is-mascot-docked .main{margin-right:424px}`. | На ≥1440 — overlay без сдвига (панель поверх правого края, но без scrim) или KPI-grid 2×2 в docked-режиме; на 1280–1439 сдвиг оставить. |
| **P2-6** | Типографика и таргеты всё ещё мелкие: body **13 px**/20.15 (база 14), meta 12, badge 12, но desktop header/send **28×28**, `docs` **18 px**, role-chip **22 px**, notice-toggle **25.3 px**, settings summary **30 px**. | `typography`, `interactive`; `1440-light-05-answer.png`; `docs` 18 px; mobile header уже 44. | Body 14/1.55; meta 12–13; desktop icons 32, docs 32, role-chip 28; mobile 44; не опускаться ниже 12. |
| **P2-7** | Роль: три индикатора (chip в шапке, hint, summary) и противоречие — chip «Проверка безопасности», hint «роль вручную», label селекта «Роль ассистента (авто по экрану)». | `1440-light-03-role-settings.png`; `ROLE`: hint «роль вручную», label «…(авто по экрану)». | Один chip; label «Роль ассистента»; helper «Авто по экрану: Первые шаги» / «Выбрано вручную: Проверка безопасности»; toast при сбросе роли по навигации. |
| **P2-8** | «Что я заметил» — тихий no-op: после клика `.mascot-notices` hidden, count 0, никакого «Пока нет замечаний». | `1440-light-12-notices.png`; `NOTICES before/after` hidden/0. | Toast/empty-state «Пока нет замечаний»; либо скрыть кнопку при 0; показывать счётчик. |
| **P2-9** | A11y: панель немодальная (`region`) — это правильно, но **фокус-ловушка осталась**: 12 Tab не выводят из панели. Typing/fallback не объявляются; `role=log` перерисовывается целиком; role-chip доступен как «Первые шаги» без «открыть настройки». | `FOCUS.tabSeq` — все `inPanel=true`; AX: `region`, combobox с именем; `.mascot-typing` без live. | Для docked-панели убрать trap (Tab выходит в dashboard), оставить Esc; `role=status` для typing/fallback; append-only лог; `aria-label="Роль: Первые шаги. Открыть настройки"`. |
| **P2-10** | История/память: чаты в `localStorage` без шифрования; память отправляет сырые прошлые сообщения (149 символов в тесте) без redaction; нет тумблера, «Очистить память», «Удалить всё», предупреждения о токенах. | `memory` 149 символов с текстом прошлого чата; `containsSecret:false`; `storageKeys`; delete-диалог «история этого браузера». | Тумблер памяти + «Очистить память»; redaction `sk-`, `ADMIN_KEY`, `x-api-key`, hex/base64; предупреждение «Не вставляйте токены»; бейдж «Хранится в этом браузере»; экспорт/удалить всё. |
| **P2-11** | Пустая панель показывает **«К началу» и «↓ Новый ответ»** даже без сообщений; на коротком ответе — ~400 px пустоты, controls висят над composer. | `1440-light-02-empty.png`, `390-light-04-answer.png`; `interactive` содержит jump-кнопки на empty. | Показывать jump только при переполнении; для короткого чата `justify-content:flex-end` или welcome-блок; не дублировать навигацию. |
| **P2-12** | Модалка rename/delete: один scrim, но очень тёмный; input не выделяет текст; после delete нет undo. | `1440-light-09-rename-modal.png`, `1440-light-10-delete-confirm.png`. | `input.select()`; scrim 40–48%; toast «Чат удалён» + «Вернуть» 5 с. |

### P3 — полировка

| ID | Находка | Что делать |
|---|---|---|
| **P3-1** | Нет copy/regenerate/stop, времени сообщений. | Hover «Копировать»/«Повторить»; «Стоп» при стриме; время 11 px. |
| **P3-2** | Нет «Очистить историю»/экспорта; локальность раскрыта только в delete. | «…» в истории: экспорт JSON, «Очистить всё»; подпись «Хранится в этом браузере». |
| **P3-3** | Иконки: history = `audit`, rename = `settings`, role = `key`. | Clock/history, pencil, user/shield; aria-label с глаголом. |
| **P3-4** | В history нет заголовка/счётчика; даты 12 px; нет группировки. | «История чатов · 3», 12 px, «Сегодня/Вчера/7 дней/Раньше». |
| **P3-5** | Docs-футер 57 px (2 строки) на 410 px, ссылки 18 px. | 12 px/32 px target; на mobile — в overflow/settings. |
| **P3-6** | Длинный ответ по умолчанию свёрнут до 220 px; «Показать полностью» внизу. | Показывать 8–12 строк + кнопку сверху/снизу; запоминать раскрытие; не скрывать ключевые шаги. |
| **P3-7** | «роль по экрану»/«роль вручную» — жаргон; два «Первые шаги» в шапке. | «Подобрана по экрану»/«Выбрана вручную»; один индикатор. |

---

## 3. Разбор состояний

### 3.1. Закрытая панель и fab

- Fab 58×58 desktop / 44×44 mobile; при открытии `hidden` + `display:none` — **жалоба «кнопка под панелью» закрыта**.
- Backdrop отсутствует; панель docked; клик по dashboard не закрывает панель (это ожидаемо для docked), закрытие — X/Esc.
- Fab в закрытом состоянии перекрывает правый нижний угол контента (mobile — карточку «События безопасности»); допустимо для FAB, но на desktop лучше не перекрывать интерактивные контролы.

### 3.2. Пустое состояние

- Панель **410×872** на 1440, x=1016, y=14; контент сдвинут на 424 px.
- Header 58.4, settings **290.3**, messages **247.3**, suggestions 72, composer 111, docs 57.
- Настройки открыты по умолчанию, 33% панели; на mobile — 320.5 (38%), messages 146.5.
- Внутри: select 40, 3 примера, абзац о памяти 12 px, «Что я заметил». Те же 3 примера ещё раз внизу — **дублирование 3/3**.
- «К началу»/«↓ Новый ответ» видны сразу, хотя сообщений нет — ложная affordance.
- Приветствие «Привет! Я Agil…» и «Отвечаю markdown-ом…» читаемо; 12–13 px.
- После первого сообщения настройки сворачиваются до 32 px — правильный progressive disclosure, но только **после** отправки.

### 3.3. Роль и настройки

- Select доступен по имени («Роль ассистента (авто по экрану)»).
- При ручном выборе chip «Проверка безопасности», hint «роль вручную», label остаётся «(авто по экрану)» — **противоречие**.
- Три индикатора роли: chip, summary, label; нужен один.
- Навигация сбрасывает ручную роль на «по экрану» — полезно, но без объяснения пользователю.

### 3.4. Отправка и typing

- Enter отправляет, Shift+Enter — новая строка; send disabled; input очищается; typing «Agil думает…» + иконка.
- Typing не объявляется скринридеру (нет `role=status`).
- Реальный ответ — **6.2 с**; для 25 с таймаута нужен прогресс/«Отменить».
- Send 28×28 desktop / 44×44 mobile.

### 3.5. Ответ

- Markdown **рендерится**: заголовки, bold, списки, code — без сырых `#`/`**` (`markdownHashes=0`, `markdownBold=0`).
- Длинный ответ >900 символов/12 строк сворачивается до **220 px**; есть «Показать полностью», «Свернуть», «К началу», «↓ Новый ответ».
- `scrollTop=0` — начало ответа видно; это закрывает прежний дефект «ответ с конца».
- Body 13/20.15; author 10.5; заголовки 14–15; читаемо, но 13 px меньше базовых 14.
- Suggestions после ответа — 3 onboarding-чипа; занимают 106 px (12% панели), не всегда контекстны.

### 3.6. Длинный ответ и длинный чат

- 60 сообщений в store → 20 в DOM; `scrollTop=0`, `atBottom=false`, `scrollHeight=2529`, `clientHeight=578`.
- Открывается на «Вопрос 21» — первое из последних 20, а не последнее сообщение; есть «↓ Новый ответ».
- Нет «Показать предыдущие», нет «20 из 60»; 40 сообщений недоступны.
- Длинный ответ свёрнут; если пользователь раскрывает, он остаётся в 578 px окне и скроллится.
- Визуального наложения на settings bar больше нет (панель docked/полноэкранная).

### 3.7. Ошибка/fallback

- При 500: локальный ответ, badge «локальные подсказки» (12 px, **3.57:1**), но **баннер не появляется** до переоткрытия панели.
- После переоткрытия: «DeepSeek недоступен. Отвечаю локальными подсказками.» + «DeepSeek недоступен» + «Повторить» — хороший паттерн, но с задержкой.
- Нет «Проверить подключение»; badge title только hover.
- `notices` пуст; info-уведомления по-прежнему фильтруются.

### 3.8. История

- Тот же panel; поиск, back, «Новый чат», текущий чат с border primary.
- Автозаголовок первого user-message (56 симв.) работает; store показывает корректные title.
- Rename: modal `role=dialog`, `aria-modal=true`, focus input, сохранение; но input не `select()`.
- Delete: confirm с названием и «…удалены из истории этого браузера», danger; удаление текущего переключает на следующий; undo нет.
- Строки 38 px desktop / 50 mobile, действия 28/44, дата 12; длинный title **обрезается без ellipsis** (368/245, 393/195).
- Нет заголовка «История чатов», счётчика, групп «Сегодня/Вчера».
- Поиск по title+сообщениям есть; empty state «Чатов не найдено» проверен.
- Esc из истории **закрывает панель**, при reopen — снова history; нет «назад».
- «+» создаёт дубли пустых чатов.

### 3.9. Память

- Тематический recall: на «Что мы обсуждали про TURN-пул?» уходит `memory` **149 символов** (title + последний user-message), ответ перечисляет «Масштабирование и TURN-пул».
- **Общий вопрос «что обсуждали?» теперь работает**: ответ «Вот темы прошлых бесед…»; в запросе `memory` 149; это закрывает прежний P1.
- В запросе **нет admin-ключа** (`containsSecret:false`), в DOM/скриншотах ключа нет.
- Но: нет chip «использована память», нет источника у ответа, нет тумблера/очистки/redaction; раскрытие — только в свёрнутых настройках 12 px.
- Память локальная; между браузерами не синхронизируется.

### 3.10. Навигация и docs

- При переходе `#/security` панель остаётся docked, chip «Проверка безопасности», hint «роль по экрану»; контент сдвинут — форма читаема, панель не блокирует.
- Docs-ссылки имеют `target="_blank"` и открываются в новой вкладке; hash не меняется — это корректно.
- Content shift 424 px; KPI 4→3+1 (см. P2-5).

### 3.11. Мобильный режим

- **Full-screen 390×844**, x=0,y=0,w=390,h=844 — прежняя 8 px рамка исправлена.
- Header 61: avatar 36, имя, chip 22, actions 44×44.
- Empty: settings 320.5, messages 146.5, suggestions 88, composer 127, docs 57 (2 строки). Первый экран перегружен.
- История: full-screen; строки 50, действия 44; title 393/195 без ellipsis; нет заголовка.
- Ответ: короткий чат — ~400 px пустоты; jump controls видны.
- 390×500 (клавиатура): panel 0–500, composer 316–443, input 325–385 виден, messages 179 — flex-adaptation работает; `visualViewport` в коде нет, на iOS нужен реальный тест.
- Rename modal 370×228 — влезает; один scrim.
- Fab 44×44, скрыт при открытой панели.

---

## 4. Редизайн desktop: wireframe и токены

### 4.1. Что уже правильно

Docked-панель без scrim — правильное направление. Осталось убрать reflow и сделать первый экран разговорным.

```
≥1280 px, панель docked
┌───────────────────────────────────────────────┬──────────────────────────┐
│ dashboard content (margin-right: 424px)        │  Agil              [↺][＋][×] │ 56
│                                               │  Помощник · DeepSeek      │ 20
│                                               ├──────────────────────────┤
│                                               │  messages 1fr, padding16 │
│                                               │  Agil · 14:32            │
│                                               │  markdown 14/1.55        │
│                                               │  [Свернуть]              │
│                                               │  [Память: TURN-пул →]    │
│                                               ├──────────────────────────┤
│                                               │  [chips 44]              │
│                                               │  [textarea 44–132][send] │ 92
│                                               │  Enter…                 │ 18
│                                               ├──────────────────────────┤
│                                               │  Документация · API      │ 28
└───────────────────────────────────────────────┴──────────────────────────┘
```

- Панель: `width: 410px; top:14; bottom:14; right:14; border-radius:12; box-shadow;` — уже так.
- На ≥1440 — **overlay без сдвига** (контент не reflow), либо KPI-grid 2×2; на 1280–1439 сдвиг.
- Header 56, body 1fr, suggestions 44, composer 92, footer 28; messages min 320.
- Убрать дублирующий summary роли; настройки — popover от chip.
- Empty: один набор примеров; настройки закрыты; jump controls скрыты.

### 4.2. Токены

| Элемент | Размер |
|---|---|
| Panel | 410 px (desktop), 100% (mobile) |
| Panel height | `calc(100dvh - 28px)` desktop; `100dvh` mobile |
| Header | 56 px, padding 12/16 |
| Avatar | 32 px |
| Name | 15/1.2/600 |
| Subtitle/status | 12/1.3/muted |
| Role chip | 28 px height, 12 px |
| Icon button | 32 desktop / 44 mobile, icon 18 |
| Messages padding | 16, gap 16 |
| Body | 14/1.55 |
| Heading in answer | 15/600, margin 16/8 |
| Meta | 12/1.3 |
| User bubble | max 85%, radius 12, padding 10/14 |
| Suggestions | 32–36, 12 px, max 3 |
| Composer | 44–132, send 36/44 |
| Hint | 12, only focus |
| Docs | 12, 32 px target |
| History row | 56–64 desktop, 64 mobile |
| History actions | 32/44 |
| Spacing | 4/8/12/16/20/24 |
| Radius | 12 controls, 16 panel, 999 chips |

### 4.3. Markdown и длинные ответы

- Санитайз markdown: h3 15/600, h4 14/600, strong 600, списки 14/1.55, code 12.5 mono, ссылки primary с underline, цитаты border-left 3.
- Max width 62ch; `overflow-wrap:anywhere`; таблицы — horizontal scroll.
- Свёрнутый вид: 10–12 строк / 220 px, gradient + «Показать полностью (≈N строк)»; после раскрытия — «Свернуть».
- `scrollTop = startOfNewAnswer` (сделано), `К началу`/`↓ Новый ответ` — только когда есть переполнение.
- Кнопки: «Копировать», «Повторить» (последний), «Стоп»; при стриме — прогресс.

### 4.4. Настройки и роль

- Настройки закрыты по умолчанию; открываются popover 320 px от chip/шестерёнки.
- Label «Роль ассистента»; helper динамический.
- Внутри: select, память (toggle/count/clear), «Что я заметил» со счётчиком, docs, «Очистить историю».
- Один chip роли; summary не дублирует.

---

## 5. History UX spec

### 5.1. Структура

```
[← 44]  История чатов · 3            [Поиск 44........] [+ Новый чат 44]
Сегодня
[Чат 1 ..................................] 14:32 · 12 сообщ.  [⋯]
[Чат 2 ..................................] 12:08 · 4 сообщ.   [⋯]
Вчера
[Чат 3 ..................................] 18:40 · 8 сообщ.   [⋯]
```

- Тот же panel, не отдельный drawer; Siemens pattern.
- Header: back 44, title 14/600, count 12 muted; search 44; New chat 44 primary.
- Group labels 11/600 uppercase muted, sticky; «Сегодня», «Вчера», «7 дней», «Раньше».
- Row 56–64 desktop/64 mobile: current border-left 3 primary + bg 6%; title 14/1.3 one-line ellipsis (не резать без `…`); meta 12 «14:32 · 12 сообщений»; overflow `⋯` 32/44.
- Действия в overflow: «Переименовать», «Удалить», «Экспорт». Не держать 2 иконки всегда.
- Hover/focus — `⋯`; mobile — 44.

### 5.2. Поведение

| Сценарий | Ожидание |
|---|---|
| Первое сообщение | title = первый user-message, 48–56 симв. + `…`; пусто — «Новый чат». |
| Rename | input с `select()`, max 80, empty → «Новый чат», Enter сохраняет, Esc отменяет. |
| Delete | confirm «Удалить чат «{title}»? Сообщения исчезнут из этого браузера.»; toast «Чат удалён» + «Вернуть» 5 с; последний → новый пустой. |
| New chat | если текущий пуст — не создавать дубль, фокусировать composer; иначе новый. |
| Current | `aria-current="true"`; при открытии — последний активный чат, не история. |
| Escape | history → chat; chat → close; modal → close modal. |
| Reopen | последний активный чат; `historyView` не сохраняется. |
| Search | title + сообщения; debounce 150; пусто → «Ничего не найдено. Измените запрос.» + «Сбросить». |
| Empty | «Пока нет чатов», «Задайте первый вопрос — он появится здесь», кнопка. |
| Sort | `updatedAt` desc; без прыжков при rename/delete. |
| Local-only | бейдж «Хранится в этом браузере» + экспорт/очистить всё. |

### 5.3. Тексты

«История чатов» · «Поиск по чатам…» · «Новый чат» · «Пока нет чатов» · «Ничего не найдено» · «Переименовать чат» · «Удалить чат?» · «Отмена»/«Удалить» · «Вернуть» · «Хранится в этом браузере».

---

## 6. Memory UX spec

### 6.1. Принцип

Память должна быть **видимой, управляемой и ограниченной**.

- Тумблер «Помнить темы из прошлых чатов» (12 px helper).
- Одноразовая подсказка в empty-state: «Agil может использовать темы из прошлых чатов. Настроить».
- Chip над ответом: `[Память: «Масштабирование и TURN-пул» · 2 сообщения →] [Не использовать]`.
- В настройках: «Память: 2 чата · 149 символов» + «Очистить память».
- Лимиты: 3 чата, 90 дней, ≤2 000 символов, последние 12 сообщений.
- Redaction: `sk-…`, `ADMIN_KEY=…`, `x-api-key`, hex/base64, email/phone; предупреждение «Не вставляйте токены и ключи».
- Общий вопрос «что обсуждали / о чём говорили / напомни темы / прошлые чаты» → 3 последних чата (title + последний user-message 200) и ответ списком. В v6 это уже работает; сохранить.
- Локальность: «Память хранится в этом браузере»; при удалении чата — удалить из памяти.

### 6.2. Статусы

| Состояние | UI |
|---|---|
| Память выключена | helper «Память выключена»; запрос без `memory`. |
| Совпадений нет | ответ без chip; в настройках «Совпадений не найдено». |
| Память использована | chip с источником и числом сообщений; `aria-label`. |
| Ошибка/таймаут | fallback-баннер + «Повторить». |
| Очистка | confirm «Очистить память? Это удалит фрагменты прошлых чатов из этого браузера.» |

### 6.3. Что сохранить

- Тематический recall и общий «что обсуждали» работают.
- В запросе нет admin-ключа (`containsSecret:false`).
- `memory` собирается из прошлых сессий, не из текущей.
- Ограничение 2 000 символов.

---

## 7. Mobile spec

- **Full-screen уже сделан**: `inset:0; width:100%; height:100dvh; border-radius:0;` — сохранить.
- Header 56/61; avatar 32; actions 44×44; role chip скрыть/уменьшить (22 px сейчас).
- Empty: настройки свернуть; messages min 260; один набор примеров; jump controls скрыть.
- Composer 44–132, send 44; docs убрать в overflow/настройки (сейчас 57 px, 2 строки).
- History: full-screen; row 64; actions 44; search 44; back 44.
- Keyboard: `visualViewport` → `--vvh`; composer `position:sticky; bottom:0`; `input.scrollIntoView({block:'nearest'})`; проверить 390×500 и 390×844.
- Short chat: `justify-content:flex-end` при <3 сообщениях или welcome-блок.
- Rename modal `calc(100vw - 32px)`, max 420; один scrim.

---

## 8. Accessibility checklist

| Проверка | Статус | Детали |
|---|---|---|
| Роль панели | ✅ | `region` уместен для docked non-modal; scrim нет. |
| Focus trap | ❌ | панель non-modal, но Tab заперт; нужно разрешить выход в dashboard. |
| Начальный фокус | ✅ | composer; history — search. |
| Escape | ⚠️ | chat → close + focus fab; history → close (нужно back); modal → close. |
| Возврат фокуса | ✅ | fab. |
| Имена кнопок | ⚠️ | header actions aria-label; select label; role-chip «Первые шаги» без «открыть настройки». |
| aria-live | ⚠️ | messages log; typing/fallback не объявляются; log перерисовывается. |
| Тап-таргеты | ⚠️ | mobile header/history 44 ✅; docs 18, role-chip 22, notice 25.3, summary 30 ❌; desktop icons 28. |
| Контраст | ⚠️ | body ≥5.7; badge 3.57:1 (12 px); dark ≥6.8. |
| Типографика | ⚠️ | body 13/20.15; meta 12; 10.5 author; ниже базы 14. |
| `aria-current` | ❌ | current row только классом. |
| `aria-expanded` | ✅ | details нативный; fab. |
| Reduced motion | ✅ | float отключён. |
| Язык | ✅ | `<html lang="ru">`. |
| Секреты | ✅/⚠️ | admin-ключ не в DOM/запросе/скриншотах; но история/память могут содержать вставленные пользователем секреты (P2-10). |
| Модалки | ✅ | `role=dialog`, `aria-modal`, focus trap, Esc. |

---

## 9. Что сохранить

- **Docked-панель без scrim** 410×872; контент сдвигается; docs в новой вкладке.
- **Скрытие fab**, 58/44, `display:none` при открытии.
- **Agil** и миграцию «Аги».
- **Markdown** + collapse 220 + «Показать полностью»/«Свернуть» + «К началу»/«↓ Новый ответ» + scroll-to-start.
- **Full-screen mobile**; header/history actions 44.
- **История**: автозаголовки, поиск, rename/delete, текущий чат.
- **Память**: тематический и общий recall, цитирование чата/даты, 2 000 символов.
- **Sticky composer**, flex-layout, длинный чат скроллится.
- **Тёмная тема** — контраст 6.8–16.8.
- **Reduced motion**, отсутствие admin-ключа в DOM/запросе.

---

## 10. Приоритетный план

### Спринт 1 — P1 (0.5–1 день)

1. Fallback: выставить `localMode/localReason` до `addMessage`, рендерить баннер сразу; badge ≥4.5; «Повторить» + «Проверить подключение»; `role=status`.

### Спринт 2 — P2 (2–3 дня)

2. Empty: настройки свернуть, примеры один раз, messages ≥320/260, jump-кнопки скрыть.
3. История: 56–64/64, ellipsis, overflow-меню, header/count/groups, Esc back, dedupe empty, undo, «Показать предыдущие 20».
4. Reflow: на ≥1440 overlay без сдвига или KPI 2×2.
5. Типографика/таргеты: body 14, docs 32, role-chip 28/44, notice 32.
6. Роль: один chip, динамический label.
7. A11y: убрать focus trap, `aria-current`, typing/fallback live, append-only log.
8. Приватность: toggle/clear memory, redaction, local-only badge, export/delete all.

### Спринт 3 — P3 (1 день)

9. Иконки, copy/regenerate/stop, timestamps.
10. Docs footer, collapse default, grouping, modal polish.

### Definition of Done

- 500 → баннер виден сразу, badge ≥4.5, «Повторить» работает.
- Empty: настройки закрыты; messages ≥320/260; нет дублей; jump скрыт.
- History: 64 px, ellipsis, header/count, Esc back, нет дублей, «Показать предыдущие».
- Docked: нет reflow 4→3+1 (или KPI 2×2).
- Body 14; docs/role-chip/notice ≥32/44.
- Focus выходит из non-modal панели; typing/fallback объявляются.
- Memory chip + toggle + clear + redaction.

---

## 11. Артефакты, ограничения, карта скриншотов

### 11.1. Артефакты

- `qa/ux-mascot/screens/**` — 34 PNG.
- `qa/ux-mascot/metrics-v2.json` — 9 состояний, боксы, шрифты, контраст, таргеты, store.
- `qa/ux-mascot/results-v2.json` — checks/events/request payload (без ключа).
- `qa/ux-mascot/live-bundle-v2.json` — хеши старта/финиша.
- `qa/ux-mascot/qa-v2.mjs` — воспроизводимый прогон.

### 11.2. Ограничения

- Native `<select>` dropdown не снят (OS-level).
- Нет реального iOS/Android и скринридера; клавиатура симулирована 390×500, `visualViewport` не эмулирован.
- Один тенант demo; без нагрузки/429.
- Live пересобирался в ходе ревью; цифры — для `CqXNGdp8`; в `tf7MYnsu` возможны правки.
- Визуальный reflow 4→3+1 зафиксирован скриншотом, CLS не измерялся отдельным PerformanceObserver.

### 11.3. Карта ключевых скриншотов

| Файл | Что показывает |
|---|---|
| [`1440-light-01-closed.png`](../qa/ux-mascot/screens/1440-light-01-closed.png) | fab 58×58 |
| [`1440-light-02-empty.png`](../qa/ux-mascot/screens/1440-light-02-empty.png) | docked, пустая панель, настройки 290 px, дубли, jump |
| [`1440-light-03-role-settings.png`](../qa/ux-mascot/screens/1440-light-03-role-settings.png) | ручная роль vs label «авто по экрану» |
| [`1440-light-04-typing.png`](../qa/ux-mascot/screens/1440-light-04-typing.png) | typing, send disabled |
| [`1440-light-05-answer.png`](../qa/ux-mascot/screens/1440-light-05-answer.png) | markdown + collapse 220 + jump |
| [`panel-1440-light-answer.png`](../qa/ux-mascot/screens/panel-1440-light-answer.png) | типографика ответа |
| [`1440-light-07-history.png`](../qa/ux-mascot/screens/1440-light-07-history.png) | история: 38 px, обрезка title |
| [`1440-light-09-rename-modal.png`](../qa/ux-mascot/screens/1440-light-09-rename-modal.png) | rename, один scrim |
| [`1440-light-10-delete-confirm.png`](../qa/ux-mascot/screens/1440-light-10-delete-confirm.png) | delete confirm |
| [`1440-light-11-history-empty-chats.png`](../qa/ux-mascot/screens/1440-light-11-history-empty-chats.png) | два пустых «Новый чат» |
| [`1440-light-12-notices.png`](../qa/ux-mascot/screens/1440-light-12-notices.png) | «Что я заметил» без фидбека |
| [`1440-light-13-nav-open.png`](../qa/ux-mascot/screens/1440-light-13-nav-open.png) | docked на «Безопасности», контент читаем |
| [`1440-light-14-doclink-open.png`](../qa/ux-mascot/screens/1440-light-14-doclink-open.png) | docs в новой вкладке (hash не меняется) |
| [`1440-light-15-long-bottom.png`](../qa/ux-mascot/screens/1440-light-15-long-bottom.png) | длинный чат, 20 из 60 |
| [`1440-light-16-long-top.png`](../qa/ux-mascot/screens/1440-light-16-long-top.png) | начало последних 20 |
| [`1440-light-17-memory-answer.png`](../qa/ux-mascot/screens/1440-light-17-memory-answer.png) | память: общий вопрос |
| [`1440-light-18-memory-disclosure.png`](../qa/ux-mascot/screens/1440-light-18-memory-disclosure.png) | раскрытие памяти 12 px |
| [`1440-light-19-error.png`](../qa/ux-mascot/screens/1440-light-19-error.png) | fallback: badge 3.57, баннера нет |
| [`1440-dark-02-long.png`](../qa/ux-mascot/screens/1440-dark-02-long.png) | тёмный docked long |
| [`1440-dark-03-history.png`](../qa/ux-mascot/screens/1440-dark-03-history.png) | тёмная история |
| [`390-light-02-empty.png`](../qa/ux-mascot/screens/390-light-02-empty.png) | mobile full-screen, перегруз |
| [`390-light-03-history.png`](../qa/ux-mascot/screens/390-light-03-history.png) | mobile history, обрезка title |
| [`390-light-04-answer.png`](../qa/ux-mascot/screens/390-light-04-answer.png) | короткий чат, пустота + jump |
| [`390-light-05-long.png`](../qa/ux-mascot/screens/390-light-05-long.png) | mobile long, 20 из 30 |
| [`390-light-06-keyboard.png`](../qa/ux-mascot/screens/390-light-06-keyboard.png) | 390×500, composer виден |
| [`390-light-07-rename-modal.png`](../qa/ux-mascot/screens/390-light-07-rename-modal.png) | mobile modal |
| [`390-dark-01-empty.png`](../qa/ux-mascot/screens/390-dark-01-empty.png) | mobile dark empty |
| [`390-dark-02-history.png`](../qa/ux-mascot/screens/390-dark-02-history.png) | mobile dark history |

---

## 12. Итог одной фразой

**Docked-панель, markdown, collapse, full-screen mobile и память уже сделаны; осталось починить статус fallback и разгрузить первый экран, историю и доступность.**
