diff --git a/client/src/pages/TaskDetail.tsx b/client/src/pages/TaskDetail.tsx index b5a1849..c976ad5 100644 --- a/client/src/pages/TaskDetail.tsx +++ b/client/src/pages/TaskDetail.tsx @@ -31,6 +31,21 @@ import { DeleteTaskDialog } from '@/components/task-detail/DeleteTaskDialog'; import { useTaskFieldConditions } from '@/components/task-detail/useTaskFieldConditions'; import type { FormData, FormTab, FormStatus, FormTransition, TaskAssigneeEntry, TabModuleInfo, TaskDetailResponse, OrgRole } from '@/components/task-detail/types'; +// Компонент чата (без заголовка — он вынесен в полосу вкладок). +// ВАЖНО: объявлен на module level, а не inline в теле TaskDetail — компонент, +// объявленный внутри другого компонента, получает новый тип на каждый рендер, +// и React полностью перемонтирует чат (сброс скролла и состояния) при ЛЮБОМ +// ре-рендере TaskDetail. Именно этот remount и был первопричиной «дёрганья» чата. +function ChatSection({ className = '', taskId }: { className?: string; taskId: number }) { + return ( +
+
+ +
+
+ ); +} + const TaskDetail = () => { const { id, taskId } = useParams<{ id: string; taskId: string }>(); const [, setLocation] = useLocation(); @@ -575,20 +590,11 @@ const TaskDetail = () => { formNamesMap[f.id] = f.name; }); - // Компонент чата (без заголовка — он вынесен в полосу вкладок). - // ВАЖНО: обязательно useCallback со стабильной идентичностью — компонент, - // объявленный inline в теле рендера, получает НОВЫЙ тип на каждый рендер, - // и React полностью перемонтирует чат (сброс скролла и состояния) при ЛЮБОМ - // ре-рендере TaskDetail (SSE-событие, refetch задачи, возврат на вкладку). - // Именно этот remount и был первопричиной «дёрганья» чата. Тот же паттерн - // уже исправлен для мобильного accordion (см. комментарий ниже). - const ChatSection = useCallback(({ className = '' }: { className?: string }) => ( -
-
- -
-
- ), [taskIdNumber]); + // Компонент чата вынесен на module level (см. объявление ChatSection выше) — + // объявленный inline в теле рендера компонент получает НОВЫЙ тип на каждый + // рендер, и React полностью перемонтирует чат (сброс скролла и состояния) + // при ЛЮБОМ ре-рендере TaskDetail. Именно этот remount и был первопричиной + // «дёрганья» чата. Тот же паттерн уже исправлен для мобильного accordion. const taskContent = ( { {chatLayout === 'left' && ( <> {chatEnabled && expandedPane !== 'task' && ( - + )} {expandedPane !== 'chat' && (
@@ -780,7 +786,7 @@ const TaskDetail = () => { {chatLayout === 'tab' && ( <> {selectedTabId === 'chat' && chatEnabled ? ( - + ) : (
{isMobile ? mobileTaskAccordion : taskContent}