# KT AI – нормативная анатомия компонентов Канон значений — `tokens.json`, канон правил — `docs/DESIGN.md`, этот файл — анатомия сложных паттернов. ## App Shell Фиксированный левый sidebar + рабочая канва (grid `sidebar-width minmax(0,1fr)`). Структура sidebar сверху вниз: 1. Brand-ряд: логотип KT AI 18–22px + подпись 13–15px. 2. Основная навигация (Главная, Лента). 3. Группы разделов по 3–5 пунктов (закон Миллера), секции разделены label. 4. Утилиты внизу (настройки, тема, профиль). **Рейл (< 1024px).** Sidebar сжимается до 52px, и в него помещается только иконный ряд. Правило структурное, а не список классов: элемент с иконкой (`svg` или `img`) внутри остаётся и становится иконной кнопкой, элемент без иконки скрывается — показывать его в 52px нечем. Значит **любой текстовый блок, добавленный продуктом в sidebar, на ноутбуке с окном в половину экрана исчезнет**. Это осознанно: раньше такие блоки оставались и раскладывались в колонку букв поверх навигации. Нужно оставить элемент в рейле — `data-rail="keep"`. Тогда за то, что он влезает в 52px, отвечает продукт. **Ограничение правила.** Скрывается элемент, у которого иконки нет. Элемент, который иконку **содержит**, остаётся — вместе со всем своим текстом: `Как это работает` уцелеет целиком, потому что голый текстовый узел селектором не спрятать. Отсюда требование к разметке: **текст в сайдбаре всегда оборачивать в элемент** (``, ``), а не оставлять голым рядом с иконкой. Тогда обёртка попадёт под правило и скроется. Собственные части шелла это уже соблюдают. Проверка на 900px, которая ловит нарушение: ```js [...document.querySelectorAll('.kt-ai-sidebar *')] .filter(e => e.textContent.trim() && e.getBoundingClientRect().width < 24 && getComputedStyle(e).display !== 'none').length === 0 ``` Собственные части app-shell (например блок тура `.sb-tour`) закрывают рейл своим правилом: `kt-ai-components.css` лежит в `@layer`, а стили шелла беcслойные, и беcслойное правило побеждает слоёное независимо от специфичности. Три состояния отзывчивости – docs/DESIGN.md «Отзывчивость». Активный пункт: `bg-active` + 2px-акцент слева. ## Поиск и command palette Топ-поиск – компактный command-вход: высота 28px, max-width 320px, placeholder короткий («Поиск…»), бейдж `⌘K` в `.kt-ai-kbd`. Открывает command palette поверх экрана (`.kt-ai-command-overlay`). ## Карточки Карточки информационные, не декоративные. Использовать только когда элемент – повторяемый объект, стартовое действие, превью или поверхность modal/drawer. - v2.3: фон/рамка/тень – только через `card-bg / card-border / card-shadow` токены (dark: hairline + aurora-свечение; light: белая поверхность + тень). Радиус — `radius-3xl`, паддинг 12–14px. - Усиленное свечение hero/AI-поверхностей: `data-glow="strong"`. - Карточку в карточку не вкладывать. ## Ряды Ряды – доминирующий паттерн каталогов (вместо крупных карточек). - Основная подпись 13px; метаданные 11–12px muted. - Иконка 16px слева (14px в плотных рядах). - Справа – счётчик, статус или скор; действия (`.actions`) видимы на hover/focus/selected. - Hover `bg-hover`, выбран `bg-active`. Зона клика – весь ряд. ## Таблицы Таблица – для любого workflow с 5+ записями со статусом, владельцем, сроком или суммой. Принципы (research-backed): - Таблице – максимум полезной ширины (`content-table`); не заворачивать в карточку. - Колонки и дефолтная сортировка – от задачи пользователя, не от схемы БД. - Заголовки 1–2 слова; видимы только task-critical колонки, остальное – в detail/настройки колонок. - Один тип значения на колонку. Главная entity-колонка – максимум две строки: label + muted-метаданные. - Текст и даты – влево; суммы, скоры, счёт – вправо (sans + tabular-nums); заголовок выравнен со своими данными; центру – нет. - Лёгкие горизонтальные dividers; вертикальных колонок-сепараторов нет (ElevenLabs, тихо/воздушно); зебра – только в read-only числовых таблицах. Шапка на лёгком divider (не border-strong). Высоты рядов — `--kt-ai-table-row*`, значения в `tokens.json`. Чекбоксы первой колонки скрыты до hover ряда или выбора (не загромождают). - Hover ряда включён всегда (помогает сканировать), даже если ряд некликабелен. - Sticky header при вертикальном скролле; sticky первая колонка при горизонтальном (`.kt-ai-sticky-first`). - Row actions – только на hover/focus/selected (на touch – всегда); bulk-bar появляется после выбора 1+ рядов; дефолт – ноль выбранных. - Первый ряд не предвыбирать. Подсветка совпадений при активном поиске. - Пагинация/итог в футере; дефолт 25 строк; loading – skeleton-ряды; пусто – `.kt-ai-empty`. - Настройки пользователя (колонки, сортировка, фильтр) живут в течение сессии + «сбросить по умолчанию». Геометрия (v2.3): таблица по умолчанию frameless full-bleed (`data-framed="true"` – контейнер с рамкой); header sticky, 12px sentence-case medium fg-muted, hairline border-strong снизу; ячейки 12px, entity 13px/500; статус – rounded-rect тег (radius-tag); вертикальное выравнивание middle до 3 строк. Toolbar: высота ≥40px; слева – имя таблицы/saved view/счётчик, справа – фильтр, сортировка, колонки, экспорт (≤4 действий); iconбуттоны из спрайта; saved views скрыты, пока их меньше двух. Применённые фильтры – `.kt-ai-filterbar` под тулбаром. Mobile: скролл внутри контейнера, не страницы; первая колонка sticky; прячутся сначала вторичные колонки (источник, заказчик, владелец), потом суммы/сроки; футер компактный («1–25 из N»). Dashboard-паттерн: KPI-полоса над таблицей только если помогает пользователю принять следующее рабочее решение по рядам; одна таблица – главный объект; детали – в правом drawer. Operational dashboard copy: - Заголовок экрана – короткое имя рабочего объекта, 1-3 слова: `Очередь рисков`, `Реестр заявок`, `Проверка договоров`. Не использовать длинное описание процесса как H1/topbar title. - Subtitle объясняет scope одной короткой фразой, до 90 символов. Детали процесса, источники данных и ограничения – в drawer/help, не в H1. - KPI над таблицей показывают состояние работы пользователя: новые, требуют решения, риски, ждут документы, просрочено, закрыто сегодня, сумма риска или SLA. Не выводить экономию часов, FTE, ROI или тенге эффекта на пользовательский dashboard, если это не management/reporting view. - Деньги допустимы в KPI только когда это рабочий объект пользователя: `сумма риска`, `расхождение`, `к оплате`, `заблокировано`. `Экономия`, `эффект`, `ROI`, `FTE` – метрики проекта, а не основной рабочей очереди. - Фильтры над таблицей – только по workflow-status главного объекта, максимум 5 пунктов включая `Все`. Confidence, тип риска, источник данных и technical labels не попадают в primary filters по умолчанию. - Workflow-status, confidence и risk type – разные поля. Нельзя смешивать их в один набор `statuses`, иначе экран перегружает фильтры и ломает decision focus. - Labels короткие: фильтр до 2 слов, status-pill до 4 слов, table header до 2 слов. Длинная причина риска уходит в drawer или отдельную колонку `Расхождение` с коротким текстом. - Для process-owner экран должен показывать next action: что требует решения сегодня, что заблокировано, где риск, какой документ нужно дослать, что закрыто. Просто список документов без статуса работы – неполный dashboard. ## Подтверждение опасных действий Три ступени по цене ошибки, а не по «важности»: | Что за действие | Чем подтверждается | |---|---| | Обратимое, но раздражающее | выполнить сразу + тост с «Отменить» (`.kt-ai-toast .action`) | | Деструктивное | модалка; кнопка — глаголом действия («Удалить агента»), не «Да» и не «ОК» | | Необратимое | ввод имени объекта | **Удержание кнопки («hold to delete») в систему не берём.** Разбор, чтобы к этому не возвращались: - **Недоступно с клавиатуры.** У удержания мыши нет клавиатурного эквивалента; накрутить удержание пробела можно, но это конфликтует со штатным «пробел нажимает кнопку» и остаётся неочевидным. Любая доступная реализация требует запасного пути — и тогда на одно действие приходится два механизма. - **Ничего не сообщает.** Модалка говорит, ЧТО удаляется, сколько записей и что от них зависит. Кольцо прогресса не говорит ничего — а в наших продуктах (закупки, договоры, инциденты) именно последствие и нужно видеть. - **Не открывается само.** Чтобы понять, что кнопку надо держать, надо сначала прочитать подпись. - **Ломается на тач-устройствах**, где долгое нажатие занято системным контекстным меню и выделением текста. Ниша, на которую он метит — «действие жалко закрывать модалкой» — у нас уже закрыта отменой в тосте, и закрыта лучше: ноль трения до действия и полное восстановление после. ## Композер чата `.kt-ai-prompt-bar` — `textarea` + кнопка отправки. Над полем может стоять ряд пилюль навыков `.kt-ai-prompt-skills`: ```html
Сверка документов
``` Выбранный навык — **объект, а не текст**: его нельзя случайно надкусить бэкспейсом, он удаляется целиком. Пустой ряд схлопывается и не занимает высоту. **Почему пилюли над полем, а не внутри строки.** Вставить невредактируемый элемент в середину текста можно только в `contenteditable`. Переписывать композер на `contenteditable` ради этого нельзя: ломаются отмена ввода, набор через IME, вставка из буфера, доступность и мобильные клавиатуры. Пилюли над полем дают то же свойство без единого из этих рисков. **Slash-команды.** Скрипт `kt-ai-composer.js` включается атрибутом `data-composer="true"`, список навыков — в `data-skills` (JSON: `id`, `label`, `hint`): - «/» в начале слова открывает меню, дальнейший набор фильтрует по названию и подсказке; - ↑ ↓ ходят по списку, Enter или Tab выбирают, Esc закрывает; - при выборе «/запрос» **убирается из текста** — навык теперь объект, а не строка; - крестик на пилюле снимает навык, Backspace в пустом поле снимает последний; - Enter без Shift отправляет: событие `composer:submit` с `{ text, skills }`. Меню открывается **над** полем: композер обычно стоит внизу экрана, и список, растущий вниз, уехал бы за край. **Каретка при вставке из меню.** Специального механизма не нужно: `textarea.selectionStart` переживает потерю фокуса, поэтому вставлять надо в `selectionStart`, а не в конец. Проверено измерением. ## Кружок с номером Шаг мастера, позиция в списке, порядковый номер. ```html 1 3 8 ``` ```tsx ``` Состояния: по умолчанию (будущий), `done` (пройден — тише текущего, но не тусклее будущих), `current` (заливка: он один на экране и обязан находиться мгновенно). Тона `ok / warn / risk` — когда номер сам несёт оценку. Размеры `sm` 18px, обычный 22px, `lg` 28px. **Почему это не иконка.** Наши иконки штриховые (1.75 в сетке 24) и используются на 14–15px. Цифра штрихом внутри штриховой окружности на таком размере выходит 6–7px высотой при штрихе около 1px — просветы у 6, 8 и 9 схлопываются. Плюс набор иконок был бы конечным (1–9), а номера бывают двузначные. Номер рисуется текстом: любое число, чёткость на любом размере, цвет и состояния от токенов. Цифры моноширинные — в колонке номеров ряды не пляшут. Степпер (`.kt-ai-step-item .n`) — тот же кружок; отдельного определения у него нет, иначе они разъедутся. ### Номер иконкой Когда номер стоит в ряду с другими иконками — `circleNumber0` … `circleNumber9`: ```html ``` ```tsx ``` Набор взят из [Tabler Icons](https://tabler.io/icons) (MIT) и приведён к нашей сетке: радиус окружности 10 вместо 9, штрих 1.75 вместо 2 — чтобы в ряду с `check-circle` они были одного размера и веса. **Иконка или компонент.** Иконка — фиксированные 0–9 и один цвет, зато встаёт в любой поток иконок. Компонент `.kt-ai-num-badge` — любое число, включая двузначные, состояния и заливка активного. Нужен номер шага в мастере — компонент; нужен номер рядом с иконкой в строке — иконка. ## Орб агента Монохромное точечное облако на canvas — индикатор того, что агент работает. Заменяет спиннер там, где ожидание содержательное, и стоит в кнопке помощника вместо подписи «AI». ```html ``` ```tsx import { KTAIOrb } from "kt-ai-design-system/kit"; ``` Состояния: `idle` (медленное дыхание), `thinking` (вращение), `listening` (пульс). Размеры — 20px в кнопке и строке, 64px как крупный индикатор. **Цвет не задаётся.** Точки рисуются `currentColor`, поэтому орб темизуется теми же токенами, что и текст рядом, и работает в обеих темах без единого правила про тему. Хотите тише — поставьте `color: var(--kt-ai-fg-faint)`. Рисование живёт в одном файле `kt-ai-orb.js` на оба рантайма: React-компонент его подключает, а не дублирует. Иначе HTML-превью и React-сборка разошлись бы в анимации — это запрещает гейт паритета G7. Из этого следует поставка: `kt-ai-orb.js` входит в бандл кита как `public/kt-ai-orb.js` — компонент запрашивает его у сайта по абсолютному пути. Без файла кнопка помощника рисует пустой кружок, и ни сборка, ни типы об этом не скажут: проверяет гейт 15 в `doctor.py`. Первый кадр рисуется синхронно, до `requestAnimationFrame`: в фоновой вкладке браузер rAF не вызывает, и орб оставался бы пустым прямоугольником. По той же причине при `prefers-reduced-motion` остаётся статичный кадр, а не пустота. Идея заимствована у [thinking-orbs](https://orbs.jakubantalik.com) (MIT). Сам пакет не подошёл: его сборка импортирует React на верхнем уровне, а HTML app-shell работает без React. ## Плашка показателя Четыре уровня: подпись, значение, уточнение, сноска. ```json { "label": "Просрочено в июне", "value": "58", "hint": "из 171" } ``` ```html
58Просрочено в июне из 171
``` ```tsx ``` **Значение — само число, уточнение — знаменатель, доля или период.** Не «58 из 171» одной строкой: полоса перестаёт читаться как ряд величин, глаз ищет число, а находит фразу. Уточнение до 40 символов. **Значение идёт сразу за подписью, а не прижимается к низу карточки.** Иначе в соседних карточках — с уточнением и без — числа встают на разной высоте и кажутся разными по важности. Проверяется измерением: у всех плашек полосы `value` обязан быть на одной высоте. Уровень поддержан во всех трёх местах: поле `hint` в `product.schema.json`, вывод в HTML-рантайме и в `KTKpiCard`. Компонент рисуется классом `.kt-ai-kpi` в обоих рантаймах — своих утилит у React-версии нет, иначе правка в ДС до неё не доезжала бы. ### Сноска: чем число НЕ является `hint` отвечает «из чего считано» и потому ограничен 40 знаками — снимать этот потолок нельзя, иначе полоса плиток перестанет быть полосой. Но у поля-источника бывает вторая правда: оно меряет не ровно то, чем показатель назван. «Ожидание в очереди» не отделяет автоприветствие бота от первого сообщения оператора — число верное, а работой человека не является. Это другой вопрос, и отвечает на него отдельное поле: ```json { "label": "Ожидание в очереди", "value": "00:41", "hint": "из 33 352 диалогов", "note": "Поле выгрузки считает время до первого сообщения в чате, а им бывает автоприветствие бота. Число верное, но работой оператора оно не является." } ``` ```html Поле выгрузки считает время до первого сообщения… ``` ```tsx ``` **Потолка у сноски нет** — она не стоит в потоке полосы, её отбивает линия. **И она не прячется под клик.** Оговорка существует затем, чтобы число не прочитали неверно; спрятанное под клик читают не все — ровно как карточку строки, из-за чего оговорку и потребовалось ставить у плитки. По той же причине у неё цвет `fg-muted`, а не самый тихий `fg-faint`: 4,9:1 на кегле 11px — это «тихо» на грани нечитаемого, а сноску надо прочитать. Куда сноска НЕ идёт: в баннер. Баннер стоит над числами, и оговорки, собранные в него, дают абзац текста до первого числа (замеряли: 716 знаков на одном экране). Баннер предупреждает, из-за чего число соврёт; сноска объясняет, что число значит; вывод раздела — что из чисел следует. Три разных места. ## Вывод раздела Последний блок экрана, после таблицы. Контракт: `conclusion: { title?, text }`, пустая строка в `text` делит абзацы. ```html
Что показывают эти числа Средняя считается только по ответившим… Смотреть стоит на долю дольше норматива…
``` **Это противовес баннера, а не его вариант.** Баннер стоит НАД числами — значит, читается до данных, и потому окрашен тоном: он предупреждает. Вывод стоит ПОСЛЕ данных и говорит, что из них следует: разбор смещения выборки, оговорка о методике, ответ на вопрос раздела. Цвета статуса у него нет — сигналить ему нечем; есть линия сверху («данные кончились, начинается их чтение») и мера строки 78ch, потому что вывод — сплошной текст, а не пары «подпись — значение». Рисуется одинаково во всех архетипах, кроме `conversational`: там контент владеет всей областью сам. ## Статика под префиксом развёртывания Приложение живёт не только в корне домена. Под `/cons` файл `/kt-ai-orb.js` уходит в корень САЙТА и не находится: пропадают спрайт иконок, орб, подсказка диаграммы и режим отзыва — тихо, только на развёртывании. ```tsx import { ktAsset, ktSetBasePath } from "kt-ai-design-system/kit"; ktSetBasePath("/cons"); // явно, до первого рендера fetch(ktAsset("/api/proposals")); // свои маршруты ломаются так же ``` Префикс — свойство развёртывания, а не аргумент вызова, поэтому задаётся один раз: `ktSetBasePath()` → `process.env.NEXT_PUBLIC_BASE_PATH` → атрибут `` → пусто. Порядок и почему он такой — `docs/SETUP.md`. В HTML-рантайме то же самое даёт `window.ktAiAsset("kt-ai-orb.js")`, а префикс приходит атрибутом на ``. Теги статики там пишет загрузчик, а не разметка: адрес уже разобранного `` изменить нельзя — браузер начинает грузить его сразу. Гейт 18 (`doctor.py`) считает путь от корня сайта мимо `ktAsset()` ошибкой сборки ДС — в обоих рантаймах. ## Чарты Высота 190–220px; bar 20–28px, радиус 2px; подписи значений 11px mono muted. Остальные правила – docs/DESIGN.md «Чарты». ## Счётчик в пункте меню Одно значение — как раньше, приглушённым тоном: ```tsx { label: "Проекты", icon: "lots", count: "12" } ``` Прогресс — пара «всего / сделано». «Сделано» рисуется тоном `ok`, и по пункту меню видно не только объём работы, но и продвижение: ```tsx { label: "Спецификации", icon: "file", count: { value: 11, done: 5 } } ``` ```html 11/5 ``` Одним цветом «11/5» читается как одно число с косой чертой — именно поэтому продукты подставляли вместо счётчика свою строку. Класс `.kt-ai-count-split` не привязан к меню: пара «всего/сделано» уместна и во вкладке, и в заголовке секции. Поле `nav[].count` в продуктовом контракте — устаревшее и одночисловое; пара живёт в API кита, а не в контракте. ## Поиск в шапке Два размера и один переключатель подсказки сочетания. ```tsx ``` ```json "search": { "placeholder": "Договор, филиал…", "compact": true } ``` ```html
⌘K
``` `compact` — 176px вместо 320px: шапка отдаёт место остальному. Бейдж в компактном поле не показывается — это ширина запроса, а не запроса с подписью; сочетание при этом работает и названо в `title`. **Сочетание и его подпись включаются ОДНИМ полем** (`shortcut`). Подпись без обработчика — обещание (правило 9d); обработчик без подписи — незаявленная горячая клавиша, которую пользователь находит случайно. Порознь их выключать нечем и незачем. **Подпись называет клавишу этой машины:** `⌘K` на Apple, `Ctrl K` на остальных. Обработчик слушает и `meta`, и `ctrl` — значит подпись «⌘K» на Windows была не сокращением, а неверным именем работающей клавиши. В React подпись считается после монтирования: на сервере `navigator` нет, и вычисленная там подпись разошлась бы с клиентской (гидрация с предупреждением). **Подсказка сочетания — элемент, а не текст в placeholder.** Пока «⌘K» стояло строкой внутри «Поиск… ⌘K», её нельзя было ни убрать, ни перевести, ни отличить от собственно подсказки поля. Гейт 19 держит это правило. Голый `.kt-ai-search` без обёртки продолжает работать — им набраны фильтры над таблицами, у которых никакого сочетания нет. ## Группа разделов в меню Тринадцать пунктов подряд читаются как список ссылок, а не как устройство продукта. Необязательное поле `group` собирает идущие подряд пункты под общим заголовком: ```json "nav": [ { "label": "Обзор", "group": "Поток" }, { "label": "Нагрузка", "group": "Поток" }, { "label": "Сверки", "group": "Достоверность" }, { "label": "Контроль Alem","group": "Достоверность" } ] ``` **Заголовок — не пункт.** Он не кликается, не получает фокус и в нумерации разделов не участвует: переход идёт по индексу пункта в `nav`, поэтому `banner.actionNav` и `S.nav(i)` считают ровно то же, что считали без групп. Пункты без `group` идут списком, как раньше. Заголовок собирает пункты, идущие ПОДРЯД. Разнесённые по списку пункты с одним значением дадут два одинаковых заголовка — валидатор об этом предупреждает, а рантайм не переставляет разделы: их порядок принадлежит продукту. В полосе значков (832–1023px) слов показать негде, и заголовок становится тем, чем является по сути, — тонким разделителем между группами. Ниже 832px sidebar превращается в выдвижную панель во всю ширину, и заголовки снова показываются словами. Сворачивания групп нет намеренно: свёрнутая группа прячет разделы, которые продукт обязан показывать, — в том числе пустые. ## Стек, который группирует `.kt-ai-v` — вертикальный стек вместо `marginTop` у каждого потомка. Кроме сырых ступеней (`xs/sm/lg/xl` → `space-*`) у него есть **ступени ритма**, и на экране пользоваться нужно именно ими: | `data-gap` | | Что этим сказано | |---|---|---| | `tight` | 12 | это одна вещь: фильтры и их вкладки, карточка ↔ карточка в списке | | `block` | 16 | панель управления и то, чем она управляет; блоки одного региона | | `group` | 24 | регионы экрана: шапка ↔ баннер ↔ содержимое | | `region` | 32 | поля рабочей области, отрыв от топбара | Ступени — у `.kt-ai-h-stack` тоже (`tight`, `block`). **Один зазор на весь корневой столбец — самая частая ошибка компоновки.** Тогда панель фильтров, её вкладки и карточки списка расходятся на одно и то же расстояние: групп на экране нет, есть столбец одинаково далёких полос. Вкладывай стек в стек — регионы снаружи, группа внутри: ```html
…
…
…
…карточки списка…
``` `.kt-ai-toolbar` — та же ступень `tight`, названная по роли: панель шага. Отдельный класс, потому что панель повторяется на каждом экране-очереди, и без имени её собирали своей обёрткой в каждом продукте. **Вертикальный `padding` `.kt-ai-filterbar` внутри стека снимается** (`.kt-ai-v > .kt-ai-filterbar`, `.kt-ai-toolbar > .kt-ai-filterbar`): иначе собственные 6px бара складывались бы со ступенью и `tight` давал бы на экране 24 вместо 12 — ступень называла бы одно, а показывала другое. Вне стека padding остаётся: там он отделяет бар от соседнего содержимого. Свой `app-stack-*` или своя обёртка панели в продукте — сигнал, что группировку не выразили, а обошли. ## Ряд, которому запрещено переноситься `.kt-ai-h-stack` по умолчанию переносится. Запрет переноса — два разных значения, и разница не косметическая: | | | |---|---| | `data-nowrap="true"` | ряд не переносится. Что делать с переполнением — забота автора. В узкой колонке такой ряд выходит за родителя и толкает горизонтальную прокрутку **всей страницы** | | `data-nowrap="scroll"` | ряд не переносится и прокручивает себя сам | Почему `true` не сделали прокручиваемым молча: `overflow-x: auto` по спецификации вынуждает `overflow-y` стать `auto` тоже — ряд начинает обрезать всё, что вылезает вверх и вниз: меню, поповер, подсказку, кольцо фокуса. Ряд с кебаб-кнопкой сломался бы, и виновника было бы не найти. Поэтому выбор явный и по имени. Прокрутка широкой **таблицы** — не этот случай: ей занимается `.kt-ai-table-wrap`. ## Сенсорная цель На ширине ≤768px (или при `pointer: coarse`) ни один интерактивный элемент не ниже **44px** — минимальной цели, в которую попадают пальцем. Порог хранится в одном месте, токене `--kt-ai-control-h-touch`, и раздаётся правилом в конце `kt-ai-components.css`. Своими руками высоты поднимать не нужно — нужно попасть под правило: - компоненты ДС — через классы `.kt-ai-*` (высота приходит из `--kt-ai-control-h-*`, которые на сенсорной ширине сами становятся 44px); - разметка React-кита — через `[data-kt-ai-kit]`, метку поддерева, которую ставит `KTAIShell`: кит размечен утилитами Tailwind, и порог на каждой из ~70 кнопок означал бы 70 мест, где его можно забыть. Контрол, который обязан быть меньше, помечается `data-kt-touch="off"` — явным отказом. Молча меньше быть нельзя: цена промаха по «Принять»/«Отклонить» — чужое решение. Ширина при этом остаётся по содержимому: растягивать пилюли фильтра по горизонтали значит ломать плотность. Квадратную цель (иконка, чекбокс) правило задаёт отдельно. ## Рама приложения: контрол появляется вместе со смыслом `KTAIShell` не рисует того, что не умеет. Кнопки поиска и профиля появляются, только когда продукт дал им содержание, — иначе их нет: ```tsx ``` Почему так, а не «покажем, потом подключим»: кит вшивал три собственных пункта палитры и меню профиля из четырёх строк — ни у одной не было обработчика. Продукт починить раму не может, он может только заклеить её своим `display: none`, и оба продукта так и сделали (заявка DS-005). Правило компоновки 9d говорит прямо: сломанный или декоративный контрол показывать нельзя. Проверяет гейт 17. Аватар берёт инициалы из имени (`ktInitials`), уведомление становится кнопкой только при `onSelect`, «перейти» в «Активности» — только при `href`/`onSelect`. **Помощник — по тому же правилу, с 6.8.0.** Он был единственным исключением: кнопка рисовалась всегда и била в `/api/ai/chat` — маршрут-заглушку из бандла ДС, отвечавшую текстом для разработчика. Специалист закупок читал «Mock AI response… Подключите реальный LLM provider», а продукт прятал кнопку своим CSS — ровно то, что канон себе уже запретил. ```tsx // отвечает маршрут продукта // отвечает контракт — как в HTML-рантайме, сети не нужно ``` Без пропа `assistant` кнопки помощника нет. Пропы `aiContext` и `aiSuggestions` сняты: ни один из них не говорил, что помощник умеет отвечать. `KTScreen` собирает `assistant` из блока `ai` контракта сам — экран из контракта получает работающего помощника, а не заглушку. Клиент принимает и `reply`, и `answer`: маршрут в бандле отдавал `answer`, а кит читал только `reply` — помощник не показал бы ответ собственной заглушки ни разу. ## Раскрытие без JS `.kt-ai-disclosure` — на div-ах и требует скрипта. Серверной странице нужен нативный `
`, состояние которого хранит браузер: ```html
Подробности обработки…
Прочитано 1 284 строки, пригодных 1 191.
``` Маркер ОС погашен в обеих записях (`::marker` и `::-webkit-details-marker`), шеврон крутится трансформом. Одиночный `` внутри оболочки тоже получает `cursor: pointer` — курсор единственный признак, по которому видно, что строка раскрывается. Оба продукта написали это себе сами, каждый по-своему; теперь это канон. ## Подвал действий боковой панели ```tsx } … /> ``` `.kt-ai-drawer-actions` липнет ко дну панели: разбор длинный, а решение принимают внизу — без этого «Принять» и «Отклонить» уезжают за экран и специалист скроллит обратно на каждой карточке. ## Подсказка значения на диаграмме Столбец, точка или сегмент показывает своё число при наведении, с клавиатуры и по касанию: ```html
``` ```tsx { label: "14.05.2026", value: 1284, hint: { title: "14 мая 2026, четверг", rows: [{ label: "Диалогов", value: "1 284" }] } } ``` **Числа даёт продукт.** `hint` уходит в разметку как есть: рантайм ничего не форматирует и не пересчитывает. Сервер и браузер округляют по-разному, и разошедшийся формат — это разошедшееся число. Без `hint` показывается `label: value` — то же, что показывал нативный `title`. Почему не `title`: он появляется через секунду, рисуется средствами ОС (чужой шрифт, ни одного токена, в тёмной теме — вставка из чужого продукта), недоступен с клавиатуры, не читается диктором, на планшете не показывается вовсе и умеет одну строку. Почему не `.kt-ai-tooltip`: та однострочная и у края карточки обрезается. | | | |---|---| | марка | любой элемент с `data-kt-tip` — столбец, точка SVG, сегмент donut, клетка будущей тепловой карты | | появление | сразу, без паузы; привязано к марке, а не к курсору | | край | подсказка разворачивается внутрь и не обрезается | | клавиатура | `tabindex="0"`, показ по фокусу, `Esc` убирает НЕ снимая фокус | | диктор | `role="tooltip"` + `aria-describedby` на марке | | тач | касание показывает, касание вне — убирает | | строк | заголовок + до трёх пар; значения справа, `tabular-nums` | | перерисовка | переживает: слушатели делегированы на `document`, не на марки | Рисование — `kt-ai-chart-tip.js`, один файл на оба рантайма (как орб). В бандле кита лежит как `public/kt-ai-chart-tip.js`; гейт 15 следит, чтобы доехал. ## Ширина свободного экрана ```json { "width": "full" } // общая ширина продукта { "width": "reading" } // мера чтения сплошного текста, ~720px ``` Без поля ширину определяют типы секций: экран только из текстовых — `reading`, любой другой — `full`. **Секция `fields` считается НЕ текстом**: пары «подпись — значение» и таблица критериев на 720px читаются хуже, чем на общей ширине, а заголовок экрана всё равно идёт во всю ширину — экран выглядел съехавшим к центру. Поле нужно, чтобы продукт называл ширину сам, а не подбирал типы секций ради неё. ## Чипы и segmented Чип (v2.3, Attio): rounded-rect `radius-tag` (5px), высота 20–24px, паддинг 8–10px, шрифт 11–12px, фон chip-токены. Segmented: сегмент 28px, активный – `bg-active` + `fg`. ## Drawer Правый drawer для деталей с сохранением контекста списка. - Ширина 420–560px (720px для сравнения evidence); < md – full-screen. - Header 48px: заголовок + крестик справа сверху; закрытие – Esc и клик мимо. ## v3.6 | Класс | Что это | Референс | |---|---|---| | `.kt-ai-slider` (+`.kt-ai-slider-row` с `.ends`) | слайдер настройки на `input[type=range]` (Speed/Stability) | ElevenLabs Settings-панель | | `.kt-ai-cols` | адаптивный двухколонник: складывается в одну колонку <720px (wizard/inbox/copilot). Прямым детям сам ставит `min-width: 0` — без этого `1fr` (= `minmax(auto,1fr)`) растягивается под содержимое и на телефоне даёт горизонтальную прокрутку страницы | — | | `.kt-ai-row-hover` / `.kt-ai-hover-lift` | hover-обратная связь рядов и кликабельных карточек | ElevenLabs rows | --- ## Компоновка экрана Держать прототипы визуально консистентными и полезными разработчикам без тяжёлого процесса. ### Правила 1. Один главный рабочий объект на экран. В табличных продуктах главный объект – таблица. 2. Не вкладывать карточки в карточки. Вторичные детали – ряды, dividers или drawer. 3. Шапка приложения компактная. Никаких лендинг-hero внутри операционных инструментов. 4. KPI – тихая метрик-полоса; для кабинета/дашборда с 2-4 главными метриками допустима сетка стат-карточек (`data-cards="true"`). 5. Фильтры – в одном тулбаре непосредственно над таблицей; применённые – filter-chips под ним. 6. Таблица идёт раньше вспомогательных панелей (статусы, фоновые задачи). 7. Действия ряда тихие: появляются на hover или при выборе (на touch – видимы всегда). 8. Детали открываются в правом drawer, не на новой странице. 9. Точка входа AI не конкурирует с таблицей: кнопка-орб Төре в топбаре рядом с поиском (не плавающий FAB). 9a. Главное действие создания – primary-кнопка вверху sidebar под брендом («Новый …» от product.entity); мастер создания (этапы процесса + загрузка документов) – в modal. Этапы/таймлайн НЕ выносить на главный список – их место в мастере и в деталях записи. 9b. (ElevenLabs) Топбар слева: панель-toggle (свернуть sidebar) + breadcrumb с именем текущего раздела (мелко, muted). Контент: hero-заголовок раздела (text-2xl semibold — 24px; text-3xl/bold это красный флаг DoD) + подпись-назначение слева, главное действие справа (как «New order» в ElevenLabs); лого только в sidebar. Воздух между топбаром и заголовком — `stack-region` (32px), дальше блоки экрана идут через `stack-group` (24px). Имя продукта живёт в sidebar, имя раздела — в breadcrumb и hero (это паттерн ElevenLabs, не дубль двух больших заголовков). 9c. Boilerplate обязателен для shell: sidebar, topbar, theme toggle и AI-кнопка Төре. Эти элементы не перерисовывать и не заменять. Главная рабочая область проектируется под задачу процесса. 9c1. Для рабочих очередей, реестров и консолидации закупок default – classic app-shell: таблица возможностей, детали в правом drawer, короткие статусы и одно primary-действие. Custom workspace используется только если таблица + drawer не решают основную работу пользователя. 9d. Если в прототипе показаны theme toggle, поиск, AI-кнопка или основные action-кнопки, они должны быть интерактивными в рамках прототипа. Нельзя показывать сломанные или декоративные controls. 9e. Поле поиска по умолчанию пустое. Не подставлять случайный пример вроде названия товара, если пользователь прямо не выбрал этот поиск. 9f. В операционном экране консолидации допустимы метрики, которые помогают принять решение: сколько ТС обработано, сколько имеют потенциал консолидации, какой предварительный потенциал экономии в тенге. Это не project ROI, а рабочие числа для приоритизации. 9g. В UI запрещены служебные формулировки генерации: `модель вернула`, `fallback`, `не проходит дизайн-систему`, `shell`, `JSON`, `prompt`. Пользователь видит продуктовый язык, а не внутренний процесс сборки. 10. Горизонтальный скролл на мобильных – только внутри контейнера таблицы. ### Порядок страницы по умолчанию 1. Shell: sidebar и топбар (glass, `.kt-ai-topbar`). 2. Компактный заголовок страницы. 3. Рабочая область под конкретный процесс: таблица, матрица, карточка возможности, сравнение источников или другой подходящий формат. 4. Метрики и фильтры – только если помогают выполнить текущую работу пользователя. 5. Вспомогательные статус-ряды. 6. Второстепенные панели. 7. Вывод раздела (`.kt-ai-conclusion`) — последним блоком, после таблицы. 8. Drawer, toast, AI-кнопка.