feat(ai-chat): role-selection cards as new-chat empty-state (verified live) #113

Closed
Ghost wants to merge 0 commits from feat/ai-chat-role-cards into develop

Фича: выбор agent-role карточками в пустом окне чата

Отчёт по скиллу архитектора.

1. Задача

Из docs/backlog/ai-chat-role-cards-empty-state.md: заменить выпадающий список <Select label="Agent role"> на цветные карточки identity по центру пустого окна нового чата. Клик по карточке применяет роль; если не нажал и просто написал — срабатывает дефолтный Universal assistant; после первого сообщения карточки исчезают.

2. Анализ / ключевое решение

Карточки рисуются через УЖЕ существующий проп emptyState у MessageList (его использует публичный шэр), а не отдельным блоком. Цепочка: AiChatWindow собирает <RoleCards/>ChatThread emptyStateMessageList. Тогда:

  • «по центру пустого окна» получается само (MessageList оборачивает emptyState в <Center>);
  • «не нажал и написал → дефолт» получается само (как только messages.length>0, empty-state не рендерится, selectedRoleId остаётся null);
  • вся серверная обвязка (roleId в body, фиксация роли при создании чата) не меняется — изменения чисто фронтовые.

3. Решение

  • Новый RoleCards (role-cards.tsx + role-cards.module.css): первая карточка — Universal assistant (серая, value null, подсвечена по умолчанию), далее по карточке на каждую включённую роль; цвет циклично из палитры 10 имён Mantine через чистый хелпер roleCardColor(index); theme-aware CSS-переменные (-light/-light-color/-filled) — корректны в светлой и тёмной теме; эмодзи опционально, описание в title. Каждая карточка — UnstyledButton с aria-pressed (a11y + тестируемость).
  • ai-chat-window.tsx: удалён блок <Select> и его импорт; добавлен roleCardsNode (только при activeChatId===null && enabledRoles.length>0), передан в ChatThread emptyState.
  • chat-thread.tsx: добавлен проп emptyState?: ReactNode, форвард в MessageList.
  • Удалена завершённая планировочная доку.

4. Найденные баги

Ревью-субагент: проблем не найдено — APPROVE с первого прохода (wiring корректен, удаление Select ничего не сломало, дефолт без регресса, палитра/тесты содержательны, посторонних изменений нет).

5. Тестирование — статистика

Юнит/компонентные тесты: 2 файла / 9 тестов зелёные (role-card-color.test.ts + role-cards.test.tsx); client tsc --noEmit чисто.

Циклы ревью: 1 (APPROVE, 0 правок).

Живая браузерная проверка: 1 цикл, автономный субагент, headless Chromium, реальный стенд + LLM. Багов: 0. Засеяно 3 включённые роли (Пират/Дедушка/Ревьюер).

  • ✓ В пустом новом чате — именно КАРТОЧКИ, не <Select>: 4 UnstyledButton[aria-pressed] (Universal + 3 роли с эмодзи), без нативного <select> и без комбобокса «Agent role» (скрин f3-cards).
  • ✓ Universal по умолчанию aria-pressed=true; клик по «Пират» → у него true, у Universal false; обратный клик возвращает (скрины f3-pirate-selected).
  • ✓ End-to-end (1 ход LLM): с выбранным «Пират» отправлено «hi» → чат привязан к роли (бейдж 🏴‍☠️ Пират в шапке, пиратский ответ «Ahoy there, matey!»), карточки исчезли (скрин f3-role-bound).
  • ✓ Негатив: новый чат без выбора → отправка «hi» → бейджа роли нет (Universal) (скрин f3-negative).

🤖 Generated with Claude Code

