From 39c540edfcf71254617573a65d575d8f94e6abc0 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:17:38 -0400 Subject: [PATCH] =?UTF-8?q?fix(chat):=20=E6=89=93=E5=BC=80=E4=B8=BB=20Agen?= =?UTF-8?q?t=20=E4=BC=9A=E8=AF=9D=E9=BB=98=E8=AE=A4=E8=B4=B4=E5=BA=95?= =?UTF-8?q?=E5=B9=B6=E5=AE=8C=E6=95=B4=E6=98=BE=E7=A4=BA=E6=9C=80=E5=90=8E?= =?UTF-8?q?=E4=B8=80=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最后一条 final answer 的完整内容是懒加载(AnswerBlock useInView+fetchDetail), 在 activity 数组稳定后才撑开高度,而滚动 layout effect 只依赖 activity 引用、 不会因高度变化重触发,导致打开后最后一条被顶到屏幕外。新增 ResizeObserver 观测 transcript 容器:用户仍贴底时,任何高度变化(transcript 挂载/详情展开/markdown 撑高) 都重新贴底;手动上滚则不再拽回。planner/worker 会话同样受益。 Claude-Session: https://claude.ai/code/session_01RVrPtpngE9T9DRJHosf6MB --- .../tasks/detail/_tabs/sessions-tab.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/web/src/app/(main)/function/tasks/detail/_tabs/sessions-tab.tsx b/web/src/app/(main)/function/tasks/detail/_tabs/sessions-tab.tsx index 1396229..b21ca81 100644 --- a/web/src/app/(main)/function/tasks/detail/_tabs/sessions-tab.tsx +++ b/web/src/app/(main)/function/tasks/detail/_tabs/sessions-tab.tsx @@ -1004,6 +1004,26 @@ export function SessionsTab({ taskId }: { taskId: string }) { const vp = viewport(); if (vp) vp.scrollTop = vp.scrollHeight; }, [activity, viewport]); + // Lazy detail loads (AnswerBlock / ToolBlock / Markdown) grow the content AFTER the + // activity array settles, WITHOUT changing its reference — so the layout effects + // above never re-fire and a freshly opened session would leave its last message + // scrolled partly off-screen (the final answer expands from a one-line summary to + // full markdown below the fold). A ResizeObserver re-pins to the bottom on any + // height change while the user is still at the bottom, so opening the main agent + // lands on the last message fully shown. contentRef's div is always mounted, so the + // observer catches the transcript mounting + each detail expanding. + // biome-ignore lint/correctness/useExhaustiveDependencies: activeId intentionally rebinds the observer to the new session's content. + React.useEffect(() => { + const el = contentRef.current; + if (!el || typeof ResizeObserver === "undefined") return; + const ro = new ResizeObserver(() => { + if (!atBottomRef.current) return; + const vp = viewport(); + if (vp) vp.scrollTop = vp.scrollHeight; + }); + ro.observe(el); + return () => ro.disconnect(); + }, [activeId, viewport]); function stop() { if (stopping) return;