docs(05/07): 锚点补记二十七 —— W50 落地并设备验收通过;细节单开 07(证据与避坑)

改了什么:
- 05 新增锚点补记二十七:结论(afee5f6 已推 / 1193 测试真跑 0 失败 / APK 尺寸)、四条硬要求逐条
  对应实现处、红线核对、设备四条判据全绿、环境坑一句话版、真后端那条主动路径是活的(副产品)、
  待办变化(W48/W49 验收环境已就绪;W50 的窄尾巴)。
- 新增 devdocs/07-W50-主动消息-验收与避坑.md:验收判据与逐条证据、完整环境坑(含绕法与命令)、
  复现整套验收的最短路径、随批次修掉的 timestamp 解码缺陷、以及「落库失败档」那条尾巴的收口建议。

为什么单开 07:05 已 1400+ 行,验收的操作细节与避坑属参考材料而非状态;05 只留结论与指针,
下一个我读 05 就能接手,需要动手复现时再翻 07。
This commit is contained in:
AskaEth committed 2026-10-06 08:11:26 +08:00
1 parent afee5f612d
commit c905a47d6f
2 files changed
+128

No files matched your search

+48
View File
@@ -1461,3 +1461,51 @@ curl -s -X PATCH "$BASE/api/v1/sessions/$SID" -H "Authorization: Bearer $TOK" \
> ### 7. 一条方法论(今天最大的教训,写给下一个我)
> 我今天**四次**栽在自己的测量上(黑屏、前台不是本 App、复用坐标、不确认文字落点),**三次**拿「对端日志缺席」当「客户端没做」的证据。
> ⇒ **先怀疑自己的测量,再怀疑代码**;**要判客户端行为,只看客户端自己的输出**;**一次只改一个变量**。
---
## ★★ 锚点补记二十七 · W50 落地并**设备验收通过**(她主动说的话,现在实时到安卓)
> 这一段是**已验证**的状态,直接当事实用。操作细节与全部证据见 `07-W50-主动消息-验收与避坑.md`。
### 1. 结论
**`proactive_message` 帧现在有消费点了**:她主动说的话**实时**出现在主会话,刷新/重启后仍在、按 `id` 不重复;
无 `id`(后端落库失败那一档)退化成**本地气泡**、绝不静默丢弃。
- 合并提交 **`afee5f6`**(`android`,已推);W50 分支与 worktree 已删、构建锁已放。
- 全量(合并后的树,`testDebugUnitTest --rerun` **真跑**):`BUILD SUCCESSFUL` / **91 suite · 1193 tests · 0 failed · 0 error**(基线 1177 + 新增 16)。
- APK:`app/build/outputs/apk/debug/app-debug.apk`(47,850,706 B)。
### 2. 四条硬要求逐条成立(合并前核过)
| 要求 | 实现处 |
|---|---|
| 按**已落库的 assistant 行**经 `mergeMessages` 并入,不造只活在 UI 的气泡 | `ChatViewModel.onProactiveMessage` ⇒ 与 `onPersisted` **同一条** `mergeMessages` |
| **自己钉死主会话**(帧壳不带 `sessionId`,`isForSession` 会放行给每个 VM)| `ui/chat/ProactiveIngest.kt` 的纯函数 `proactiveIngestFor`:`sessionId != MAIN_SESSION_ID ⇒ Skip`(**等值**比较,`web` ≠ `web:xxx`)|
| 无 `id` 退化成本地气泡,**不许静默丢弃** | 同上 `Bubble`(本地 id `proactive-local:` + 帧里已有的稳定键 ⇒ 重投也是 upsert);只有「无 id **且**正文空白」才 Skip |
| 顺带触发 W49 的节流静默重拉 | `SessionsViewModel.onProactiveMessage`:复用 `persistedRoute` + `requestSilentRefresh`(**没有**新取数路径);另补「正看着主会话 ⇒ 看到即已读」那一档 |
红线核对:`mergeMessages` 语义未动(只被调用)、W49 节流判据未动、W48 的 VM key 未动、**没有**接成「收到就整表重拉」。
### 3. 顺带修掉一个不修就等于没做 W50 的缺陷
`ProactiveData.timestamp` 线上是 RFC3339 **字符串**(Go `time.Time`),旧声明 `Long?` ⇒ 解码抛异常被
`WsEvent.parse` 的 `runCatching`(`WsEvent.kt:654`)吞成 `null` ⇒ **整帧在成形之前就没了**(只加 `onEvent` 分支碰不到它)。
现在按字面量收(字符串/数字都吃)。详见 `07` §4。
### 4. 设备证据(假后端,四条判据全绿)
①主会话**实时**出现(`delivered=1`,5s 后 dump 到正文 + 角标「发送至 网页」);②次级会话 **0 次**;
③重启后 **1 条**、**同 id 重投仍 1 条**;④`--without-id` 档本地气泡 **1 条**、重启后 **0 条**而落库那条仍 1 条。
### 5. 这一次的环境坑(一句话版;完整版在 `07` §2)
**`input text`/keyevent 的标点会被 Gboard 转成全角**(系统 locale 是 zh-CN)——
绕法是「地址栏只填**纯字母** `localhost`」(`normalizeBaseUrl` 会补 `http://`,`ApiClientFactory.kt:109`)
+ `adb reverse tcp:80 tcp:<后端端口>`(**需 root**,且 `adb root` 会清掉所有 reverse)。
另:点太快会 **ANR 被强杀**(冷启动等 25~35s 再动 + 三个动画开关置 0);黑屏挡不住(每次交互前 `keyevent 224` + 确认前台);
`adb shell /sdcard/...` 要 `MSYS_NO_PATHCONV=1`;Python 要 `PYTHONIOENCODING=utf-8`。
### 6. 副产品:真后端那条主动路径是活的
验收前 App 还连着真后端时,主会话历史里有一条 `发送至 cyrene-backthink` 的她的话(「早安呀,亲爱的♪ …」)——
**落地行、来源角标、主会话归属三者都对**,与本次修的消费口径一致(那条来自历史拉取,不是实时帧)。
### 7. 待办变化
- 上面第 3 项「W48/W49 设备验收」**环境已就绪**(模拟器已 root、reverse 指向真后端)⇒ 现在是**可做**状态。
- W50 留了一条**窄尾巴**(只在「落库失败」档可达:降级气泡长按可删 ⇒ 会用本地 id 打服务端报错);收口做法见 `07` §5。
- 已更正「代理旧路径」那条陈旧项(见 `5520d60`)。