## Фича: выбор agent-role карточками в пустом окне чата Отчёт по скиллу архитектора. ### 1. Задача Из `docs/backlog/ai-chat-role-cards-empty-state.md`: заменить выпадающий список `<Select label="Agent role">` на цветные карточки identity по центру пустого окна нового чата. Клик по карточке применяет роль; если не нажал и просто написал — срабатывает дефолтный Universal assistant; после первого сообщения карточки исчезают. ### 2. Анализ / ключевое решение Карточки рисуются через УЖЕ существующий проп `emptyState` у `MessageList` (его использует публичный шэр), а не отдельным блоком. Цепочка: `AiChatWindow` собирает `<RoleCards/>` → `ChatThread emptyState` → `MessageList`. Тогда: - «по центру пустого окна» получается само (MessageList оборачивает emptyState в `<Center>`); - «не нажал и написал → дефолт» получается само (как только `messages.length>0`, empty-state не рендерится, `selectedRoleId` остаётся `null`); - вся серверная обвязка (`roleId` в body, фиксация роли при создании чата) **не меняется** — изменения чисто фронтовые. ### 3. Решение - Новый `RoleCards` (`role-cards.tsx` + `role-cards.module.css`): первая карточка — Universal assistant (серая, value `null`, подсвечена по умолчанию), далее по карточке на каждую включённую роль; цвет циклично из палитры 10 имён Mantine через чистый хелпер `roleCardColor(index)`; theme-aware CSS-переменные (`-light`/`-light-color`/`-filled`) — корректны в светлой и тёмной теме; эмодзи опционально, описание в `title`. Каждая карточка — `UnstyledButton` с `aria-pressed` (a11y + тестируемость). - `ai-chat-window.tsx`: удалён блок `<Select>` и его импорт; добавлен `roleCardsNode` (только при `activeChatId===null && enabledRoles.length>0`), передан в `ChatThread emptyState`. - `chat-thread.tsx`: добавлен проп `emptyState?: ReactNode`, форвард в `MessageList`. - Удалена завершённая планировочная доку. ### 4. Найденные баги **Ревью-субагент:** проблем не найдено — **APPROVE** с первого прохода (wiring корректен, удаление Select ничего не сломало, дефолт без регресса, палитра/тесты содержательны, посторонних изменений нет). ### 5. Тестирование — статистика **Юнит/компонентные тесты:** 2 файла / **9 тестов** зелёные (`role-card-color.test.ts` + `role-cards.test.tsx`); client `tsc --noEmit` чисто. **Циклы ревью:** 1 (APPROVE, 0 правок). **Живая браузерная проверка:** 1 цикл, автономный субагент, headless Chromium, реальный стенд + LLM. Багов: **0**. Засеяно 3 включённые роли (Пират/Дедушка/Ревьюер). - ✓ В пустом новом чате — именно КАРТОЧКИ, не `<Select>`: 4 `UnstyledButton[aria-pressed]` (Universal + 3 роли с эмодзи), без нативного `<select>` и без комбобокса «Agent role» (скрин `f3-cards`). - ✓ Universal по умолчанию `aria-pressed=true`; клик по «Пират» → у него `true`, у Universal `false`; обратный клик возвращает (скрины `f3-pirate-selected`). - ✓ End-to-end (1 ход LLM): с выбранным «Пират» отправлено «hi» → чат привязан к роли (бейдж `🏴‍☠️ Пират` в шапке, пиратский ответ «Ahoy there, matey!»), карточки исчезли (скрин `f3-role-bound`). - ✓ Негатив: новый чат без выбора → отправка «hi» → бейджа роли нет (Universal) (скрин `f3-negative`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-06-21 06:32:55 +03:00
Replace the new-chat <Select label="Agent role"> picker with colored role
cards rendered as the empty-state of a brand-new chat (centered in the window),
per docs/backlog/ai-chat-role-cards-empty-state.md. Clicking a card selects that
identity; sending without a pick falls back to the Universal assistant; the
cards disappear once the chat is non-empty. Purely client-side — the existing
selectedAiRoleIdAtom + roleId request wiring (server role fixation on chat
creation) is unchanged.

- new RoleCards rendered through the existing emptyState prop chain
  (AiChatWindow -> ChatThread -> MessageList); MessageList already supported it.
- Universal assistant card (gray, value null, default-selected) + one card per
  enabled role, color cycled from a 10-name Mantine palette via the pure
  roleCardColor() helper; theme-aware CSS vars (light/-light-color/-filled).
- each card is an UnstyledButton with aria-pressed for a11y + testability.
- tests: role-card-color (palette cycling, negative-safe) + role-cards.test.tsx
  (render, emoji/name, selection highlight, click -> onSelect). 9 tests green,
  client tsc clean.

Verified live in-browser: cards (not a Select) show for a new chat; selecting
Пират binds the chat to that role end-to-end (badge + pirate reply); no pick =>
Universal; cards vanish after the first message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ghost closed this pull request 2026-06-21 14:48:39 +03:00

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#113