From 95a32a316f730351caabafb39d001db4f6928ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 2 Oct 2026 19:08:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E6=8D=A2=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E6=97=B6=E5=A4=8D=E4=BD=8D=20DirectProject=20?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E6=89=80=E6=9C=89=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ADR 新增第 6 条:会话身份作为显式信号复位跟随最新/锚点/胶囊状态,并记录「key 重建列表」的取舍 - 决策记录追加换会话复位的症状、决策与验证口径 Co-authored-by: Junie --- ...ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md | 9 ++++++++- docs/project-memory/shared-memory/decision-log.md | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md b/docs/adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md index 2727e9a24..91040f899 100644 --- a/docs/adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md +++ b/docs/adr/【ADR】DirectProject对话滚动与历史自动加载-2026-10-02.md @@ -52,6 +52,12 @@ DirectProject 聊天区(`apps/ai-game-creator-shell/src/view/project-developme - 控制器对渲染层暴露可渲染的 `historyLoading` 与 `historyError`(外加 `retryEarlierHistory`),取代只存在于 ref 里的加载标志。 - 新增元素全部用内联 Tailwind 工具类,不改 `styles.css`;列表本身仍保持 `message-list project-chat-message-list` 类名不变,只追加 `overflow-anchor` 工具类。 +### 6. 换会话时复位滚动所有权 + +- 列表容器在切项目时**不会重挂载**:`DirectProjectChatView` 在 `App.tsx` 只有一处渲染、没有 `key`,内部的 `DirectProjectConversation` 也没有 `key`,而 `useConversationScroll` 的跟随最新 / `atBottom` / `hasNewReply` / 前插锚点 / 折叠头 / 程序化滚动标记只在挂载时初始化一次。控制器自己按项目重置了历史状态,滚动所有权却一直漏着。 +- 症状:在项目 A 往上滚过再切到 B——① B 的首屏不贴底,用户得自己往下滚;② B 的第一批回合会在「不跟随」分支被算成新内容,胶囊在新项目上直接显示「有新回复 · 回到底部」,可用户根本没在 B 里离开过底部。 +- 决策:把会话身份(`conversationKey`,DirectProject 传项目路径)作为**显式信号**传给 `useConversationScroll`;身份变化时复位上述滚动所有权状态、把终态指纹同步成新会话内容、再贴底。不改变组件生命周期,同一个项目重开(身份不变)也不会被当成新会话;将来策划对话复用同一个 hook 时用的是同一套信号。 + ## 备选方案与取舍 1. **保留按钮 + 只加自动加载**:加载中仍靠按钮做唯一反馈,且删掉按钮后失败路径没有补救入口;按钮本身与滚动自动加载重复。 @@ -60,11 +66,12 @@ DirectProject 聊天区(`apps/ai-game-creator-shell/src/view/project-developme 4. **展开后总是把正文滚进视口**:对正文比视口矮的折叠块会把画面大幅上移,打断正在读历史的用户;采用「跟随时贴底 / 否则冻结折叠头 + 只滚到刚好露出新展开正文的最小位移」。 5. **平滑滚动期间照常处理滚动事件与布局补偿**:程序化滚动会被应用自己的补偿打断(第一次写 `scrollTop` 即取消动画),用户点了「回到底部」也停在半路;因此改为滚动期间冻结这两条路径。 6. **自动重试失败的历史加载**:弱网下会反复打接口,且用户看不出到底在重试还是在挂起;改为挂起 + 内联重试行。 +7. **用 `key={projectPath}` 让 React 重建会话列表**:改动最小,但整份消息列表连同加载行的 150ms 延迟计时一起重建,而且「同一个项目重开」也会被当成新会话;改用显式身份信号复位,重建与否与「换没换会话」解耦。 ## 影响 - 历史加载失败不再只写顶部状态行,而是落到列表里的内联错误行;顶部状态行仍保留首屏读取失败等其它用途。 - 滚动是表现层行为,正式状态仍在后端投影与运行态事件;本 ADR 不新增领域概念。 - 阈值(触顶 24px、贴底 48px、spinner 150ms)是可按手感调整的常量,集中放在 `components/DirectProjectConversation/conversationScrollPolicy.ts`。 -- 验收:纯函数与 jsdom 组件测试覆盖阈值、锚点还原、加载/错误行、胶囊文案与显隐;滚动观感(顶部加载圈、胶囊出现与消失、底部展开回贴、历史前插不跳)必须真机手动验收——jsdom 没有布局。 +- 验收:纯函数与 jsdom 组件测试覆盖阈值、锚点还原、加载/错误行、胶囊文案与显隐;滚动观感(顶部加载圈、胶囊出现与消失、底部展开回贴、历史前插不跳、切项目后首屏贴底且不误报「有新回复」)必须真机手动验收——jsdom 没有布局。 - 明确的后续项(不在本次范围):`PlanningChatView` 与 `App.tsx` 遗留 `message-history-more` 路径的同款改造、未读条数徽标、Playwright 端到端。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index fddbd7a79..e2b31bbd3 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -8,6 +8,8 @@ - 验证:新增 37 条与实现同目录的用例(阈值与加载门、锚点读取/还原、折叠头冻结与正文对齐、hook 的触顶/填充/胶囊/未读/延迟加载行、组件层的按钮移除与内联重试行)由根 `vitest.config.ts` 的 include 收进门禁;`npm run ai-game-creator-shell:typecheck`、`eslint`(含新文件)、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 通过;滚动观感(顶部加载圈、胶囊显隐、底部展开回贴、历史前插不跳)留真机手动验收。 - 追加(2026-10-02,两个真机缺陷的根因与修正):①「点回到底部只下去一屏、到不了底」——平滑动画期间滚动事件把「跟随最新」翻成假,布局补偿与锚点还原接着写 `scrollTop`,而真实浏览器里任何一次写都会取消正在跑的平滑动画;现在滚动位置由这次程序化滚动独占(滚动事件只在贴底时才结算并交还,补偿整段跳过),滚轮 / 触摸 / 键盘接手立刻交还,避免标记永远挂着。②「展开折叠块没有自动滚动到位」——旧规则只在展开正文比视口还高时才动,正文矮的折叠块(工具组、思考块)展开后正文仍在视口外;现在冻结折叠头之后统一补「刚好露出新展开正文」的最小位移(底边超出就补超出量,比视口还高则对齐正文顶边),两段位移合成一次写。落在 `conversationToggleReveal.ts`(新增纯函数 `toggleRevealDelta`)与 `useConversationScroll.ts`(新增 `programmaticScrollRef`)。 - 验证(2026-10-02 追加):同目录用例补齐到 48 条(新增 `toggleRevealDelta` 六例、hook 的四例程序化滚动用例,其中两例在修正前确实红)全部通过;Chromium 真机脚本复验三处——点回到底部收敛到 `scrollHeight - clientHeight`、底部展开贴到新底、视口外展开补 269px 后正文底边正好贴视口下缘。 +- 追加(2026-10-02,换会话复位滚动所有权):`useConversationScroll` 的跟随最新 / `atBottom` / `hasNewReply` / 前插锚点 / 折叠头 / 程序化滚动标记只在挂载时初始化一次,而 `DirectProjectChatView` 切项目时不重挂载——在项目 A 往上滚过再切 B,B 首屏不贴底且胶囊直接显示「有新回复 · 回到底部」。现在把会话身份(`conversationKey`,DirectProject 传项目路径)作为显式信号传进 hook,身份变化即复位这批状态、同步终态指纹并重新贴底;不改成 `key` 重建列表,避免消息列表重建与加载行 150ms 延迟计时重来,同一个项目重开也不算换会话。 +- 验证(2026-10-02 追加,换会话复位):`useConversationScroll` 新增两例换会话用例(新会话首屏贴底且不出现胶囊、切会话后在 B 里往上滚只显示「回到底部」而不是「有新回复」),修正前确实红、修正后绿;chat 范围用例全绿。 ## 2026-10-01 Web、后台与 AGC 一键联调