docs(05): 锚点补记十九——子会话流程变成清单(S1~S9,每条带文件:行号与归类),先纠正我自己两条错前提,附 12 步设备验收骨架、优先级建议与 ws_block_proxy.py 不在 git 里的提醒
This commit is contained in:
1 parent
ff699f5079
commit
669d05b781
1 file changed
+50
@@ -1233,3 +1233,53 @@ curl -s -X PATCH "$BASE/api/v1/sessions/$SID" -H "Authorization: Bearer $TOK" \
|
|||||||
> ### 未做
|
> ### 未做
|
||||||
> - **设备验证**(任务书禁碰 adb):「一次事故一次横幅」的体验只由纯函数单测 + KDoc 钉住。若要设备验收,用 `ws_block_proxy.py` 造场景即可。
|
> - **设备验证**(任务书禁碰 adb):「一次事故一次横幅」的体验只由纯函数单测 + KDoc 钉住。若要设备验收,用 `ws_block_proxy.py` 造场景即可。
|
||||||
> - 项目没有 `ChatViewModel` 单测装置,所以复位判据提成纯函数来钉(与 W43 同一做法);清零点被调用这件事靠既有架构保证。
|
> - 项目没有 `ChatViewModel` 单测装置,所以复位判据提成纯函数来钉(与 W43 同一做法);清零点被调用这件事靠既有架构保证。
|
||||||
|
|
||||||
|
> ## ★★ 锚点补记十九(2026-10-06 · **「子会话流程」从模糊待办变成清单**)
|
||||||
|
>
|
||||||
|
> 依据:HEAD=`ff699f5` 的**只读静态核对**(含后端定点交叉验证)。⚠️ **除 S3/S6 之外均未做设备实测**(完成判据里「需真机」已逐条标出)。
|
||||||
|
>
|
||||||
|
> ### ⚠️ 先纠正两条我自己写错的前提(免得沿用错的方向)
|
||||||
|
> 1. **「每个 `ChatScreen` 各建一条 `ChatSocket`」已经不成立** —— W28(`7b97440`)已收成**进程级唯一**:`AppContainer.kt:229`(`by lazy` 唯一实例)、`ChatViewModel.kt:964-967`(只订阅)、`SessionsViewModel.kt:135-153`(唯一 connect/close 主人)。剩下的是**VM 不回收**那一半(S4)。
|
||||||
|
> 2. **`sendInterrupt` 不再是死 API**(`devdocs/02:144` 已闭环):唯一调用方 `ChatViewModel.kt:2006-2009`(「设置=打断 且 忙」时才发)。但**「切会话时打断」这个原始出口确实从未接线**(S5),且全 App 没有停止按钮。
|
||||||
|
>
|
||||||
|
> ### 清单(按严重度降序)
|
||||||
|
>
|
||||||
|
> **S1 · 客户端问题 · 体验瑕疵(换服务器 = 看到别的后端的内容)**:登出/重登或**换服务器地址**后,聊天页仍显示**登出前那份消息列表**(抽屉则空着,直到打开一次才回来)。
|
||||||
|
> 证据:`ChatScreen.kt:150-153`(`viewModel(key = sessionId)`,owner 是 Activity 级 store)+ `ChatViewModel.kt:932-968`(只在 init 拉一次)/`:2912-2914`(`onCleared` 空)+ `CyreneApp.kt:23-31`(登出只把它移出组合,实例仍在);**会话列表有 `baseUrl+username` 指纹闸**(`SessionPreferences.kt:34-36`、`SessionsViewModel.kt:397-398`),**ChatViewModel 没有对应指纹**。
|
||||||
|
> 修法方向:给 VM key 加账号指纹(一行);**真正的回收**要自定义 `ViewModelStoreOwner`,而它与「切回来状态还在」强绑定 ⇒ **属要主人拍板的设计取舍**。
|
||||||
|
>
|
||||||
|
> **S2 · 客户端问题 · 体验瑕疵**:**次级会话没有「握手 history」这第二条安全网**。进次级会话时 WS 未连(冷启动离线)→ 此后连接恢复**不补拉**;主会话由 `history`(最近 50 条)兜住。
|
||||||
|
> 证据:`ChatViewModel.kt:982-997`(`gapCursor` 只在「离开 Connected」时记、重连才补)、`:1787-1805`(`armGapCursorForFallback` 的既有边界);后端 `ws_hub_conn.go:57` 固定 `replayHistory(MainSessionID)`、`ws_history.go:36`。
|
||||||
|
>
|
||||||
|
> **S3 · 客户端问题 · 需真机确认**:冷启动进次级会话可能**闪一下「还没有消息」**。
|
||||||
|
> 证据:`ChatViewModel.kt:2408-2409`(history 帧过滤后为空就无条件 `loading=false`)而初值 `true`(`:610`),正文要等 `repo.loadLatest`;渲染分支 `ChatScreen.kt:486-487`。
|
||||||
|
>
|
||||||
|
> **S4 · 客户端问题 · 仅技术债**:**每个访问过的会话各留一个常驻 VM**(仍订阅共享流、**每次重连各自发一轮 after 补拉**、删会话不回收),各留消息列表(含内联 base64 图片)。
|
||||||
|
> 证据:`ChatScreen.kt:150-153`/`177-182`、`AppContainer.kt:215-221`(KDoc 自认)、`ChatViewModel.kt:966`/`982-997`/`2303-2310`/`2960`(20 页上限)。
|
||||||
|
> ⚠️ **顺带一处文档硬伤**:`ChatHomeScreen.kt:26-33` 与 `ChatScreen.kt:161-164` 写「`onSessionGone` 一被调用,那个会话的 VM 连同状态**立刻被销毁**」——**这是错的**(key 变了只是组合被销毁,VM 仍在 store 里继续收帧)。行为结论没错、成因写错了,**建议顺手改对(零成本)**。
|
||||||
|
>
|
||||||
|
> **S5 · 客户端问题 · 体验瑕疵**:**切会话不打断**——在次级会话发一条后立刻切回主会话,那一轮继续在服务端跑完(算力照花),抽屉也看不出谁在生成。
|
||||||
|
> 证据:`sendInterrupt` 唯一调用点 `ChatViewModel.kt:2008`(仅发送时刻按 `InterruptScope.kt:39-40` 发)、会话切换路径 `ChatHomeScreen.kt:119-122` 里没有任何打断动作。
|
||||||
|
> 注:**不接线今天未必更差**(切走不打断 + VM 不回收,恰好让「切回来那一轮还在、流式完整」成立)。真正缺的是**能看见 + 能停**的新入口(可复用 S9 的广播帧)。
|
||||||
|
>
|
||||||
|
> **S6 · 契约缺口(要登记后端)+ 客户端 · 体验瑕疵**:**抽屉未读对「非激活会话」不实时增长**——停在主会话时次级落了新消息,红点不出现,必须开一次抽屉才补上。
|
||||||
|
> 证据:`SessionsViewModel.kt:359-369`(`message_persisted` 只在 `sessionId == activeSessionId` 时推进)、列表更新唯一触发点 `ChatHomeScreen.kt:96-102`;**后端设计里已登记为遗留**:`Cyrene_v2/DevDocs/design/16:103`。归类理由:`message_persisted` **不带 `unreadCount`** ⇒ 客户端算不准(契约面缺增量);客户端可自愈(节流重拉 `GET /sessions`)。
|
||||||
|
>
|
||||||
|
> **S7 · 客户端问题 · 仅技术债**:**已删除会话的僵尸 VM** 下次重连会发一次注定 404 的补拉,404 被吞(`sessionGone` 位没有消费者)。证据:`SessionsViewModel.kt:265-278`、`ChatViewModel.kt:2303-2310`/`:2333-2336`/`:2912-2914`。
|
||||||
|
>
|
||||||
|
> **S8 · 结论:以下几处主/次「没有分叉」,别再返工**:发送/流式/灰态/**并行分桶**、分页与补拉、主会话禁删(三处一致)、改名/置顶、已读游标、分享落地/D2 搜索/图片与附件 —— 全部按 `sessionId` 等值收敛,**同一份代码**。
|
||||||
|
> 另:**`sessionId → N` 序号映射已在 `c09a211` 整块退役**(`devdocs/02:146`)⇒「次级会话序号」**不再是待办**。
|
||||||
|
>
|
||||||
|
> **S9 · 客户端问题 · 仅技术债**:「这个会话正在生成」**既看不见、也没有冷启动可查的状态面**:`session_busy`/`session_idle`/`turn_begin` 都带 sessionId 且在广播,但 `SessionsViewModel.onEvent`(`:335-344`)只认 `session_read`/`message_persisted`;握手也不回放状态。
|
||||||
|
>
|
||||||
|
> ### 优先级建议(排查方给的)
|
||||||
|
> **先修**:**S1**(唯一会把「别的环境的对话内容」摆上屏幕的东西;便宜的一步是给 VM key 加账号指纹)、**S6**(日常最容易撞到;客户端可自愈 + 给后端登记)。**可继续搁置**:S4/S5/S3/S7/S2(各有理由,且 S4/S5 的正解与产品取舍绑定)。**零成本顺手**:把 S4 里那处 `ChatHomeScreen.kt:30` / `ChatScreen.kt:163` 的错误说法改对。
|
||||||
|
>
|
||||||
|
> ### 设备验收步骤(12 步,已设计好;**除 S3/S6 外都还没跑**)
|
||||||
|
> 步骤骨架(每步的判据在排查报告里,逐条都很具体):0 纪律(先 `dumpsys window | grep mCurrentFocus` 确认前台是本 App + `adb reverse tcp:10942 tcp:10942`)→ 1 建次级会话(判 `GET /sessions` 里有 `type:"sub"`)→ 2 次级发送+流式(判 `logcat -s CyreneRoute` 恰好一行 `route=Ws`、库里两条)→ 3 冷启动进次级(**盯 S3 那一下闪**;判无主会话内容混入)→ 4 切会话不打断取证(假后端 `out/upstream.jsonl` 无 `interrupt`)→ 5 次级没有 history 兜底(`--history-replay 2`,判 history 帧只含 `web`)→ 6 「次级+首屏空+WS 从未连上」的已知洞(**需 `ws_block_proxy.py`**)→ 7 未读/已读(**S6 现场**)→ 8 置顶/改名/禁删 → 9 删除(判 `GET .../messages` 404)→ 10 别处删除的兜底横幅 → 11 重连补拉在次级跑不跑(S2 闭环)→ 12 登出/重登陈旧状态(**S1 现场**)。
|
||||||
|
>
|
||||||
|
> ### ⚠️ 工具位置(排查方找不到它)
|
||||||
|
> **`ws_block_proxy.py` 不在 git 里** —— 它在 `app/build/e2e-shots/`(**被 gitignore**)。它很好用(只掐 `/ws/chat`,且 **Web 前端不经过 adb reverse ⇒ 代理日志只来自 App**),但**一个 `clean` 就可能把它扫掉**。**建议下一步把它挪进 `devtools/`**(连同一小段用法说明),否则下次又得重写。
|
||||||
|
>
|
||||||
|
> ### 排查方自己标注的不确定(照抄,别当成全量结论)
|
||||||
|
> 结论都是**静态核对**;S1/S2/S3 需真机确认。**后端只做了定点核对**(握手回放、广播路由、会话 404 契约、上行帧路由、回合播种)——**没有通读编排器**,所以「次级会话的回合在服务端与主会话 100% 同形」**不能背书**;记忆写回、主动消息、后台思考是否只走主会话**未逐条核对**。
|
||||||
Reference in new issue
Block a user