[feature][ai-chat] Авто-сворачивание окна чата в шапку при фокусе на странице, разворот по клику #42

Closed
opened 2026-06-20 21:01:53 +03:00 by Ghost · 0 comments

Контекст

Плавающее окно AI-чата (AiChatWindow) перекрывает контент страницы: открыв чат и начав читать/листать страницу под ним, окно остаётся во весь рост и закрывает текст/таблицу. Свернуть можно только вручную кнопкой «—» (Minimize).

Хотим, чтобы окно само сворачивалось в свою шапку, как только пользователь переключается на страницу (кликает мимо окна — в редактор/контент), и разворачивалось обратно по клику на шапку. Сворачивание — визуальный коллапс (как нынешний Minimize), НЕ закрытие: поток ответа агента не должен прерываться (ChatThread остаётся смонтированным).

Полный разбор: docs/backlog/ai-chat-collapse-on-page-focus.md.

Что уже есть

  • Состояние minimized + toggleMinimize (кнопка «—»).
  • Визуальный коллапс в CSS (.minimized → высота шапки; .minimized .content { display: none } без размонтирования).
  • i18n-ключ "Expand" в apps/client/public/locales/en-US/translation.json (уже добавлен заранее).

Что НЕ сделано (из 6 частей плана реализована только 1)

  1. Авто-своротuseEffect со слушателем mousedown на document в capture-фазе, активный только при windowOpen && !minimized; setMinimized(true), если клик вне окна (el.contains(target)) и не внутри портала Mantine (target.closest("[data-portal]")).
  2. Разворот по клику на шапку — в startDrag различать клик и драг по порогу ≈4px; при свёрнутом окне → setMinimized(false). Нужен minimizedRef (useRef) против stale-closure в useCallback([]).
  3. setMinimized(false) при открытии окна (переход windowOpen → true), чтобы свёрнутое состояние не «залипало» между сессиями.
  4. Аффорданс/доступность .dragBar — CSS .minimized .dragBar { cursor: pointer; }; в свёрнутом виде role="button", tabIndex={0}, aria-label={t("Expand")}, обработчик Enter/Space.
  5. Чистые хелперы + тестыshouldCollapseOnOutsidePointer, isHeaderClick; component-тест (@testing-library/react) + прогон pnpm --filter client lint и test.

Файлы

  • apps/client/src/features/ai-chat/components/ai-chat-window.tsx
  • apps/client/src/features/ai-chat/components/ai-chat-window.module.css
  • apps/client/public/locales/en-US/translation.json (ключ "Expand" уже есть)

Тонкие моменты

  • Гард [data-portal] обязателен: кебаб-меню списка чатов и confirm-модалка удаления рендерятся в порталы — без гарда клик по «Rename»/«Delete» свернул бы чат в момент выбора.
  • Capture-фаза — чтобы stopPropagation со страницы не прятал событие; на клики внутри окна/порталов не влияет (их отсекают гарды).
  • Стрим не прерывается: при minimized ChatThread остаётся в DOM, ответ достреливается в фоне.
  • Автофокус композера при открытии — это фокус внутри окна, ложного немедленного сворота не вызывает.

Источник: бэклог docs/backlog/ai-chat-collapse-on-page-focus.md (статус 🔴 не сделано).

## Контекст Плавающее окно AI-чата (`AiChatWindow`) перекрывает контент страницы: открыв чат и начав читать/листать страницу под ним, окно остаётся во весь рост и закрывает текст/таблицу. Свернуть можно только вручную кнопкой «—» (Minimize). Хотим, чтобы окно **само сворачивалось в свою шапку**, как только пользователь переключается на страницу (кликает мимо окна — в редактор/контент), и **разворачивалось обратно по клику на шапку**. Сворачивание — визуальный коллапс (как нынешний Minimize), **НЕ** закрытие: поток ответа агента не должен прерываться (`ChatThread` остаётся смонтированным). Полный разбор: `docs/backlog/ai-chat-collapse-on-page-focus.md`. ## Что уже есть - Состояние `minimized` + `toggleMinimize` (кнопка «—»). - Визуальный коллапс в CSS (`.minimized` → высота шапки; `.minimized .content { display: none }` без размонтирования). - i18n-ключ `"Expand"` в `apps/client/public/locales/en-US/translation.json` (уже добавлен заранее). ## Что НЕ сделано (из 6 частей плана реализована только 1) 1. **Авто-сворот** — `useEffect` со слушателем `mousedown` на `document` в **capture-фазе**, активный только при `windowOpen && !minimized`; `setMinimized(true)`, если клик вне окна (`el.contains(target)`) и не внутри портала Mantine (`target.closest("[data-portal]")`). 2. **Разворот по клику на шапку** — в `startDrag` различать клик и драг по порогу ≈4px; при свёрнутом окне → `setMinimized(false)`. Нужен `minimizedRef` (useRef) против stale-closure в `useCallback([])`. 3. **`setMinimized(false)` при открытии** окна (переход `windowOpen → true`), чтобы свёрнутое состояние не «залипало» между сессиями. 4. **Аффорданс/доступность `.dragBar`** — CSS `.minimized .dragBar { cursor: pointer; }`; в свёрнутом виде `role="button"`, `tabIndex={0}`, `aria-label={t("Expand")}`, обработчик Enter/Space. 5. **Чистые хелперы + тесты** — `shouldCollapseOnOutsidePointer`, `isHeaderClick`; component-тест (@testing-library/react) + прогон `pnpm --filter client lint` и `test`. ## Файлы - `apps/client/src/features/ai-chat/components/ai-chat-window.tsx` - `apps/client/src/features/ai-chat/components/ai-chat-window.module.css` - `apps/client/public/locales/en-US/translation.json` (ключ `"Expand"` уже есть) ## Тонкие моменты - Гард `[data-portal]` **обязателен**: кебаб-меню списка чатов и confirm-модалка удаления рендерятся в порталы — без гарда клик по «Rename»/«Delete» свернул бы чат в момент выбора. - **Capture-фаза** — чтобы `stopPropagation` со страницы не прятал событие; на клики внутри окна/порталов не влияет (их отсекают гарды). - **Стрим не прерывается**: при `minimized` `ChatThread` остаётся в DOM, ответ достреливается в фоне. - Автофокус композера при открытии — это фокус **внутри** окна, ложного немедленного сворота не вызывает. --- Источник: бэклог `docs/backlog/ai-chat-collapse-on-page-focus.md` (статус 🔴 не сделано).
Ghost closed this issue 2026-06-21 01:36:55 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#42