修复:更早历史的世代号在渲染期推进,切换项目的守卫不等 effect
- 换项目时 historyLoadTokenRef 与 projectPathRef 同一处推进,复位 effect 不再推进 - 旧读取在「切换提交完成、复位 effect 未跑」的窗口落地时立刻失效,不再合并条目或写游标 - ADR 第 1 条与决策记录说明推进时机与其安全性依据(该窗口内不会新起带旧游标的读取) Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
+9
-3
@@ -208,10 +208,18 @@ export function useDirectProjectChatController({
|
||||
*
|
||||
* 只比项目路径不够——A→B→A 之后在飞的旧 A 读取又落回同一个路径,会把新一代的加载态与游标
|
||||
* 一起改掉。世代号让任何非最新一次读取在落地时整段失效(不合并、不写游标、不关加载态)。
|
||||
*
|
||||
* 换项目的推进放在**渲染期**,与 `projectPathRef` 同一处:守卫要在切换提交之后立刻生效。
|
||||
* 放进下面的复位 effect 会晚一步——React 的 passive effect 走宏任务,promise 续体走微任务,
|
||||
* 旧读取可能在「提交完成、复位 effect 还没跑」的窗口里落地,那时世代号还是旧的,于是照常合并
|
||||
* 条目、写游标、关加载态(复位 effect 随后会清掉状态,但这仍是一次白跑的跨项目读取)。
|
||||
*/
|
||||
const historyLoadTokenRef = useRef(0);
|
||||
const projectPathRef = useRef<string | null>(projectPath);
|
||||
projectPathRef.current = projectPath;
|
||||
if (projectPathRef.current !== projectPath) {
|
||||
historyLoadTokenRef.current += 1;
|
||||
projectPathRef.current = projectPath;
|
||||
}
|
||||
const directTurnRunningRef = useRef(currentTurnRunning);
|
||||
directTurnRunningRef.current = currentTurnRunning;
|
||||
const commandInFlightRef = useRef(commandInFlight);
|
||||
@@ -227,8 +235,6 @@ export function useDirectProjectChatController({
|
||||
historyErrorRef.current = null;
|
||||
setHistoryError(null);
|
||||
historyOldestItemIdRef.current = null;
|
||||
// 换项目即换代:上一项目的在飞读取(停在旧世代号上)落地后整段失效。
|
||||
historyLoadTokenRef.current += 1;
|
||||
}, [projectPath]);
|
||||
|
||||
// 回合期间的平台会话保活由 Rust 持有:Direct 回合的占用登记时启动,占用释放(回合收口)即停止;
|
||||
|
||||
@@ -22,7 +22,7 @@ DirectProject 聊天区(`apps/ai-game-creator-shell/src/view/project-developme
|
||||
- 两个触发都不越过既有的首屏订阅锚点 `lastCompletedItemId`;一次加载仍最多连拉 5 页(口径见 [`【ADR】DirectProject对话历史单一事实源-2026-09-16`](./【ADR】DirectProject对话历史单一事实源-2026-09-16.md))。
|
||||
- 加载中在列表最上方(比最旧一条回合更靠上)挂载一行 `role="status"`、`aria-live="polite"` 的「正在加载更早的对话」,带旋转圈;延迟 150ms 才显示,加载结束即卸载。它按需挂载,靠位置补偿(见第 2 条)保证下面的消息不跳。
|
||||
- 失败:挂起自动加载,列表顶部保留一行内联错误行——`role="alert"` 只包住「加载更早对话失败」文案本身,重试是可聚焦按钮、留在 live region 之外(assertive + atomic 的 live region 里不放交互控件);**不自动重试**,只有点重试(或切换项目)才重新开始;重试成功后错误行消失。
|
||||
- 一次加载与它所属的**世代**绑定:切换项目或新起一次读取都推进世代号(`historyLoadTokenRef`),旧世代落地时整段失效——不合并条目、不写游标、不关加载态。只比项目路径不够:A→B→A 之后在飞的旧读取又落回同一个路径,原守卫放行,会把新一代的加载行与 `historyLoadingRef` 这道并发闸门一起改掉。
|
||||
- 一次加载与它所属的**世代**绑定:切换项目或新起一次读取都推进世代号(`historyLoadTokenRef`),旧世代落地时整段失效——不合并条目、不写游标、不关加载态。只比项目路径不够:A→B→A 之后在飞的旧读取又落回同一个路径,原守卫放行,会把新一代的加载行与 `historyLoadingRef` 这道并发闸门一起改掉。换项目的推进放在**渲染期**(与 `projectPathRef` 同一处),不放在复位 effect 里:passive effect 走宏任务、promise 续体走微任务,旧读取可能在「切换提交完成、复位 effect 还没跑」的窗口里落地,那时世代号还是旧的,守卫会放行。
|
||||
|
||||
### 2. 前插锚定:回合 key + 块身份 + 偏移,关掉浏览器原生锚定
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
- 验证(2026-10-02 追加,动画目标重对准):`useConversationScroll.test.tsx` 新增一例——点击回到底部后内容变高(`scrollHeight` 1200→1500)触发一次布局变化,断言动画目标从 `[1200]` 变为 `[1200, 1500]`、落到新底部后仍能交出控制权;修正前在目标数组处红。
|
||||
- 追加(2026-10-02,review:错误行的重试按钮在断言式 live region 内):`DirectProjectHistoryErrorRow` 原来把重试按钮渲染在 `<p role="alert">` 里面,而 `role="alert"` 隐含 `aria-live="assertive"` + `aria-atomic="true"`,交互控件会被卷进整段断言性播报、读屏也不一定把它当可聚焦按钮。修正:容器改为普通 `<div>`,`role="alert"` 只包住「加载更早对话失败」文案本身,重试按钮是 live region 之外的兄弟。
|
||||
- 验证(2026-10-02 追加,错误行结构):`DirectProjectConversation.test.tsx` 新增一例断言 `role="alert"` 节点不包含重试按钮、且文案仍在 alert 内并仍可点击;修正前红。
|
||||
- 追加(2026-10-02,review:世代号推进时机):`historyLoadTokenRef` 的换项目推进原来只在复位 effect 里,而 `projectPathRef.current` 是渲染期赋值的——交接窗口里守卫不再即时生效:React 的 passive effect 走宏任务、promise 续体走微任务,切换提交之后、复位 effect 之前落地的旧读取拿到的仍是旧世代号,会照常合并条目、写游标、关加载态(复位 effect 随后清掉状态,故终态没坏,但白跑一次跨项目读取且留下瞬时脏状态)。修正:推进移到渲染期,与 `projectPathRef` 同一处、只在路径真的变化时推进;复位 effect 不再推进。安全性依据:同一提交里子组件「填充视口」effect 的判据(`turns` / `historyHasMore` / `historyLoading` / `historyError` / 稳定的 `loadEarlier`)都不变,订阅状态复位本身也是 effect,因此这个窗口里不可能新起一次带旧游标的读取,去掉 effect 里的那一次推进不会放过它。
|
||||
- 验证(2026-10-02 追加,世代号推进时机):该窗口依赖 React 调度(act 会把 effect 与断言放在同一个作用域里冲掉),jsdom 下无法构造出「旧读取先于复位 effect 落地」的确定性用例,因此没有新增红灯用例;既有的控制器世代用例(切项目、A→B→A)保持全绿,行为等价性由「本窗口内不会有新读取启动」的依赖分析支撑。真机若要硬证据,需在慢读取期间切项目并观察是否多打一次跨项目读取。
|
||||
|
||||
## 2026-10-01 Web、后台与 AGC 一键联调
|
||||
|
||||
|
||||
Reference in New Issue
Block a user