Fix/unfinished turn #463
Reference in New Issue
Block a user
Delete Branch "fix/unfinished-turn"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
close #458
1 [performance · low]
DirectProjectChatView的回合投影没缓存,下游 memo 形同虚设 —— 已修(c8a8b0b0b)DirectProjectChatView.tsx每次渲染直接调buildDirectChatTurns({...})得到directTurns;useDirectProjectTurnStatus里是useMemo(..., [turnRunning, turnBusy, turns])。directTurns每次渲染都是新数组引用,turns判据永远在变,那层useMemo永远命中不了缓存。不是功能错误,但是死代码,而且会让读代码的人误以为turnStatus引用稳定。directTurns自己加useMemo,判据[directEntries, localMessages, directTurnRunning, pendingUserItemId](正好是投影的全部入参,四个都是 primitive 或 state 引用),并在注释里写明为什么不能省。没有采纳备选「删掉 hook 里的 memo」:该省的本来就是整份投影的重算。tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit、ESLint、Prettier 通过;appSurface.test.ts203 passed / 13 skipped。2 [test · medium] 新增的窗口期用例可能没等 invoke 发出 —— 已修(
67d2f0842)tests/appSurface/chat-composer.suite.ts的does not report a finished turn while the host has not acknowledged the send yet只写了pending[0]?.resolve('回复'),没等chat_with_game_creator_direct_codex被调用;同文件其它用到pending的用例都先waitFor等 invoke。ensureConversationWriteAllowed)才 invoke,pending[0]可能还不存在;可选链把收尾的 resolve 变成空操作,于是「发送压根没到宿主」的用例也会绿,它声称覆盖的「已发出、宿主未确认」窗口并没被真正压住。invoke,先waitFor断言它以本轮文本被调用(与同文件既有写法一致),再做窗口期断言。3 [bug · high] 权限被拒分支与
finally各清一次忙态,把下一轮刚设上的忙态清掉 —— 已修(cab31613f)useDirectProjectChatController.ts的startTurn:写权限门返回false且没走onConfirmed时执行endTurnCommand(); dispatchNextQueuedTurn(); return;。dispatchNextQueuedTurn()的闸门是turnBusyRef.current || directTurnRunningRef.current,此时刚被上一句置假,于是同步走到startTurn(next)→beginTurnCommand(nextId):turnBusyRef.current = true、setPendingUserItemId(下一轮身份)。return后,同一个异步函数的finally无条件执行endTurnCommand():turnBusyRef又被置假,pendingUserItemId被清空(setTurnBusy(true)与随后的setTurnBusy(false)会被 React 并到同一批,最终是 false)。commandInFlight === false、pendingUserItemId === ''的状态下继续跑完整条链路(再次权限门 → invoke → 整轮)。后果有两条:①displayBusy为假,header / composer 在这一轮真的在飞时还接受新的发送;② 这轮的三态因为身份被清掉只会落到finished,刚修掉的「空窗期显示本轮结束」会在这条路径上复现。try之前声明let queueAdvance = false;;queueAdvance = true; return;(不在这里endTurnCommand,也不在这里出队);finally收口成一次:endTurnCommand();之后按queueAdvance || (invoked && projectPathRef.current === nextProjectPath)出队。这样
endTurnCommand()每轮只跑一次,且一定在出队之前,顺带消掉现在分支与finally的重复调用。invoked=false且queueAdvance=false的路径(例如!resolveTauriInvoke()早退)仍然不出队,与现状一致;只有权限被拒这一条改成走finally出队。queueAdvance = true; return;;finally固定顺序为「刷新清单 →endTurnCommand()→ 出队」;模块注释把这条顺序写成契约。keeps the next queued turn busy when the write gate refuses the running one:写权限门挂起时先把第二条排进队列,再让权限门拒绝,断言被出队那一轮在飞期间输入区仍是「终止」而不是「发送」。改之前该用例红(发送钮已经回来了),改之后绿。npx vitest run appSurface.test.ts directProjectTurn.test.tsx directTurnPresentation.test.ts directProjectTurnStatus.test.ts directThreadChat.test.ts→ 256 passed / 13 skipped;tsc、ESLint、Prettier、check:encoding、git diff --check通过。4 [bug · high] 报错 / 崩溃之后界面仍显示「陶泥儿正在处理」(用户现场反馈,不在原 review 里)—— 已修(
a35956f3e)turn.started/turn.completed这一对事件,reducer 的turnRunning就是「正在处理」卡片与输入盒忙态的唯一来源(displayBusy = nativeRunning || commandInFlight)。turn.completed放在collect之后统一补发,collect内部的return Err都会走到那条补发;但进程崩了、回合任务被中止、panic 不会走到补发。事件只活在内存 Thread Manager 里,lifecycle_anchor还会把最后一条生命周期事件(也就是那条turn.started)在重新订阅时回放,所以连重进都自愈不了。released」会手动markTurnStopped(),真失败路径没人放掉运行态。turn.started没配上终态,turnRunning永远为真 → 「陶泥儿正在处理」卡片常驻、输入盒停在「终止」且新消息全部进队列,看起来就是"还在处理"。chat_with_game_creator_direct_codex)以真失败返回时,controller 按本轮身份调用新的stopDirectThreadTurn:只放掉「是否在跑」,不写终态时间;收口身份记进commandClosedTurnUserItemId,同身份迟到的turn.started不再复活,而迟到的真turn.completed仍会补上结束时间。身份不同(another-turn-running)不动,用户主动终止也不算失败(宿主必然补终态)。stops claiming the turn is running when a failed send left turn.started open;变异验证(拿掉 controller 里的兜底收口)该用例红、恢复即绿;定向 6 个测试文件 268 passed / 13 skipped,tsc、ESLint、Prettier、check:encoding、check-doc-index、git diff --check通过。