feat(ai-chat): auto-open last chat bound to the document (#191) #209

Merged
vvzvlad merged 2 commits from feat/191-chat-doc-binding into develop 2026-06-27 02:56:32 +03:00

Closes #191

Что сделано

При открытии плавающего окна ИИ-чата из шапки на странице документа теперь автоматически открывается последний чат, привязанный к этому документу. Привязка переиспользует существующее поле ai_chats.page_id (без миграции): «привязанный чат» = самый свежий непросмотренный чат пользователя, созданный на этой странице — поэтому новый чат на странице автоматически становится привязанным.

Сервер (read-only)

  • AiChatRepo.findLatestByPage(creatorId, workspaceId, pageId) — самый свежий по createdAt (тай-брейк по id), deletedAt IS NULL, скоуп user+workspace.
  • POST /ai-chat/bound-chat (BoundChatDto) → { chatId: string | null }. Доступа к странице не проверяем: матчатся только СВОИ чаты, чужой pageId ничего не раскрывает.

Клиент

  • getBoundChat(pageId) в сервисе.
  • Хук useOpenAiChatForCurrentPage — резолв только на реальном переходе закрыто→открыто; вне страницы сохраняет текущий выбор; fail-soft → свежий чат; драфт/роль чистятся только при реальной смене чата.
  • Подключён в app-header.tsx вместо прямого тоггла. Бейдж авторства (ai-agent-badge.tsx) и окно (ai-chat-window.tsx) не трогались — диплинк идёт в обход привязки.

Тесты

  • Сервер (jest): findLatestByPage (скоуп/фильтры/ордеринг/undefined); контроллер bound-chat (id владельца, null для чужого/несуществующего pageId).
  • Клиент (vitest): хук — резолв на странице, fresh при null, off-page сохраняет выбор, гард при открытом окне, чистка драфта только при смене, fail-soft, чистка роли.

Проверки (зелёные)

  • pnpm --filter server exec tsc --noEmit -p tsconfig.json — OK
  • jest (2 сьюта, 5 тестов) — OK
  • pnpm --filter client exec tsc -b — OK
  • vitest (новый хук 7 тестов + регресс ai-agent-badge 8 тестов) — OK

Без миграции. Без node_modules в коммите.

🤖 Generated with Claude Code

Closes #191 ## Что сделано При открытии плавающего окна ИИ-чата из шапки на странице документа теперь автоматически открывается последний чат, привязанный к этому документу. Привязка переиспользует существующее поле `ai_chats.page_id` (без миграции): «привязанный чат» = самый свежий непросмотренный чат пользователя, созданный на этой странице — поэтому новый чат на странице автоматически становится привязанным. ### Сервер (read-only) - `AiChatRepo.findLatestByPage(creatorId, workspaceId, pageId)` — самый свежий по `createdAt` (тай-брейк по `id`), `deletedAt IS NULL`, скоуп user+workspace. - `POST /ai-chat/bound-chat` (`BoundChatDto`) → `{ chatId: string | null }`. Доступа к странице не проверяем: матчатся только СВОИ чаты, чужой `pageId` ничего не раскрывает. ### Клиент - `getBoundChat(pageId)` в сервисе. - Хук `useOpenAiChatForCurrentPage` — резолв только на реальном переходе закрыто→открыто; вне страницы сохраняет текущий выбор; fail-soft → свежий чат; драфт/роль чистятся только при реальной смене чата. - Подключён в `app-header.tsx` вместо прямого тоггла. Бейдж авторства (`ai-agent-badge.tsx`) и окно (`ai-chat-window.tsx`) не трогались — диплинк идёт в обход привязки. ## Тесты - Сервер (jest): `findLatestByPage` (скоуп/фильтры/ордеринг/undefined); контроллер `bound-chat` (id владельца, `null` для чужого/несуществующего pageId). - Клиент (vitest): хук — резолв на странице, fresh при null, off-page сохраняет выбор, гард при открытом окне, чистка драфта только при смене, fail-soft, чистка роли. ## Проверки (зелёные) - `pnpm --filter server exec tsc --noEmit -p tsconfig.json` — OK - jest (2 сьюта, 5 тестов) — OK - `pnpm --filter client exec tsc -b` — OK - vitest (новый хук 7 тестов + регресс ai-agent-badge 8 тестов) — OK Без миграции. Без node_modules в коммите. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-06-26 06:20:25 +03:00
On opening the floating AI-chat window from the header on a document page,
auto-open the LAST chat bound to that document. Binding reuses the existing
ai_chats.page_id (no migration): the bound chat is the requesting user's
most-recent non-deleted chat created on that page, so a new chat on the page
becomes the bound one for free. Resolution happens only on a genuine
closed -> open transition; the provenance badge deep-link is untouched.

Server: AiChatRepo.findLatestByPage + POST /ai-chat/bound-chat (BoundChatDto),
both read-only and owner/workspace-scoped.
Client: getBoundChat service + useOpenAiChatForCurrentPage hook wired into the
app-header entry point (fail-soft to a fresh chat; draft/role cleared only on a
real switch).
Tests: repo scoping/ordering, controller wiring, and hook behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vvzvlad added the feature label 2026-06-26 15:49:30 +03:00
Owner

Code review — PR #209: авто-открытие последнего чата, привязанного к документу (#191)

Вердикт: Request changes. Логика корректна и хорошо покрыта тестами, но есть один must-fix (отсутствует запись в CHANGELOG для пользовательского изменения поведения — нарушает строгую конвенцию репозитория) и заметная UX-регрессия задержки открытия окна, которую стоит починить до мержа.

Объём: дифф developfeat/191-chat-doc-binding (merge-base 3ddc329b), 9 файлов, +401/−4. Прогнаны параллельные аспектные ревьюеры (security, stability, conventions, documentation, regressions, test-coverage, simplification, architecture) + judge-проход.

Must fix before merge

  • [documentation] Добавить запись в CHANGELOG под [Unreleased]/### Added про авто-открытие привязанного чата (#191)CHANGELOG.md:11-13
    Репозиторий строго ведёт CHANGELOG по фичам: секция [Unreleased]/### Added уже документирует каждую сопоставимую пользовательскую AI-chat фичу по номеру issue (#183, #168, #180, #175, #166), и недавние мержи стабильно трогают CHANGELOG. PR меняет поведение видимое пользователю — кнопка AI-чата в шапке теперь не просто тоглит окно, а авто-открывает последний чат, привязанный к текущему документу, — но записи нет; релиз-ноты рассинхронизированы с поведением. Подтверждено: CHANGELOG.md отсутствует в диффе. Fix: добавить буллет в ### Added (или ### Changed) секции [Unreleased] в стиле существующих AI-chat записей со ссылкой (#191) — привязка через существующий ai_chats.page_id (без миграции), побеждает самый свежий собственный чат, fail-soft на свежий чат.

  • [warning][regressions] Открывать окно до await getBoundChat, чтобы первый клик оставался мгновеннымapps/client/src/features/ai-chat/hooks/use-open-ai-chat.ts:30-58
    Раньше кнопка в шапке открывала плавающее окно синхронно (чистая запись в атом, без I/O). Теперь setWindowOpen(true) стоит ПОСЛЕ resolved = await getBoundChat(pageId), поэтому на странице окно не появляется до возврата ответа POST /ai-chat/bound-chat. На медленном соединении пользователь жмёт кнопку и ничего не происходит на время round-trip — читается как зависший контрол; catch ловит ошибки, но не латентность. Fix: вызвать setWindowOpen(true) заранее (до блока if (pageId)), затем дождаться getBoundChat и применить переключение чата (setActiveChatId/setDraft/setSelectedRoleId) уже после резолва.

  • [suggestion][simplification] Убрать избыточный setWindowOpen(true) в ветке «окно уже открыто»apps/client/src/features/ai-chat/hooks/use-open-ai-chat.ts:31-35
    aiChatWindowOpenAtom — обычный boolean-атом; ветка выполняется только когда windowOpen уже true, так что setWindowOpen(true) пишет то же значение и является no-op. Хук не импортирует локальный minimized-state окна, так что развернуть свёрнутое окно этот вызов тоже не может — единственный эффект ветки — ранний return. Fix: заменить if (windowOpen) { setWindowOpen(true); return; } на if (windowOpen) return;; финальный setWindowOpen(true) сохраняет использование сеттера, чистки не требуется.

  • [suggestion][regressions] Подтвердить намеренность отказа от тогл-закрытия кнопкой в шапкеapps/client/src/components/layouts/global/app-header.tsx:108
    Старая кнопка тоглила окно — повторный клик при открытом окне закрывал его. Новый openAiChat только выставляет windowOpen=true (ранний return в ветке «уже открыто» не закрывает), так что иконка в шапке больше не закрывает чат. Закрытие остаётся доступным через крестик окна (ai-chat-window.tsx:547), тупика нет, но это тихое изменение UX для тех, кто использовал иконку как тогл. Fix: если тогл-закрытие было фичей — закрывать окно в ранней ветке, когда windowOpen уже true; иначе оставить как есть и отметить намеренность в описании PR.

  • [suggestion][simplification] (опц.) Вынести дублированный резолв current-page-id в общий хелперapps/client/src/features/ai-chat/hooks/use-open-ai-chat.ts:23-25
    Пара useMatch("/s/:spaceSlug/p/:pageSlug") + extractPageSlugId(match?.params?.pageSlug) с многострочным комментарием скопирована дословно из ai-chat-window.tsx (оба сайта в pathless layout-роуте, где useParams() не видит :pageSlug); комментарий хука прямо говорит «Same route-match trick the window uses». Мелкая опциональная чистка (по коду extractPageSlugId повторяется ~25 раз через useParams(), паттерн не стандартизирован). Fix: маленький useCurrentPageId() в apps/client/src/features/ai-chat/hooks/, вызывать из обоих мест.

Test coverage

Вся новая логика покрыта. Клиентский хук use-open-ai-chat.test.tsx (135 строк) покрывает: резолв привязанного чата и переключение, отсутствие привязанного чата (fresh/null), off-page (без резолва), повторный клик при открытом окне (без переключения), сохранение драфта при совпадении чата, fail-soft на ошибку резолва, сброс выбранной роли при реальном переключении. Серверная сторона покрыта ai-chat.repo.spec.ts (findLatestByPage) и ai-chat.controller.bound-chat.spec.ts.

Architecture & design (forward-looking, non-blocking)

  • Четвёртая точка «открыть чат + переключить активный чат» через прямую композицию атомовapps/client/src/features/ai-chat/{hooks/use-open-ai-chat.ts, ai-chat-window.tsx, ui/ai-agent-badge.tsx}
    Инвариант переключения (setActiveChatId + setDraft("") + setSelectedRoleId(null), с опорой на render-фазовый reconciler use-chat-session.ts) теперь переизобретён в четырёх сайтах, каждый со своим подмножеством эффектов: selectChat (полный набор + cancelPendingAdoption), startNewChat (то же с null), ai-agent-badge.tsx openChat (без setSelectedRoleId(null)), и новый хук (полный набор, под guard resolved !== activeChatId). Пропущенный сброс роли в badge — конкретный симптом дрейфа. Новый хук корректен; проблема forward-looking: будущие правки семантики переключения нужно помнить в до четырёх местах.
    вынести единый openChatForPage/switchToChat (общий хук), через который идут все четыре сайта (включая badge)** (effort: m). Pros: одно определение «open + switch» (атом-записи + guard + сброс роли/драфта), убирает тихий дрейф badge, новая #191-логика становится тонким вызовом, будущие изменения семантики — в одном месте. Cons: selectChat/startNewChat дополнительно зовут cancelPendingAdoption, владелец которого — useChatSession внутри окна и недоступен из standalone-хука, так что чистая абстракция требует, чтобы disarm шёл через публичный атом-путь (reconciler уже обнуляет pendingNewChatRef при реальном расхождении); in-window вызывающие сохранят свой cancelPendingAdoption. Нужна аккуратность, чтобы честно сохранить различие in-window/out-of-window.

  • Семантика и индексация ai_chats.page_idapps/server/src/database/repos/ai-chat/ai-chat.repo.ts (findLatestByPage); миграция 20260622T120000-ai-chat-page-origin.ts; индекс 20260409T132415-ai-chat.ts
    Колонка page_id введена в 20260622T120000 явно как «Informational provenance shown in the chat-history list» — намеренно не несущая, без индекса, ON DELETE SET NULL. PR превращает её в реальный query-ключ: findLatestByPage фильтрует по (creator_id, workspace_id, page_id) + ORDER BY created_at DESC, id DESC LIMIT 1 при каждом открытии окна на странице. Единственный индекс idx_ai_chats_workspace_creator на (workspace_id, creator_id, id) не покрывает этот предикат/сортировку; page_id не проиндексирован. Корректность delete/move уже верна (hard-delete страницы → page_id null → привязка fail-soft отпадает; move сохраняет id → привязка живёт). Проблема: задокументированный контракт «informational» теперь противоречит использованию, а паттерн доступа не объявлен на уровне БД.
    схему оставить, но обновить документацию миграции/колонки, признав, что page_id теперь ещё и lookup-ключ (убрать формулировку «informational only»)** (effort: s). Pros: ноль стоимости записи, убирает рассинхрон контракта/использования, следующий читатель не введён в заблуждение. Cons: индекса нет, паттерн доступа остаётся необъявленным на уровне БД, опирается на малый размер таблицы.

## Code review — PR #209: авто-открытие последнего чата, привязанного к документу (#191) **Вердикт: Request changes.** Логика корректна и хорошо покрыта тестами, но есть один must-fix (отсутствует запись в CHANGELOG для пользовательского изменения поведения — нарушает строгую конвенцию репозитория) и заметная UX-регрессия задержки открытия окна, которую стоит починить до мержа. _Объём: дифф `develop`…`feat/191-chat-doc-binding` (merge-base `3ddc329b`), 9 файлов, +401/−4. Прогнаны параллельные аспектные ревьюеры (security, stability, conventions, documentation, regressions, test-coverage, simplification, architecture) + judge-проход._ ### Must fix before merge - **[documentation] Добавить запись в CHANGELOG под `[Unreleased]`/`### Added` про авто-открытие привязанного чата (#191)** — `CHANGELOG.md:11-13` Репозиторий строго ведёт CHANGELOG по фичам: секция `[Unreleased]`/`### Added` уже документирует каждую сопоставимую пользовательскую AI-chat фичу по номеру issue (#183, #168, #180, #175, #166), и недавние мержи стабильно трогают CHANGELOG. PR меняет поведение видимое пользователю — кнопка AI-чата в шапке теперь не просто тоглит окно, а авто-открывает последний чат, привязанный к текущему документу, — но записи нет; релиз-ноты рассинхронизированы с поведением. Подтверждено: `CHANGELOG.md` отсутствует в диффе. Fix: добавить буллет в `### Added` (или `### Changed`) секции `[Unreleased]` в стиле существующих AI-chat записей со ссылкой `(#191)` — привязка через существующий `ai_chats.page_id` (без миграции), побеждает самый свежий собственный чат, fail-soft на свежий чат. - **[warning][regressions] Открывать окно до `await getBoundChat`, чтобы первый клик оставался мгновенным** — `apps/client/src/features/ai-chat/hooks/use-open-ai-chat.ts:30-58` Раньше кнопка в шапке открывала плавающее окно синхронно (чистая запись в атом, без I/O). Теперь `setWindowOpen(true)` стоит ПОСЛЕ `resolved = await getBoundChat(pageId)`, поэтому на странице окно не появляется до возврата ответа `POST /ai-chat/bound-chat`. На медленном соединении пользователь жмёт кнопку и ничего не происходит на время round-trip — читается как зависший контрол; `catch` ловит ошибки, но не латентность. Fix: вызвать `setWindowOpen(true)` заранее (до блока `if (pageId)`), затем дождаться `getBoundChat` и применить переключение чата (`setActiveChatId`/`setDraft`/`setSelectedRoleId`) уже после резолва. - **[suggestion][simplification] Убрать избыточный `setWindowOpen(true)` в ветке «окно уже открыто»** — `apps/client/src/features/ai-chat/hooks/use-open-ai-chat.ts:31-35` `aiChatWindowOpenAtom` — обычный boolean-атом; ветка выполняется только когда `windowOpen` уже `true`, так что `setWindowOpen(true)` пишет то же значение и является no-op. Хук не импортирует локальный `minimized`-state окна, так что развернуть свёрнутое окно этот вызов тоже не может — единственный эффект ветки — ранний `return`. Fix: заменить `if (windowOpen) { setWindowOpen(true); return; }` на `if (windowOpen) return;`; финальный `setWindowOpen(true)` сохраняет использование сеттера, чистки не требуется. - **[suggestion][regressions] Подтвердить намеренность отказа от тогл-закрытия кнопкой в шапке** — `apps/client/src/components/layouts/global/app-header.tsx:108` Старая кнопка тоглила окно — повторный клик при открытом окне закрывал его. Новый `openAiChat` только выставляет `windowOpen=true` (ранний `return` в ветке «уже открыто» не закрывает), так что иконка в шапке больше не закрывает чат. Закрытие остаётся доступным через крестик окна (`ai-chat-window.tsx:547`), тупика нет, но это тихое изменение UX для тех, кто использовал иконку как тогл. Fix: если тогл-закрытие было фичей — закрывать окно в ранней ветке, когда `windowOpen` уже `true`; иначе оставить как есть и отметить намеренность в описании PR. - **[suggestion][simplification] (опц.) Вынести дублированный резолв current-page-id в общий хелпер** — `apps/client/src/features/ai-chat/hooks/use-open-ai-chat.ts:23-25` Пара `useMatch("/s/:spaceSlug/p/:pageSlug")` + `extractPageSlugId(match?.params?.pageSlug)` с многострочным комментарием скопирована дословно из `ai-chat-window.tsx` (оба сайта в pathless layout-роуте, где `useParams()` не видит `:pageSlug`); комментарий хука прямо говорит «Same route-match trick the window uses». Мелкая опциональная чистка (по коду `extractPageSlugId` повторяется ~25 раз через `useParams()`, паттерн не стандартизирован). Fix: маленький `useCurrentPageId()` в `apps/client/src/features/ai-chat/hooks/`, вызывать из обоих мест. ### Test coverage Вся новая логика покрыта. Клиентский хук `use-open-ai-chat.test.tsx` (135 строк) покрывает: резолв привязанного чата и переключение, отсутствие привязанного чата (fresh/null), off-page (без резолва), повторный клик при открытом окне (без переключения), сохранение драфта при совпадении чата, fail-soft на ошибку резолва, сброс выбранной роли при реальном переключении. Серверная сторона покрыта `ai-chat.repo.spec.ts` (findLatestByPage) и `ai-chat.controller.bound-chat.spec.ts`. ### Architecture & design (forward-looking, non-blocking) - **Четвёртая точка «открыть чат + переключить активный чат» через прямую композицию атомов** — `apps/client/src/features/ai-chat/{hooks/use-open-ai-chat.ts, ai-chat-window.tsx, ui/ai-agent-badge.tsx}` Инвариант переключения (`setActiveChatId` + `setDraft("")` + `setSelectedRoleId(null)`, с опорой на render-фазовый reconciler `use-chat-session.ts`) теперь переизобретён в четырёх сайтах, каждый со своим подмножеством эффектов: `selectChat` (полный набор + `cancelPendingAdoption`), `startNewChat` (то же с null), `ai-agent-badge.tsx` `openChat` (без `setSelectedRoleId(null)`), и новый хук (полный набор, под guard `resolved !== activeChatId`). Пропущенный сброс роли в badge — конкретный симптом дрейфа. Новый хук корректен; проблема forward-looking: будущие правки семантики переключения нужно помнить в до четырёх местах. вынести единый `openChatForPage`/`switchToChat` (общий хук), через который идут все четыре сайта (включая badge)** (effort: m). Pros: одно определение «open + switch» (атом-записи + guard + сброс роли/драфта), убирает тихий дрейф badge, новая #191-логика становится тонким вызовом, будущие изменения семантики — в одном месте. Cons: `selectChat`/`startNewChat` дополнительно зовут `cancelPendingAdoption`, владелец которого — `useChatSession` внутри окна и недоступен из standalone-хука, так что чистая абстракция требует, чтобы disarm шёл через публичный атом-путь (reconciler уже обнуляет `pendingNewChatRef` при реальном расхождении); in-window вызывающие сохранят свой `cancelPendingAdoption`. Нужна аккуратность, чтобы честно сохранить различие in-window/out-of-window. - **Семантика и индексация `ai_chats.page_id`** — `apps/server/src/database/repos/ai-chat/ai-chat.repo.ts (findLatestByPage)`; миграция `20260622T120000-ai-chat-page-origin.ts`; индекс `20260409T132415-ai-chat.ts` Колонка `page_id` введена в `20260622T120000` явно как «Informational provenance shown in the chat-history list» — намеренно не несущая, без индекса, `ON DELETE SET NULL`. PR превращает её в реальный query-ключ: `findLatestByPage` фильтрует по `(creator_id, workspace_id, page_id)` + `ORDER BY created_at DESC, id DESC LIMIT 1` при каждом открытии окна на странице. Единственный индекс `idx_ai_chats_workspace_creator` на `(workspace_id, creator_id, id)` не покрывает этот предикат/сортировку; `page_id` не проиндексирован. Корректность delete/move уже верна (hard-delete страницы → `page_id` null → привязка fail-soft отпадает; move сохраняет id → привязка живёт). Проблема: задокументированный контракт «informational» теперь противоречит использованию, а паттерн доступа не объявлен на уровне БД. схему оставить, но обновить документацию миграции/колонки, признав, что `page_id` теперь ещё и lookup-ключ (убрать формулировку «informational only»)** (effort: s). Pros: ноль стоимости записи, убирает рассинхрон контракта/использования, следующий читатель не введён в заблуждение. Cons: индекса нет, паттерн доступа остаётся необъявленным на уровне БД, опирается на малый размер таблицы.
Ghost added 1 commit 2026-06-26 17:17:36 +03:00
Address PR #209 review.

- use-open-ai-chat.ts: call setWindowOpen(true) before awaiting
  getBoundChat so the header button feels instant on slow connections;
  the chat switch (setActiveChatId/setDraft/setSelectedRoleId) is applied
  after the round-trip resolves. Also drop the redundant no-op
  setWindowOpen(true) in the already-open branch (bare early return).
- CHANGELOG.md: document the header AI-chat button auto-opening the
  latest chat bound to the current document under [Unreleased]/Added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner

Code review (re-review) — PR #209: AI chat — кнопка в хедере авто-открывает чат, привязанный к документу (#191)

Вердикт: Approve. Прошлый блокер (отсутствующая запись в CHANGELOG для #191) закрыт; дельта — корректный рефактор порядка открытия окна без новых блокеров, регрессий и проблем авторизации.

Ре-ревью дельты 908b993b..99e4afdb (2 файлов, +16/−7). Аспекты: stability, conventions, documentation, test-coverage (параллельные ревьюеры + judge).

Статус прошлых блокеров

  • Запись в CHANGELOG для #191 — закрыт. В CHANGELOG.md (секция Added) добавлен пункт «AI chat: header button auto-opens the chat bound to the current document» с описанием поведения и ссылкой (#191). Запись по существу: описаны выбор новейшего привязанного чата, переиспользование ai_chats.page_id без миграции и fail-soft на свежий чат.

Must fix before merge

Нет.

Non-blocking

Нет.

Test coverage

Покрыто (логически без регрессии). Поведенческая логика дельты не изменилась — setWindowOpen(true) лишь перенесён до round-trip getBoundChat, чтобы окно открывалось мгновенно; ветки разрешения чата (pageId есть/нет, bound/null, catch → fail-soft, переключение только при resolved !== activeChatId) идентичны прежним. Early-return if (windowOpen) return; эквивалентен прежнему setWindowOpen(true); return; (атом уже true). Новой логики, требующей отдельных тестов, дельта не вносит.

## Code review (re-review) — PR #209: AI chat — кнопка в хедере авто-открывает чат, привязанный к документу (#191) **Вердикт: Approve.** Прошлый блокер (отсутствующая запись в CHANGELOG для #191) закрыт; дельта — корректный рефактор порядка открытия окна без новых блокеров, регрессий и проблем авторизации. _Ре-ревью дельты `908b993b..99e4afdb` (2 файлов, +16/−7). Аспекты: stability, conventions, documentation, test-coverage (параллельные ревьюеры + judge)._ ### Статус прошлых блокеров - **Запись в CHANGELOG для #191 — закрыт.** В `CHANGELOG.md` (секция `Added`) добавлен пункт «AI chat: header button auto-opens the chat bound to the current document» с описанием поведения и ссылкой `(#191)`. Запись по существу: описаны выбор новейшего привязанного чата, переиспользование `ai_chats.page_id` без миграции и fail-soft на свежий чат. ### Must fix before merge Нет. ### Non-blocking Нет. ### Test coverage Покрыто (логически без регрессии). Поведенческая логика дельты не изменилась — `setWindowOpen(true)` лишь перенесён до round-trip `getBoundChat`, чтобы окно открывалось мгновенно; ветки разрешения чата (`pageId` есть/нет, bound/`null`, `catch` → fail-soft, переключение только при `resolved !== activeChatId`) идентичны прежним. Early-return `if (windowOpen) return;` эквивалентен прежнему `setWindowOpen(true); return;` (атом уже `true`). Новой логики, требующей отдельных тестов, дельта не вносит.
Ghost force-pushed feat/191-chat-doc-binding from 99e4afdb8c to 669742832a 2026-06-26 20:39:05 +03:00 Compare
Ghost force-pushed feat/191-chat-doc-binding from 669742832a to c64d7f315e 2026-06-26 21:03:47 +03:00 Compare
vvzvlad merged commit 4a3819373d into develop 2026-06-27 02:56:32 +03:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#209