根因:`emit` 把每次改动都写成 `flattenAccounts(groups)`(写盘形态,空行被丢掉),而 草稿本身就是界面手势的往返载体 ⇒ 刚点出来的占位空行当场蒸发,界面照旧(主人报的 「配置指定适配器主人账号的卡片内的添加账号ID按钮似乎没用」)。`+ 添加平台` 走同一条 通路(空平台组同样被丢),实测同样失效。 改法:分清两种扁平形态 —— · 草稿 `draftAccounts`(编辑期,无损:空行/未选平台的组都留着;不 trim 正在输入的字) · 写盘形态 `accountsForSave`(保存期,只留平台与账号 ID 都非空的条目) 后端契约与 `configs/users/owner.yaml` 格式一个字不改。保存路径写盘前必须收形:半成品 条目(有 id 没平台)会被后端 `Validate()` 直接 400(internal/user/load.go)。 测试:`OwnerAccountsView` 从编辑器里拆出来(无 hook),测试可直接调用它、点它自己的 按钮、把 onChange 收到的真草稿再喂回去重渲染 —— 走的是「手势 → 草稿 → 界面」真往返 (本项目没有 jsdom)。修前该测试红在「点『+ 添加账号 ID』⇒ 界面上真的多出一行 — got 1, want 2」。
- 帧壳 `turnId` 在 socket 层不再被丢(`lib/ws.ts` 的 dispatchFrame 补 turnId/seq)—— 按路分桶的前提 - 并行帧组接入:`turn_begin` / `steer_ack` / `pending_ack` / `pending_drain` / `pending_full` / `chat_duplicate` ⇒ 六态 + 灰态 + `(sessionId, turnId)` 两级分桶;`appendChunk` 不再永远写尾气泡 - 多路忙态:`session_interrupted` 不再无条件当空闲(看 mode/activeTurns);`stream_end` 不再只按会话过滤 - `documents` 渲染(含 status/chars/truncated 与**逐字中文 reason**);`turnId` 历史行分组小标 - `System.tsx` 键名改正(真键 HOST/PORT/ADMIN_USERNAME/ADMIN_PASSWORD,避免照抄静默失效) - 主人档案**按平台分组** + 平台下拉 + 账号候选;**写盘仍展平数组**(后端契约不变) - 验证:tsc --noEmit ✅、npm run build ✅、22 个前端测试文件全通过(本次新增 3 文件 108 条断言) - 未做(如实):握手 history handler 与断线补拉、上行 interrupt 按钮、proactive_message/subagent_result 等帧落点、文档上传入口与图片预检、voice 管线、System 的 env 读端点