diff --git a/.gitignore b/.gitignore index f2bdae1..2ef611c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ playwright-report/ .playwright/ e2e/screenshots/ *.tsbuildinfo +__pycache__/ diff --git a/IMPLEMENTATION_REPORT.md b/IMPLEMENTATION_REPORT.md index ad0c9c3..f8a321c 100644 --- a/IMPLEMENTATION_REPORT.md +++ b/IMPLEMENTATION_REPORT.md @@ -1,92 +1,74 @@ -# Отчёт о реализации: Перевод Krona Front на дизайн-систему v3.0 и модуль «Коммуникации» (Correction Pass) +# Отчёт о реализации: Исправление мобильного и планшетного интерфейса «Коммуникаций» ## 1. Общие сведения -- **Проект:** krona-front -- **Рабочая ветка:** codex/design-system-communications-correction -- **UI-библиотека:** krona-library (@krona/tokens, @krona/themes, @krona/ui) -- **Стандарт дизайна:** KRONA_DESIGN_SYSTEM_V3.md (версия 3.0: «Благородный зелёный», «Светлый пергамент», «Графитовый тёмный лес») +- **Проект:** krona-front (`https://git.ochenstarik.ru/ochenstarik/krona-front`) +- **Рабочая ветка:** `codex/design-system-communications-mobile-tablet-correction` +- **Базовый коммит:** `49f5a34` +- **UI-библиотека:** krona-library (`@krona/tokens`, `@krona/themes`, `@krona/ui`) +- **Стандарт дизайна:** `KRONA_DESIGN_SYSTEM_V3.md` - **Эталонные макеты:** - - Коммуникация.png (Desktop 1440/1280) - - Коммуникация планшет горизонт.png (Tablet landscape 1024) - - Коммуникация планшет вертикальный.png (Tablet vertical 768) - - Коммуникация телефон.png (Mobile 360/390/430) -- **Целевой модуль:** /communications («Коммуникации») + - `Коммуникация.png` (Desktop 1440/1280) + - `Коммуникация планшет горизонт.png` (Tablet landscape 1024) + - `Коммуникация планшет вертикальный.png` (Tablet vertical 768) + - `Коммуникация телефон.png` (Mobile 360/390/430) +- **Целевой модуль:** `/communications` («Коммуникации») --- -## 2. Архитектура интеграции с дизайн-системой v3.0 +## 2. Ключевые архитектурные решения -### 2.1. Единый источник истины и семантические токены -Все дизайн-токены, темы оформления и компоненты подключаются напрямую из публичных скомпилированных пакетов библиотеки krona-library: -- @krona/tokens и @krona/tokens.css: палитра бренда, типографические шкалы (Manrope + Cormorant Garamond), радиусы (--krona-radius-*), тени (--krona-shadow-*), z-индексы и отступы. -- @krona/themes и @krona/themes.css: системные темы оформления v3: - 1. parchment-light — светлая пергаментная тема (теплый фон #F3EAD7 / #F9F6F0, контрастные карточки, хвойный текст #162820); - 2. -oble-green — фирменная лесная тема (глубокий хвойный #1E3A2F, мягкий фон, акцент шалфея); - 3. graphite-forest-dark — ночная графитовая тема (фон #121714, поверхность #1A211D, изумрудный акцент #34D399). -- @krona/ui (dist/index.mjs): атомарные компоненты библиотеки (Button, Input, Badge, Avatar, Tabs, Card, ConnectorPill, MessageBubble, Modal). Внешние зависимости React/React-DOM корректно экстернализованы, устраняя конфликт дублирования инстансов React. +### 2.1. Адаптивность от реальной ширины рабочей области (Container Queries / ResizeObserver) +Вместо фиксированных медиа-запросов экрана разметка `MessengersView.tsx` управляется через `ResizeObserver` по фактической ширине контейнера: +1. **Compact (< 600px):** Строгий 1-колоночный drilldown: + - **Уровень 1 (Список):** Полноэкранный список диалогов с поиском и фильтрами. + - **Уровень 2 (Чат):** Окно диалога с кнопкой возврата назад и тизером контакта снизу. + - **Уровень 3 (Контакт):** Полноэкранная карточка контакта со сделками CRM, задачами и AI-сводкой. +2. **Medium (600–949px):** 2 колонки: + - Список диалогов (~280–300px) слева. + - Активный диалог справа (занимает все оставшееся пространство, min 320px). + - Сплющенная горизонтальная карточка контакта под чатом **полностью устранена**. Карточка контакта открывается через слайд-овер `Drawer` по кнопке профиля в хедере чата. +3. **Wide (≥ 950px):** 3 колонки: + - Список диалогов (310–330px). + - Активный диалог (min 360px). + - Вертикальная карточка контакта (320–350px). -### 2.2. Управление темами и обратная совместимость -Хранилище тем src/stores/theme.ts синхронно устанавливает: -- Корневой атрибут data-krona-theme (parchment-light, -oble-green, graphite-forest-dark). -- Легаси-атрибут data-theme (light, soft-light, dark) для обратной совместимости с существующими компонентами и тестами. -- Сохранение активной темы в localStorage и поддержку prefers-reduced-motion. +### 2.2. Семантические токены и ликвидация сырых HEX-кодов +- Использованы переменные дизайн-системы V3: `var(--krona-primary)`, `var(--krona-surface)`, `var(--krona-primary-soft)`, `var(--krona-border)`, `var(--krona-gold)`. +- Никаких захардкоженных HEX-кодов в прикладной верстке. +- В `src/index.css` обеспечен импорт `@import '@krona/ui.css';`. + +### 2.3. Векторные SVG-портреты +- В `public/avatars/` сгенерированы 11 легковесных SVG-портретов (`ivan.svg`, `olga.svg`, `alexander.svg`, `elena.svg`, `dmitry.svg`, `maria.svg`, `sergey.svg`, `anna.svg`, `maxim.svg`, `irina.svg`, `artem.svg`). +- В моковых данных `mockData.ts` все контакты привязаны к соответствующим локальным SVG-аватарам. +- Компонент `` корректно отображает векторные портреты. + +### 2.4. Предотвращение скролла всего окна при новых сообщениях +- Заменен вызов `scrollIntoView()` на прямое управление контейнером сообщений: + `messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;` +- Это исключило нежелательный скролл всего документа (`window.scrollY === 0`). + +### 2.5. Устранение наложения мобильной нижней навигации +- В `AppShell.tsx` для маршрута `/communications` на мобильных устройствах добавлен отступ `pb-16`. +- Инпут чата и тизер контакта больше не перекрываются фиксированным баром `BottomNav` и доступны для взаимодействия. --- -## 3. Точная реализация макетов модуля «Коммуникации» +## 3. Матрица верификации (Fail-Closed Verification) -### 3.1. Десктоп (>= 1280px) — по макету Коммуникация.png -- **Шапка раздела:** Слоган курсивом *«Больше, чем общение с поддержкой»*, ботаническая гравюра-водяной знак, вкладки *Мессенджеры*, *Email*, *Каналы*, *Контакты*, *Настройки* с бейджами непрочитанных. -- **Панель коннекторов:** Пилюли каналов (Все 48, Telegram 12, WhatsApp 8, VK 6, MAX 4, Email 10) и кнопка «Подключить». -- **3 приподнятые скругленные карточки ( -ounded-2xl):** - 1. **Карточка 1 (Список чатов, 320–350px):** Поиск, фильтры («Все», «Непрочитанные», «Избранные»), сортировка, список диалогов со статусом в сети, бейджем канала и непрочитанными. - 2. **Карточка 2 (Активный диалог, центр):** - - Хедер: аватар, имя, статус «в сети», компания, должность, кнопки «В избранное», звонок, видеозвонок, меню. - - Сообщения: входящие белые пузыри с аватаром слева; исходящие шалфейно-оливковые пузыри (#E8EDE4 / #2F3A32) справа с двойной галочкой; карточка вложения PDF («Коммерческое предложение.pdf») с кнопкой «Скачать». - - Панель быстрых ответов (чипсы «Да, подготовим», «Отправить договор», «Нужны реквизиты?», «Назначить встречу» и др.). - - Инпут сообщений: кнопка скрепки, поле ввода с поддержкой Enter, AI-помощник (Sparkles), эмодзи, микрофон, коньячно-бронзовая кнопка отправки (#9A743D). - 3. **Карточка 3 (Карточка контакта / CRM, справа):** - - Аватар, имя, должность, компания, бейдж «Клиент», золотой бейдж «VIP», статус «В сети». - - Подключенные каналы (Telegram, WhatsApp, Email) с кнопкой «+ Канал». - - Вкладки: «Инфо», «✦ AI-сводка», «Связи (5)». - - Поля: телефон, email, ответственный менеджер, часовой пояс, заметки. - - Быстрые CRM-действия: **«Создать сделку»** и **«Создать задачу»** с тостом и фиксацией в хранилище. - -### 3.2. Планшет (768px – 1279px) — по макетам Коммуникация планшет вертикальный.png и Коммуникация планшет горизонт.png -- 2-колоночный адаптивный макет: - - Левая колонка: список диалогов (300–320px). - - Правая колонка: вертикальный стек, в котором сверху расположен активный чат с полноценным композером сообщений, а снизу — **горизонтальная карточка контакта** с аватаркой, именем, бейджем VIP, списком каналов и кнопками «Создать сделку» / «Создать задачу». - -### 3.3. Мобильные устройства (< 768px) — по макету Коммуникация телефон.png -- 1-колоночный 3-уровневый drilldown: - - **Уровень 1 (Список):** Полноэкранный список чатов с коннекторами. - - **Уровень 2 (Диалог):** При выборе чата открывается диалог с кнопкой возврата «Назад», входящими/исходящими сообщениями, чипсами быстрых ответов, композером и фиксированной внизу тизер-плашкой профиля («Иван Петров • Директор • Профиль →»). - - **Уровень 3 (Профиль):** Нажатие на «Профиль» открывает полную карточку контакта с вкладками, деталями и кнопками создания сделки/задачи, с кнопкой возврата «Назад к диалогу». - - Мобильная нижняя навигация не перекрывает элементы управления благодаря расчету отступов (pb-16), минимальный размер кликабельных зон >= 44x44px. +| Проверка | Команда | Результат | Статус | +|---|---|---|:---:| +| **Линтер** | `npm run lint` (`oxlint --deny-warnings`) | 0 warnings, 0 errors (238 файлов) | **PASS** | +| **Компиляция TypeScript** | `npx tsc -b` | Exit code 0 | **PASS** | +| **Модульные и интеграционные тесты** | `npm test` (`vitest run`) | 490/490 тестов в 27 файлах прошли | **PASS** | +| **E2E тесты (Playwright)** | `npx playwright test e2e/13-communications-visual-and-responsive.spec.ts` | 32/32 тестов во всех 8 вьюпортах прошли | **PASS** | +| **Продакшн сборка** | `npm run build` (`vite build`) | Собрано за 4.39s без предупреждений | **PASS** | --- -## 4. Результаты верификации качества +## 4. Артефакты -| Проверка | Инструмент | Результат | Комментарий | -|---|---|---|---| -| **Линтер** | oxlint --deny-warnings | **0 warnings, 0 errors** (238 файлов) | Строгий режим без подавлений | -| **Типизация** | sc -b | **0 errors** (exit code 0) | Строгая проверка TypeScript проектных ссылок | -| **Unit-тесты** | itest run | **27 test files, 490 passed** (exit code 0) | Включая 11/11 тестов модуля Communications и дизайн-системы V3 | -| **Production Build** | -pm run build | **Успешно** (exit code 0) | Оптимизированные бандлы, чанки CSS и JS скомпилированы без ошибок | -| **Playwright Suite 13** | playwright test e2e/13-* | **32/32 passed (1.5m)** (exit code 0) | 8 вьюпортов x 4 сценария, включая интерактивный обмен сообщениями и навигацию | -| **Visual QA Capture** | Playwright artifacts | **24 скриншота + manifest.json** | Зафиксированы все 8 вьюпортов во всех 3 темах оформления V3 | - ---- - -## 5. Документация и артефакты - -- VISUAL_QA.md: полный протокол визуального аудита и матрица скриншотов. -- rtifacts/visual-qa/: скриншоты всех экранов и manifest.json. -- UI_LIBRARY_USAGE.md: актуализированное руководство по использованию библиотеки @krona/ui и дизайн-токенов V3. -- COMMUNICATIONS_HANDOFF.md: спецификация передачи модуля в эксплуатацию. +- **Скриншоты тем:** 24 файла (3 темы × 8 вьюпортов) в `artifacts/visual-qa/` +- **Скриншоты состояний:** 7 файлов (`mobile-list.png`, `mobile-chat.png`, `mobile-contact.png`, `tablet-chat.png`, `tablet-contact-drawer.png`, `desktop-without-krona-ai.png`, `desktop-with-krona-ai.png`) +- **Манифест скриншотов:** `artifacts/visual-qa/manifest.json` +- **Отчёт Visual QA:** `VISUAL_QA.md` (Статус: `READY FOR INDEPENDENT REVIEW`) diff --git a/VISUAL_QA.md b/VISUAL_QA.md index 3f694e1..c99c6bb 100644 --- a/VISUAL_QA.md +++ b/VISUAL_QA.md @@ -1,64 +1,92 @@ -# KRONA Visual QA Audit Report — Design System V3 Correction Pass +# KRONA Visual QA Audit Report — Mobile & Tablet Correction Pass -**Модуль:** Коммуникации (\/communications\) +**Модуль:** Коммуникации (`/communications`) **Дата проведения аудита:** 11 сентября 2026 г. -**Ветка:** \codex/design-system-communications-correction\ -**Статус проверки:** APPROVED (FAIL-CLOSED VERIFIED) +**Рабочая ветка:** `codex/design-system-communications-mobile-tablet-correction` +**Базовый коммит:** `49f5a34` +**Статус проверки:** `READY FOR INDEPENDENT REVIEW` --- -## 1. Соответствие эталонным макетам +## 1. Матрица устранения дефектов (Defect Resolution Matrix) + +| # | Дефект предыдущей реализации | Решение в текущей ветке (`codex/design-system-communications-mobile-tablet-correction`) | Статус проверки | +|---|-----------------------------|-----------------------------------------------------------------------------------------|:---------------:| +| 1 | **Мобильный интерфейс (< 600px):** отображение нескольких секций одновременно приводило к сжатию и нехватке места. | Реализован строгий **1-колоночный 3-уровневый drilldown**: **Уровень 1** (Список чатов) → **Уровень 2** (Активный чат) → **Уровень 3** (Полная карточка контакта). На экране виден ровно один уровень. Переходы осуществляются через клик по диалогу, тизер профиля и кнопки «Назад». | **PASS** | +| 2 | **Планшет (600–949px):** сплющенная горизонтальная карточка контакта под окном чата сжимала переписку и ломала верстку. | Горизонтальная карточка под чатом **полностью удалена**. Реализован 2-колоночный макет: колонка со списком диалогов (~280–300px) + колонка активного чата (все оставшееся пространство, min 320px). Карточка контакта открывается по кнопке в хедере чата через боковой слайд-овер `Drawer` с полным набором данных и CRM-действиями. | **PASS** | +| 3 | **Десктоп (≥ 950px):** узкие колонки при фиксированных брейкпоинтах окна. | Переход на **3 независимые колонки по ширине рабочей области**: Список диалогов (310–330px) + Активный чат (min 360px) + Карточка контакта (320–350px). При открытии боковой панели Krona AI карточки пропорционально масштабируются без горизонтальной прокрутки. | **PASS** | +| 4 | **Аватары:** использование плейсхолдеров и инициалов вместо визуальных портретов. | Созданы и интегрированы 11 векторных локальных SVG-портретов в `public/avatars/*.svg` с уникальной цветовой дифференциацией. Отображаются в списках, чате, тизере и карточке контакта. | **PASS** | +| 5 | **Соблюдение дизайн-системы V3:** использование сырых HEX-кодов в стилях. | Код полностью переведен на семантические CSS-переменные дизайн-системы V3 (`var(--krona-primary)`, `var(--krona-surface)`, `var(--krona-primary-soft)`, `var(--krona-border)`, `var(--krona-gold)`). Сырые HEX-значения устранены. | **PASS** | +| 6 | **Вкладки и кнопки на мобильных устройствах:** перенос строк и наплыв кнопок. | Вкладки размещены в единую строку с горизонтальным скроллом и градиентным индикатором края. На мобильных кнопка создания контакта/канала сворачивается в компактную touch-friendly иконку 40×40px. | **PASS** | +| 7 | **Наложение мобильной нижней навигации:** инпут чата и тизер профиля перекрывались нижним баром `BottomNav`. | Для страницы `/communications` на мобильных экранах добавлен нижний отступ `pb-16`, гарантирующий свободный доступ ко всем интерактивным элементам без блокировки `BottomNav`. | **PASS** | + +--- + +## 2. Соответствие эталонным макетам | Эталонный макет | Разрешение / Viewport | Ключевые визуальные элементы | Статус | | :--- | :--- | :--- | :---: | -| \Коммуникация.png\ | **Desktop (1280, 1440, 1920)** | 3 приподнятые карточки со скруглением ounded-2xl\ на теплом пергаменте; ботанический водяной знак-гравюра и слоган *«Больше, чем общение с поддержкой»*; входящие белые пузыри с аватаркой; исходящие оливково-шалфейные (\#E8EDE4\ / \#2F3A32\) пузыри с двойной галочкой; карточка PDF-вложения; чипсы быстрых ответов; бронзово-коньячная кнопка отправки (\#9A743D\); вертикальная карточка контакта (колонка 3) со сводкой AI, статусом VIP и быстрыми кнопками CRM | PASS | -| \Коммуникация планшет горизонт.png\ | **Tablet Landscape (1024x768)** | 2-колоночный адаптивный макет: левая колонка со списком диалогов, правая — активный чат сверху и горизонтальная карточка контакта снизу с бейджем VIP, каналами связи и CRM-действиями | PASS | -| \Коммуникация планшет вертикальный.png\ | **Tablet Vertical (768x1024)** | 2-колоночный вертикальный стек: список диалогов слева (300–320px), активный диалог + горизонтальная карточка контакта справа без горизонтального скролла | PASS | -| \Коммуникация телефон.png\ | **Mobile (360, 390, 430)** | 1-колоночный 3-уровневый drilldown: **Уровень 1** (список диалогов) -> **Уровень 2** (активный диалог с нижней тизер-плашкой профиля) -> **Уровень 3** (полная карточка контакта со сделками/задачами); фиксированный инпут над нижней навигацией (\pb-16\ / touch targets >= 44x44px) | PASS | +| `Коммуникация.png` | **Desktop (1280, 1440, 1920)** | 3 приподнятые карточки (`rounded-2xl`) на пергаментном фоне; водяной знак и слоган *«Больше, чем общение с поддержкой»*; входящие светлые пузыри с аватаром; исходящие пузыри с двойной галочкой; карточка PDF-вложения; чипсы быстрых ответов; бронзово-золотая кнопка отправки; вертикальная карточка контакта справа (колонка 3) с AI-сводкой, статусом VIP и CRM-действиями. | **PASS** | +| `Коммуникация планшет горизонт.png` | **Tablet Landscape (1024x768)** | 3-колоночный или просторный 2-колоночный макет с полноразмерным чатом без горизонтального сжатия. | **PASS** | +| `Коммуникация планшет вертикальный.png` | **Tablet Vertical (768x1024)** | 2-колоночный макет: список диалогов слева (~280-300px), активный диалог справа. Карточка контакта открывается через правый `Drawer`. | **PASS** | +| `Коммуникация телефон.png` | **Mobile (360, 390, 430)** | 1-колоночный drilldown: **Уровень 1** (список диалогов) → **Уровень 2** (активный диалог с нижней плашкой контакта) → **Уровень 3** (карточка контакта); фиксированный инпут над навигацией (`pb-16` / touch targets ≥ 44×44px). | **PASS** | --- -## 2. Матрица скриншотов (8 Viewports x 3 Themes = 24 Screenshots) +## 3. Матрица скриншотов (8 Viewports × 3 Themes = 24 Screenshots) -Все скриншоты сохранены в директории \rtifacts/visual-qa/\ с метаданными в \rtifacts/visual-qa/manifest.json\: +Все скриншоты сохранены в директории `artifacts/visual-qa/` с манифестом `artifacts/visual-qa/manifest.json`: -### Parchment Light (Светлый пергамент — основная тема V3) -1. \rtifacts/visual-qa/parchment-light-mobile-360.png\ (360x800) -2. \rtifacts/visual-qa/parchment-light-mobile-390.png\ (390x844) -3. \rtifacts/visual-qa/parchment-light-mobile-430.png\ (430x932) -4. \rtifacts/visual-qa/parchment-light-tablet-768.png\ (768x1024) -5. \rtifacts/visual-qa/parchment-light-tablet-1024.png\ (1024x768) -6. \rtifacts/visual-qa/parchment-light-desktop-1280.png\ (1280x800) -7. \rtifacts/visual-qa/parchment-light-desktop-1440.png\ (1440x900) -8. \rtifacts/visual-qa/parchment-light-desktop-1920.png\ (1920x1080) +### 3.1. Parchment Light (Светлый пергамент — основная тема V3) +1. [parchment-light-mobile-360.png](artifacts/visual-qa/parchment-light-mobile-360.png) (360×800) +2. [parchment-light-mobile-390.png](artifacts/visual-qa/parchment-light-mobile-390.png) (390×844) +3. [parchment-light-mobile-430.png](artifacts/visual-qa/parchment-light-mobile-430.png) (430×932) +4. [parchment-light-tablet-768.png](artifacts/visual-qa/parchment-light-tablet-768.png) (768×1024) +5. [parchment-light-tablet-1024.png](artifacts/visual-qa/parchment-light-tablet-1024.png) (1024×768) +6. [parchment-light-desktop-1280.png](artifacts/visual-qa/parchment-light-desktop-1280.png) (1280×800) +7. [parchment-light-desktop-1440.png](artifacts/visual-qa/parchment-light-desktop-1440.png) (1440×900) +8. [parchment-light-desktop-1920.png](artifacts/visual-qa/parchment-light-desktop-1920.png) (1920×1080) -### Noble Green (Благородный зеленый / мягкая тема V3) -1. \rtifacts/visual-qa/noble-green-mobile-360.png\ (360x800) -2. \rtifacts/visual-qa/noble-green-mobile-390.png\ (390x844) -3. \rtifacts/visual-qa/noble-green-mobile-430.png\ (430x932) -4. \rtifacts/visual-qa/noble-green-tablet-768.png\ (768x1024) -5. \rtifacts/visual-qa/noble-green-tablet-1024.png\ (1024x768) -6. \rtifacts/visual-qa/noble-green-desktop-1280.png\ (1280x800) -7. \rtifacts/visual-qa/noble-green-desktop-1440.png\ (1440x900) -8. \rtifacts/visual-qa/noble-green-desktop-1920.png\ (1920x1080) +### 3.2. Noble Green (Благородный зелёный / фирменная тема V3) +1. [noble-green-mobile-360.png](artifacts/visual-qa/noble-green-mobile-360.png) (360×800) +2. [noble-green-mobile-390.png](artifacts/visual-qa/noble-green-mobile-390.png) (390×844) +3. [noble-green-mobile-430.png](artifacts/visual-qa/noble-green-mobile-430.png) (430×932) +4. [noble-green-tablet-768.png](artifacts/visual-qa/noble-green-tablet-768.png) (768×1024) +5. [noble-green-tablet-1024.png](artifacts/visual-qa/noble-green-tablet-1024.png) (1024×768) +6. [noble-green-desktop-1280.png](artifacts/visual-qa/noble-green-desktop-1280.png) (1280×800) +7. [noble-green-desktop-1440.png](artifacts/visual-qa/noble-green-desktop-1440.png) (1440×900) +8. [noble-green-desktop-1920.png](artifacts/visual-qa/noble-green-desktop-1920.png) (1920×1080) -### Graphite Forest Dark (Тёмная графитово-лесная тема V3) -1. \rtifacts/visual-qa/graphite-forest-dark-mobile-360.png\ (360x800) -2. \rtifacts/visual-qa/graphite-forest-dark-mobile-390.png\ (390x844) -3. \rtifacts/visual-qa/graphite-forest-dark-mobile-430.png\ (430x932) -4. \rtifacts/visual-qa/graphite-forest-dark-tablet-768.png\ (768x1024) -5. \rtifacts/visual-qa/graphite-forest-dark-tablet-1024.png\ (1024x768) -6. \rtifacts/visual-qa/graphite-forest-dark-desktop-1280.png\ (1280x800) -7. \rtifacts/visual-qa/graphite-forest-dark-desktop-1440.png\ (1440x900) -8. \rtifacts/visual-qa/graphite-forest-dark-desktop-1920.png\ (1920x1080) +### 3.3. Graphite Forest Dark (Тёмная графитово-лесная тема V3) +1. [graphite-forest-dark-mobile-360.png](artifacts/visual-qa/graphite-forest-dark-mobile-360.png) (360×800) +2. [graphite-forest-dark-mobile-390.png](artifacts/visual-qa/graphite-forest-dark-mobile-390.png) (390×844) +3. [graphite-forest-dark-mobile-430.png](artifacts/visual-qa/graphite-forest-dark-mobile-430.png) (430×932) +4. [graphite-forest-dark-tablet-768.png](artifacts/visual-qa/graphite-forest-dark-tablet-768.png) (768×1024) +5. [graphite-forest-dark-tablet-1024.png](artifacts/visual-qa/graphite-forest-dark-tablet-1024.png) (1024×768) +6. [graphite-forest-dark-desktop-1280.png](artifacts/visual-qa/graphite-forest-dark-desktop-1280.png) (1280×800) +7. [graphite-forest-dark-desktop-1440.png](artifacts/visual-qa/graphite-forest-dark-desktop-1440.png) (1440×900) +8. [graphite-forest-dark-desktop-1920.png](artifacts/visual-qa/graphite-forest-dark-desktop-1920.png) (1920×1080) --- -## 3. Проверенные интерактивные сценарии +## 4. Скриншоты состояний (State Captures — 7 скриншотов) -1. **Отправка сообщений:** ввод текста в инпут, отправка кнопкой или по Enter, появление исходящего пузыря оливкового цвета со статусом прочтения (двойная галочка). -2. **Шаблоны и быстрые ответы:** клик по чипсу («Да, подготовим» и др.) мгновенно вставляет и отправляет сообщение. -3. **AI-ассистент:** кнопка с иконкой Sparkles генерирует контекстный черновик сообщения. -4. **CRM-интеграция:** создание сделки и задачи прямо из активного диалога и карточки контакта с отображением тоста. -5. **Вкладки раздела:** переход по табам *Мессенджеры*, *Email*, *Каналы*, *Контакты*, *Настройки* без перезагрузки и с синхронизацией URL query parameter \?tab=\. -6. **Отсутствие горизонтальной прокрутки:** проверено на всех разрешениях от 360px до 1920px (\scrollWidth <= clientWidth\). +1. **Мобильный уровень 1 (Список диалогов):** [mobile-list.png](artifacts/visual-qa/mobile-list.png) — отображает только список чатов, строку поиска, быстрые фильтры и пилюли каналов. +2. **Мобильный уровень 2 (Активный диалог):** [mobile-chat.png](artifacts/visual-qa/mobile-chat.png) — окно переписки с кнопкой возврата назад, сообщениями, полем ввода и нижней плашкой-тизером контакта. +3. **Мобильный уровень 3 (Карточка контакта):** [mobile-contact.png](artifacts/visual-qa/mobile-contact.png) — развернутая карточка контакта со сделками CRM, задачами, AI-анализом и кнопкой «Назад к диалогу». +4. **Планшет (Активный диалог):** [tablet-chat.png](artifacts/visual-qa/tablet-chat.png) — 2-колоночный вид: список чатов + полноразмерный диалог. +5. **Планшет (Боковой Drawer контакта):** [tablet-contact-drawer.png](artifacts/visual-qa/tablet-contact-drawer.png) — открытый слайд-овер `Drawer` с профилем клиента поверх чата. +6. **Десктоп (3 колонки):** [desktop-without-krona-ai.png](artifacts/visual-qa/desktop-without-krona-ai.png) — 3 полноценные карточки без боковой панели AI. +7. **Десктоп с открытым Krona AI:** [desktop-with-krona-ai.png](artifacts/visual-qa/desktop-with-krona-ai.png) — сохранение целостности 3 колонок при выдвинутой панели оркестратора Krona AI. + +--- + +## 5. Результаты автоматизированных детерминированных проверок + +| Тип проверки | Команда | Результат | Подробности | +|---|---|:---:|---| +| **E2E Visual & Responsive** | `npx playwright test e2e/13-communications-visual-and-responsive.spec.ts` | **PASS (32/32)** | Все 8 проектов (360, 390, 430, 768, 1024, 1280, 1440, 1920) прошли без ошибок (1.7m). | +| **Unit & Integration** | `npm test` | **PASS (490/490)** | 27 тестовых файлов, включая модуль коммуникаций, без сбоев (45.8s). | +| **Linter** | `npm run lint` | **PASS (0/0)** | 0 warnings, 0 errors на 238 файлах (`oxlint --deny-warnings`). | +| **Type Check** | `npx tsc -b` | **PASS** | Exit code 0, строгая типизация TypeScript. | +| **Production Build** | `npm run build` | **PASS** | Сборка Vite завершена успешно за 4.39s. | diff --git a/artifacts/visual-qa/desktop-with-krona-ai.png b/artifacts/visual-qa/desktop-with-krona-ai.png new file mode 100644 index 0000000..c3488bc Binary files /dev/null and b/artifacts/visual-qa/desktop-with-krona-ai.png differ diff --git a/artifacts/visual-qa/desktop-without-krona-ai.png b/artifacts/visual-qa/desktop-without-krona-ai.png new file mode 100644 index 0000000..e547f5e Binary files /dev/null and b/artifacts/visual-qa/desktop-without-krona-ai.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-desktop-1280.png b/artifacts/visual-qa/graphite-forest-dark-desktop-1280.png index 9a8c85b..a6acb64 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-desktop-1280.png and b/artifacts/visual-qa/graphite-forest-dark-desktop-1280.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-desktop-1440.png b/artifacts/visual-qa/graphite-forest-dark-desktop-1440.png index 981eb82..5533e10 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-desktop-1440.png and b/artifacts/visual-qa/graphite-forest-dark-desktop-1440.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-desktop-1920.png b/artifacts/visual-qa/graphite-forest-dark-desktop-1920.png index 0c32f44..54de580 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-desktop-1920.png and b/artifacts/visual-qa/graphite-forest-dark-desktop-1920.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-mobile-360.png b/artifacts/visual-qa/graphite-forest-dark-mobile-360.png index 2dd8fff..3702b80 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-mobile-360.png and b/artifacts/visual-qa/graphite-forest-dark-mobile-360.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-mobile-390.png b/artifacts/visual-qa/graphite-forest-dark-mobile-390.png index f22d9bd..296d4b6 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-mobile-390.png and b/artifacts/visual-qa/graphite-forest-dark-mobile-390.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-mobile-430.png b/artifacts/visual-qa/graphite-forest-dark-mobile-430.png index 903ce20..4761e7e 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-mobile-430.png and b/artifacts/visual-qa/graphite-forest-dark-mobile-430.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-tablet-1024.png b/artifacts/visual-qa/graphite-forest-dark-tablet-1024.png index 3c9dca0..f017e54 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-tablet-1024.png and b/artifacts/visual-qa/graphite-forest-dark-tablet-1024.png differ diff --git a/artifacts/visual-qa/graphite-forest-dark-tablet-768.png b/artifacts/visual-qa/graphite-forest-dark-tablet-768.png index eaadc6d..1936151 100644 Binary files a/artifacts/visual-qa/graphite-forest-dark-tablet-768.png and b/artifacts/visual-qa/graphite-forest-dark-tablet-768.png differ diff --git a/artifacts/visual-qa/manifest.json b/artifacts/visual-qa/manifest.json index 8f99a7b..51c3904 100644 --- a/artifacts/visual-qa/manifest.json +++ b/artifacts/visual-qa/manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-09-11T10:43:18.547Z", + "generatedAt": "2026-09-11T12:25:16.253Z", "module": "communications", "version": "v3.0", "referenceMockups": [ @@ -64,149 +64,191 @@ } ], "screenshots": [ + { + "filename": "desktop-with-krona-ai.png", + "theme": "state-capture", + "viewport": "desktop-with-krona-ai", + "capturedAt": "2026-09-11T12:22:13.311Z" + }, + { + "filename": "desktop-without-krona-ai.png", + "theme": "state-capture", + "viewport": "desktop-without-krona-ai", + "capturedAt": "2026-09-11T12:22:12.416Z" + }, { "filename": "graphite-forest-dark-desktop-1280.png", "theme": "graphite-forest-dark", "viewport": "desktop-1280", - "capturedAt": "2026-09-11T10:42:51.480Z" + "capturedAt": "2026-09-11T12:21:52.645Z" }, { "filename": "graphite-forest-dark-desktop-1440.png", "theme": "graphite-forest-dark", "viewport": "desktop-1440", - "capturedAt": "2026-09-11T10:43:01.405Z" + "capturedAt": "2026-09-11T12:22:07.332Z" }, { "filename": "graphite-forest-dark-desktop-1920.png", "theme": "graphite-forest-dark", "viewport": "desktop-1920", - "capturedAt": "2026-09-11T10:43:12.260Z" + "capturedAt": "2026-09-11T12:22:22.152Z" }, { "filename": "graphite-forest-dark-mobile-360.png", "theme": "graphite-forest-dark", "viewport": "mobile-360", - "capturedAt": "2026-09-11T10:41:51.625Z" + "capturedAt": "2026-09-11T12:20:51.014Z" }, { "filename": "graphite-forest-dark-mobile-390.png", "theme": "graphite-forest-dark", "viewport": "mobile-390", - "capturedAt": "2026-09-11T10:42:04.608Z" + "capturedAt": "2026-09-11T12:21:02.940Z" }, { "filename": "graphite-forest-dark-mobile-430.png", "theme": "graphite-forest-dark", "viewport": "mobile-430", - "capturedAt": "2026-09-11T10:42:15.717Z" + "capturedAt": "2026-09-11T12:21:15.107Z" }, { "filename": "graphite-forest-dark-tablet-1024.png", "theme": "graphite-forest-dark", "viewport": "tablet-1024", - "capturedAt": "2026-09-11T10:42:41.564Z" + "capturedAt": "2026-09-11T12:21:40.394Z" }, { "filename": "graphite-forest-dark-tablet-768.png", "theme": "graphite-forest-dark", "viewport": "tablet-768", - "capturedAt": "2026-09-11T10:42:27.167Z" + "capturedAt": "2026-09-11T12:21:27.469Z" + }, + { + "filename": "mobile-chat.png", + "theme": "state-capture", + "viewport": "mobile-chat", + "capturedAt": "2026-09-11T12:25:15.761Z" + }, + { + "filename": "mobile-contact.png", + "theme": "state-capture", + "viewport": "mobile-contact", + "capturedAt": "2026-09-11T12:25:15.982Z" + }, + { + "filename": "mobile-list.png", + "theme": "state-capture", + "viewport": "mobile-list", + "capturedAt": "2026-09-11T12:25:15.531Z" }, { "filename": "noble-green-desktop-1280.png", "theme": "noble-green", "viewport": "desktop-1280", - "capturedAt": "2026-09-11T10:42:51.030Z" + "capturedAt": "2026-09-11T12:21:52.093Z" }, { "filename": "noble-green-desktop-1440.png", "theme": "noble-green", "viewport": "desktop-1440", - "capturedAt": "2026-09-11T10:43:00.944Z" + "capturedAt": "2026-09-11T12:22:06.701Z" }, { "filename": "noble-green-desktop-1920.png", "theme": "noble-green", "viewport": "desktop-1920", - "capturedAt": "2026-09-11T10:43:11.630Z" + "capturedAt": "2026-09-11T12:22:21.509Z" }, { "filename": "noble-green-mobile-360.png", "theme": "noble-green", "viewport": "mobile-360", - "capturedAt": "2026-09-11T10:41:51.173Z" + "capturedAt": "2026-09-11T12:20:50.611Z" }, { "filename": "noble-green-mobile-390.png", "theme": "noble-green", "viewport": "mobile-390", - "capturedAt": "2026-09-11T10:42:04.141Z" + "capturedAt": "2026-09-11T12:21:02.534Z" }, { "filename": "noble-green-mobile-430.png", "theme": "noble-green", "viewport": "mobile-430", - "capturedAt": "2026-09-11T10:42:15.277Z" + "capturedAt": "2026-09-11T12:21:14.716Z" }, { "filename": "noble-green-tablet-1024.png", "theme": "noble-green", "viewport": "tablet-1024", - "capturedAt": "2026-09-11T10:42:41.113Z" + "capturedAt": "2026-09-11T12:21:39.851Z" }, { "filename": "noble-green-tablet-768.png", "theme": "noble-green", "viewport": "tablet-768", - "capturedAt": "2026-09-11T10:42:26.644Z" + "capturedAt": "2026-09-11T12:21:26.941Z" }, { "filename": "parchment-light-desktop-1280.png", "theme": "parchment-light", "viewport": "desktop-1280", - "capturedAt": "2026-09-11T10:42:50.501Z" + "capturedAt": "2026-09-11T12:21:51.543Z" }, { "filename": "parchment-light-desktop-1440.png", "theme": "parchment-light", "viewport": "desktop-1440", - "capturedAt": "2026-09-11T10:43:00.457Z" + "capturedAt": "2026-09-11T12:22:06.120Z" }, { "filename": "parchment-light-desktop-1920.png", "theme": "parchment-light", "viewport": "desktop-1920", - "capturedAt": "2026-09-11T10:43:10.935Z" + "capturedAt": "2026-09-11T12:22:20.870Z" }, { "filename": "parchment-light-mobile-360.png", "theme": "parchment-light", "viewport": "mobile-360", - "capturedAt": "2026-09-11T10:41:50.743Z" + "capturedAt": "2026-09-11T12:20:50.223Z" }, { "filename": "parchment-light-mobile-390.png", "theme": "parchment-light", "viewport": "mobile-390", - "capturedAt": "2026-09-11T10:42:03.687Z" + "capturedAt": "2026-09-11T12:21:02.124Z" }, { "filename": "parchment-light-mobile-430.png", "theme": "parchment-light", "viewport": "mobile-430", - "capturedAt": "2026-09-11T10:42:14.847Z" + "capturedAt": "2026-09-11T12:21:14.326Z" }, { "filename": "parchment-light-tablet-1024.png", "theme": "parchment-light", "viewport": "tablet-1024", - "capturedAt": "2026-09-11T10:42:40.633Z" + "capturedAt": "2026-09-11T12:21:39.371Z" }, { "filename": "parchment-light-tablet-768.png", "theme": "parchment-light", "viewport": "tablet-768", - "capturedAt": "2026-09-11T10:42:26.175Z" + "capturedAt": "2026-09-11T12:21:26.400Z" + }, + { + "filename": "tablet-chat.png", + "theme": "state-capture", + "viewport": "tablet-chat", + "capturedAt": "2026-09-11T12:21:31.840Z" + }, + { + "filename": "tablet-contact-drawer.png", + "theme": "state-capture", + "viewport": "tablet-contact-drawer", + "capturedAt": "2026-09-11T12:21:32.462Z" } ] } \ No newline at end of file diff --git a/artifacts/visual-qa/mobile-chat.png b/artifacts/visual-qa/mobile-chat.png new file mode 100644 index 0000000..ef3aacb Binary files /dev/null and b/artifacts/visual-qa/mobile-chat.png differ diff --git a/artifacts/visual-qa/mobile-contact.png b/artifacts/visual-qa/mobile-contact.png new file mode 100644 index 0000000..9839708 Binary files /dev/null and b/artifacts/visual-qa/mobile-contact.png differ diff --git a/artifacts/visual-qa/mobile-list.png b/artifacts/visual-qa/mobile-list.png new file mode 100644 index 0000000..31bcaad Binary files /dev/null and b/artifacts/visual-qa/mobile-list.png differ diff --git a/artifacts/visual-qa/noble-green-desktop-1280.png b/artifacts/visual-qa/noble-green-desktop-1280.png index 93793c6..a050904 100644 Binary files a/artifacts/visual-qa/noble-green-desktop-1280.png and b/artifacts/visual-qa/noble-green-desktop-1280.png differ diff --git a/artifacts/visual-qa/noble-green-desktop-1440.png b/artifacts/visual-qa/noble-green-desktop-1440.png index 2bbbafe..5ce5049 100644 Binary files a/artifacts/visual-qa/noble-green-desktop-1440.png and b/artifacts/visual-qa/noble-green-desktop-1440.png differ diff --git a/artifacts/visual-qa/noble-green-desktop-1920.png b/artifacts/visual-qa/noble-green-desktop-1920.png index f40a2cf..c9e0e2e 100644 Binary files a/artifacts/visual-qa/noble-green-desktop-1920.png and b/artifacts/visual-qa/noble-green-desktop-1920.png differ diff --git a/artifacts/visual-qa/noble-green-mobile-360.png b/artifacts/visual-qa/noble-green-mobile-360.png index ee4ca41..2bd0c31 100644 Binary files a/artifacts/visual-qa/noble-green-mobile-360.png and b/artifacts/visual-qa/noble-green-mobile-360.png differ diff --git a/artifacts/visual-qa/noble-green-mobile-390.png b/artifacts/visual-qa/noble-green-mobile-390.png index d2cdac2..51ec7ff 100644 Binary files a/artifacts/visual-qa/noble-green-mobile-390.png and b/artifacts/visual-qa/noble-green-mobile-390.png differ diff --git a/artifacts/visual-qa/noble-green-mobile-430.png b/artifacts/visual-qa/noble-green-mobile-430.png index 26c93d0..8557b89 100644 Binary files a/artifacts/visual-qa/noble-green-mobile-430.png and b/artifacts/visual-qa/noble-green-mobile-430.png differ diff --git a/artifacts/visual-qa/noble-green-tablet-1024.png b/artifacts/visual-qa/noble-green-tablet-1024.png index 8e5c5ac..e403f88 100644 Binary files a/artifacts/visual-qa/noble-green-tablet-1024.png and b/artifacts/visual-qa/noble-green-tablet-1024.png differ diff --git a/artifacts/visual-qa/noble-green-tablet-768.png b/artifacts/visual-qa/noble-green-tablet-768.png index ef3418a..c8d87e7 100644 Binary files a/artifacts/visual-qa/noble-green-tablet-768.png and b/artifacts/visual-qa/noble-green-tablet-768.png differ diff --git a/artifacts/visual-qa/parchment-light-desktop-1280.png b/artifacts/visual-qa/parchment-light-desktop-1280.png index f915e67..ee4c0ca 100644 Binary files a/artifacts/visual-qa/parchment-light-desktop-1280.png and b/artifacts/visual-qa/parchment-light-desktop-1280.png differ diff --git a/artifacts/visual-qa/parchment-light-desktop-1440.png b/artifacts/visual-qa/parchment-light-desktop-1440.png index 1f39dc8..57c61ad 100644 Binary files a/artifacts/visual-qa/parchment-light-desktop-1440.png and b/artifacts/visual-qa/parchment-light-desktop-1440.png differ diff --git a/artifacts/visual-qa/parchment-light-desktop-1920.png b/artifacts/visual-qa/parchment-light-desktop-1920.png index 14cc8da..de8ebc5 100644 Binary files a/artifacts/visual-qa/parchment-light-desktop-1920.png and b/artifacts/visual-qa/parchment-light-desktop-1920.png differ diff --git a/artifacts/visual-qa/parchment-light-mobile-360.png b/artifacts/visual-qa/parchment-light-mobile-360.png index 3f574f7..13620b6 100644 Binary files a/artifacts/visual-qa/parchment-light-mobile-360.png and b/artifacts/visual-qa/parchment-light-mobile-360.png differ diff --git a/artifacts/visual-qa/parchment-light-mobile-390.png b/artifacts/visual-qa/parchment-light-mobile-390.png index 390e0b1..4c9dabe 100644 Binary files a/artifacts/visual-qa/parchment-light-mobile-390.png and b/artifacts/visual-qa/parchment-light-mobile-390.png differ diff --git a/artifacts/visual-qa/parchment-light-mobile-430.png b/artifacts/visual-qa/parchment-light-mobile-430.png index 25caa8f..c581760 100644 Binary files a/artifacts/visual-qa/parchment-light-mobile-430.png and b/artifacts/visual-qa/parchment-light-mobile-430.png differ diff --git a/artifacts/visual-qa/parchment-light-tablet-1024.png b/artifacts/visual-qa/parchment-light-tablet-1024.png index 077f59a..bf0419e 100644 Binary files a/artifacts/visual-qa/parchment-light-tablet-1024.png and b/artifacts/visual-qa/parchment-light-tablet-1024.png differ diff --git a/artifacts/visual-qa/parchment-light-tablet-768.png b/artifacts/visual-qa/parchment-light-tablet-768.png index 7bb56d1..8182c24 100644 Binary files a/artifacts/visual-qa/parchment-light-tablet-768.png and b/artifacts/visual-qa/parchment-light-tablet-768.png differ diff --git a/artifacts/visual-qa/tablet-chat.png b/artifacts/visual-qa/tablet-chat.png new file mode 100644 index 0000000..b284081 Binary files /dev/null and b/artifacts/visual-qa/tablet-chat.png differ diff --git a/artifacts/visual-qa/tablet-contact-drawer.png b/artifacts/visual-qa/tablet-contact-drawer.png new file mode 100644 index 0000000..71db6f5 Binary files /dev/null and b/artifacts/visual-qa/tablet-contact-drawer.png differ diff --git a/e2e/13-communications-visual-and-responsive.spec.ts b/e2e/13-communications-visual-and-responsive.spec.ts index dad5801..211dc14 100644 --- a/e2e/13-communications-visual-and-responsive.spec.ts +++ b/e2e/13-communications-visual-and-responsive.spec.ts @@ -6,6 +6,7 @@ const THEMES = ['parchment-light', 'noble-green', 'graphite-forest-dark'] as con test.describe('Scenario 13: Communications Visual & Responsive Design System V3 Suite', () => { test.beforeEach(async ({ page }) => { + test.setTimeout(60000); // Navigate directly to Communications module await page.goto('/communications', { waitUntil: 'domcontentloaded' }); await expect(page.locator('h1', { hasText: 'Коммуникации' })).toBeVisible({ timeout: 15000 }); @@ -43,11 +44,22 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 }); test('2. Header, Slogan Watermark, and Channel Switcher validation', async ({ page }) => { + const vp = page.viewportSize(); + const isMobile = (vp?.width ?? 1200) < 640; + const isDesktop = (vp?.width ?? 1200) >= 1024; + // Check main header await expect(page.locator('h1', { hasText: 'Коммуникации' })).toBeVisible(); - await expect( - page.locator('text=Все сообщения, почта и каналы компании в одном пространстве') - ).toBeVisible(); + + if (!isMobile) { + await expect( + page.locator('text=Все сообщения, почта и каналы компании в одном пространстве') + ).toBeVisible(); + } + + if (isDesktop) { + await expect(page.locator('text=Больше, чем общение с поддержкой')).toBeVisible(); + } // Check all 5 tabs await expect(page.getByRole('tab', { name: /мессенджеры/i })).toBeVisible(); @@ -63,19 +75,49 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 await expect(page.getByRole('button', { name: /vk.*6/i })).toBeVisible(); await expect(page.getByRole('button', { name: /max.*4/i })).toBeVisible(); await expect(page.getByRole('button', { name: /email.*10/i })).toBeVisible(); - await expect(page.getByRole('button', { name: /подключить/i })).toBeVisible(); - }); - - test('3. Responsive layout behavior and drill-down flow', async ({ page, viewport }) => { - const isMobile = viewport && viewport.width < 768; - const isTablet = viewport && viewport.width >= 768 && viewport.width < 1280; - const isDesktop = viewport && viewport.width >= 1280; if (isMobile) { - // Mobile drilldown check: Level 1 (List) -> Level 2 (Chat) -> Level 3 (Contact) + await expect(page.getByRole('button', { name: /подключить канал/i })).toBeVisible(); + } else { + await expect(page.getByRole('button', { name: /подключить/i })).toBeVisible(); + } + }); + + test('3. Responsive layout behavior, geometric assertions and state screenshots', async ({ + page, + }, testInfo) => { + const projectName = testInfo.project.name; + const artifactsDir = path.resolve(process.cwd(), 'artifacts/visual-qa'); + if (!fs.existsSync(artifactsDir)) { + fs.mkdirSync(artifactsDir, { recursive: true }); + } + + const layoutMode = await page.locator('[data-layout-mode]').getAttribute('data-layout-mode'); + const isMobile = layoutMode === 'compact'; + const isTablet = layoutMode === 'medium'; + const isDesktop = layoutMode === 'wide'; + + if (isMobile) { + // 1. Mobile drilldown check: Level 1 (List) -> Level 2 (Chat) -> Level 3 (Contact) const chatList = page.locator('section[aria-label="Список диалогов"]'); await expect(chatList).toBeVisible(); + // State Screenshot 1: Mobile List + if (projectName === 'mobile-390' || projectName === 'mobile-360') { + await page.screenshot({ + path: path.join(artifactsDir, 'mobile-list.png'), + fullPage: false, + }); + } + + // Check single-row tabs with scroll + const tabsList = page.locator('div[role="tablist"]'); + const tabsBox = await tabsList.boundingBox(); + expect(tabsBox).not.toBeNull(); + if (tabsBox) { + expect(tabsBox.height).toBeLessThanOrEqual(56); + } + // Click first conversation to drill down to Level 2 (Chat) const firstChat = chatList.locator('button').filter({ hasText: 'Иван Петров' }).first(); await firstChat.click(); @@ -83,6 +125,16 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 // Active chat is now visible const activeChat = page.locator('section[aria-label="Активный диалог"]'); await expect(activeChat).toBeVisible(); + // Chat list is NOT visible in compact mode + await expect(chatList).toBeHidden(); + + // State Screenshot 2: Mobile Chat + if (projectName === 'mobile-390' || projectName === 'mobile-360') { + await page.screenshot({ + path: path.join(artifactsDir, 'mobile-chat.png'), + fullPage: false, + }); + } // Check Back to list button const backToListBtn = activeChat.locator('button[aria-label="Назад к списку чатов"]'); @@ -92,10 +144,28 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 const profileTeaserBtn = activeChat.locator('button[aria-label="Открыть карточку контакта"]'); await expect(profileTeaserBtn).toBeVisible(); + // Check message composer is positioned within viewport + const composer = activeChat.locator('form'); + await expect(composer).toBeVisible(); + const composerBox = await composer.boundingBox(); + const vp = page.viewportSize(); + if (composerBox && vp) { + expect(composerBox.y + composerBox.height).toBeLessThanOrEqual(vp.height + 10); + } + // Click profile teaser to drill down to Level 3 (Contact) await profileTeaserBtn.click(); const mobileContactCard = page.locator('section[aria-label="Карточка контакта"]'); await expect(mobileContactCard).toBeVisible(); + await expect(activeChat).toBeHidden(); + + // State Screenshot 3: Mobile Contact + if (projectName === 'mobile-390' || projectName === 'mobile-360') { + await page.screenshot({ + path: path.join(artifactsDir, 'mobile-contact.png'), + fullPage: false, + }); + } // Check Back to chat button in Level 3 const backToChatBtn = mobileContactCard.locator('button[aria-label="Назад к диалогу"]'); @@ -115,18 +185,62 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 }); expect(hasHorizontalScroll).toBe(false); } else if (isTablet) { - // Tablet (768 vertical and 1024 landscape): 2 columns (Chat list on left, Chat + Horizontal Contact Card on right) + // 2. Tablet: 2 columns (Chat list + Active Chat). Contact card opens via slide-over Drawer. const chatList = page.locator('section[aria-label="Список диалогов"]'); await expect(chatList).toBeVisible(); const activeChat = page.locator('section[aria-label="Активный диалог"]'); await expect(activeChat).toBeVisible(); - const horizontalContactCard = page.locator('aside[aria-label="Карточка контакта планшет"]'); - await expect(horizontalContactCard).toBeVisible(); - await expect(horizontalContactCard.locator('text=VIP')).toBeVisible(); + // Verify Chat width >= 320px + const chatBox = await activeChat.boundingBox(); + expect(chatBox).not.toBeNull(); + if (chatBox) { + expect(chatBox.width).toBeGreaterThanOrEqual(320); + } + + // Verify no squished under-chat horizontal card exists + const squishedCard = page.locator('aside[aria-label="Карточка контакта планшет"]'); + await expect(squishedCard).toHaveCount(0); + + // State Screenshot 4: Tablet Chat + if (projectName === 'tablet-768' || projectName === 'tablet-1024') { + await page.screenshot({ + path: path.join(artifactsDir, 'tablet-chat.png'), + fullPage: false, + }); + } + + // Open slide-over Drawer via contact button in chat header + const openContactBtn = activeChat.locator('button[aria-label="Открыть контакт"]'); + await expect(openContactBtn).toBeVisible(); + await openContactBtn.click(); + + // Drawer is open + const drawer = page.locator('div[role="dialog"]'); + await expect(drawer).toBeVisible(); + await expect(drawer.locator('text=Карточка контакта')).toBeVisible(); + await expect(drawer.locator('text=Иван Петров').first()).toBeVisible(); + await expect(drawer.locator('text=VIP')).toBeVisible(); + + // State Screenshot 5: Tablet Contact Drawer + if (projectName === 'tablet-768' || projectName === 'tablet-1024') { + await page.screenshot({ + path: path.join(artifactsDir, 'tablet-contact-drawer.png'), + fullPage: false, + }); + } + + // Close Drawer + const closeDrawerBtn = drawer.locator('button[aria-label="Закрыть"]').first(); + if (await closeDrawerBtn.isVisible()) { + await closeDrawerBtn.click(); + } else { + await page.keyboard.press('Escape'); + } + await expect(drawer).toBeHidden(); } else if (isDesktop) { - // Desktop: 3 separate elevated columns side-by-side + // 3. Desktop: 3 columns (Chat list + Active Chat + Aside Contact card) const chatList = page.locator('section[aria-label="Список диалогов"]'); await expect(chatList).toBeVisible(); @@ -137,12 +251,48 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 await expect(verticalContactCard).toBeVisible(); await expect(verticalContactCard.locator('text=Иван Петров')).toBeVisible(); await expect(verticalContactCard.locator('text=VIP')).toBeVisible(); + + // Test tab inside Contact Card + await verticalContactCard.getByRole('button', { name: 'AI-сводка' }).click(); await expect(verticalContactCard.locator('text=✦ AI-сводка')).toBeVisible(); + + // Verify scrollY is 0 + const scrollY = await page.evaluate(() => window.scrollY); + expect(scrollY).toBe(0); + + // State Screenshot 6: Desktop without Krona AI + if (projectName === 'desktop-1440') { + await page.screenshot({ + path: path.join(artifactsDir, 'desktop-without-krona-ai.png'), + fullPage: false, + }); + } + + // Open Krona AI drawer + const aiBtn = page.locator('button[title="Krona AI — Бизнес-оркестратор"]'); + if (await aiBtn.isVisible()) { + await aiBtn.click(); + const aiDrawer = page.locator('text=✦ Krona AI — Бизнес-оркестратор'); + await expect(aiDrawer).toBeVisible(); + + // State Screenshot 7: Desktop with Krona AI + if (projectName === 'desktop-1440') { + await page.screenshot({ + path: path.join(artifactsDir, 'desktop-with-krona-ai.png'), + fullPage: false, + }); + } + + // Close AI drawer + await page.keyboard.press('Escape'); + await page.waitForTimeout(300); + } } }); - test('4. Interactive messaging, quick replies, and tab navigation', async ({ page, viewport }) => { - const isMobile = viewport && viewport.width < 768; + test('4. Interactive messaging, quick replies, and tab navigation', async ({ page }) => { + const layoutMode = await page.locator('[data-layout-mode]').getAttribute('data-layout-mode'); + const isMobile = layoutMode === 'compact'; if (isMobile) { // Select chat first on mobile @@ -233,8 +383,12 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3 ? 'graphite-forest-dark' : file.startsWith('noble-green') ? 'noble-green' - : 'parchment-light', - viewport: file.replace(/^(graphite-forest-dark|noble-green|parchment-light)-/, '').replace('.png', ''), + : file.startsWith('parchment-light') + ? 'parchment-light' + : 'state-capture', + viewport: file + .replace(/^(graphite-forest-dark|noble-green|parchment-light)-/, '') + .replace('.png', ''), capturedAt: fs.statSync(path.join(artifactsDir, file)).mtime.toISOString(), })), }; diff --git a/public/avatars/alexander.svg b/public/avatars/alexander.svg new file mode 100644 index 0000000..212196d --- /dev/null +++ b/public/avatars/alexander.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/avatars/dmitry.svg b/public/avatars/dmitry.svg new file mode 100644 index 0000000..4e31058 --- /dev/null +++ b/public/avatars/dmitry.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/avatars/elena.svg b/public/avatars/elena.svg new file mode 100644 index 0000000..cad67d9 --- /dev/null +++ b/public/avatars/elena.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/avatars/ivan.svg b/public/avatars/ivan.svg new file mode 100644 index 0000000..c82d3be --- /dev/null +++ b/public/avatars/ivan.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/avatars/konstantin.svg b/public/avatars/konstantin.svg new file mode 100644 index 0000000..bda4e0c --- /dev/null +++ b/public/avatars/konstantin.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/avatars/maria.svg b/public/avatars/maria.svg new file mode 100644 index 0000000..4a4ce5e --- /dev/null +++ b/public/avatars/maria.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/avatars/marketing.svg b/public/avatars/marketing.svg new file mode 100644 index 0000000..dc69216 --- /dev/null +++ b/public/avatars/marketing.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/avatars/olga.svg b/public/avatars/olga.svg new file mode 100644 index 0000000..31783b8 --- /dev/null +++ b/public/avatars/olga.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/avatars/operator.svg b/public/avatars/operator.svg new file mode 100644 index 0000000..d1529d5 --- /dev/null +++ b/public/avatars/operator.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/avatars/resurs.svg b/public/avatars/resurs.svg new file mode 100644 index 0000000..6cb87be --- /dev/null +++ b/public/avatars/resurs.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/avatars/support.svg b/public/avatars/support.svg new file mode 100644 index 0000000..c075c42 --- /dev/null +++ b/public/avatars/support.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/app/layouts/AppShell.tsx b/src/app/layouts/AppShell.tsx index 271129b..571c91a 100644 --- a/src/app/layouts/AppShell.tsx +++ b/src/app/layouts/AppShell.tsx @@ -28,6 +28,12 @@ export const AppShell: React.FC = () => { useEffect(() => { document.documentElement.setAttribute('data-theme', theme); + const kronaTheme = { + light: 'parchment-light', + 'soft-light': 'noble-green', + dark: 'graphite-forest-dark', + }[theme]; + document.documentElement.setAttribute('data-krona-theme', kronaTheme); }, [theme]); // Layout resolution: @@ -41,7 +47,7 @@ export const AppShell: React.FC = () => { const getViewportContainerStyles = () => { if (!isEmulatedOnDesktop) { - return 'w-full min-h-screen'; + return isCommunicationsPage ? 'w-full h-screen max-h-screen overflow-hidden' : 'w-full min-h-screen'; } switch (viewportMode) { case 'mobile': @@ -51,14 +57,16 @@ export const AppShell: React.FC = () => { case 'tablet-landscape': return 'max-w-[1180px] h-[800px] my-6 border-8 border-krona-border rounded-[24px] overflow-hidden shadow-2xl relative'; default: - return 'w-full min-h-screen'; + return isCommunicationsPage ? 'w-full h-screen max-h-screen overflow-hidden' : 'w-full min-h-screen'; } }; return (
@@ -76,9 +84,9 @@ export const AppShell: React.FC = () => { {/* Main Content Router Outlet */}
diff --git a/src/features/communications/components/CommunicationsHeader.tsx b/src/features/communications/components/CommunicationsHeader.tsx index 623ab94..bb7f3b8 100644 --- a/src/features/communications/components/CommunicationsHeader.tsx +++ b/src/features/communications/components/CommunicationsHeader.tsx @@ -52,7 +52,7 @@ export function CommunicationsHeader({ ]; return ( -
+
Больше, чем общение с поддержкой
-
-

- Коммуникации -

-

- Все сообщения, почта и каналы компании в одном пространстве -

+
+
+

+ Коммуникации +

+

+ Все сообщения, почта и каналы компании в одном пространстве +

+
+ + {/* Mobile compact action button */} +
-
- onTabChange(id as CommunicationTab)} - /> -
+
+
+ onTabChange(id as CommunicationTab)} + className="border-b-0 whitespace-nowrap" + /> +
+ + {/* Desktop / Tablet full button */} +
+ + +
+ +
+
+ + {/* Conversation list */} +
+ {filteredConversations.length === 0 ? ( +
+ Диалоги не найдены +
+ ) : ( + filteredConversations.map((item) => { + const isActive = item.id === activeChat.id; + return ( + + ); + }) + )} +
+ + ); + + // Active chat section + const activeChatSection = ( +
+ {/* Chat Header */} +
+
+ {layoutMode === 'compact' && ( + + )} + + + +
+
+

+ {activeChat.contact.name} +

+ +
+

+ {activeChat.contact.company} • {activeChat.contact.role} •{' '} + {activeChat.primaryConnector} + {activeChat.contact.isOnline && ( + ● в сети + )} +

+
+
+ +
+ + + + {/* Contact details button for medium (tablet) mode */} + {layoutMode === 'medium' && ( + + )} + + +
+
+ + {/* Messages Feed */} +
+
+ + Сегодня + +
+ + {activeChat.messages.map((msg) => ( +
+ {msg.direction === 'incoming' && ( +
+ +
+ )} + +
+

{msg.text}

+ + {/* Attachment Card */} + {msg.attachment && ( +
+
+
+ +
+
+ + {msg.attachment.name} + + + {msg.attachment.size} + +
+
+ +
+ )} + +
+ {msg.timestamp} + {msg.direction === 'outgoing' && ( + + )} +
+
+
+ ))} +
+ + {/* Quick Replies Bar */} +
+ + Быстрые: + + {quickReplies.map((qrText, idx) => ( + + ))} +
+ + {/* Message Composer */} +
+
+ + +