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}