chore: rebuild release package
This commit is contained in:
47
.planning/session-form-switch-race/findings.md
Normal file
47
.planning/session-form-switch-race/findings.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 调研发现
|
||||
|
||||
## 用户侧证据
|
||||
|
||||
- 截图一:底部“表单”入口角标为 `1`,消息区只有普通助手消息,没有待处理表单卡片。
|
||||
- 截图二:执行 F5 后,同一会话顶部出现“先定空条件规则”表单,角标仍为 `1`。
|
||||
- 由此可排除“表单未持久化”这一主假设,更可能是 SPA 会话切换后的客户端状态同步或渲染失效。
|
||||
|
||||
## 待验证假设
|
||||
|
||||
1. 会话切换触发的消息请求存在竞态,旧会话响应晚到后覆盖当前消息列表。
|
||||
2. 待处理表单计数按全局/会话摘要更新,但表单卡片依赖另一份未同步的消息状态。
|
||||
3. 表单渲染使用了只在首次加载执行的派生缓存、去重集合或已处理 ID 集合,切换会话时未重置/重建。
|
||||
|
||||
## 代码定位(第一轮)
|
||||
|
||||
- `renderPendingCcwebPrompts()` 通过 `collectCurrentPendingCcwebPrompts()` 统计表单角标。
|
||||
- `collectCurrentPendingCcwebPrompts()` 优先读取 `sessionCache.get(currentSessionId).snapshot.messages`,随后再扫描消息区 DOM;因此角标可以来自完整缓存,即使卡片尚未进入 DOM。
|
||||
- `renderMessages()` 对超过 10 条的历史消息采用分批渲染:先渲染最后 10 条,再用 `setTimeout` 逐批前插;每个延迟批次用全局 `renderEpoch` 防止旧会话继续写 DOM。
|
||||
- `renderMessages()` 首批完成后调用 `renderPendingNotes()`,后者会立即刷新表单角标;此时较旧的表单消息可能还未渲染,形成“角标为 1、卡片暂缺”的短暂状态。
|
||||
- `applySessionSnapshot()` 是缓存展示和服务端 `session_info` 的共同入口;缓存展示强制 `immediate: true`,正常服务端响应默认分批渲染。
|
||||
- 当前需要继续验证:快速切换过程中,哪个响应/早退分支使当前会话的剩余批次被 `renderEpoch` 永久取消,却没有再次对当前快照执行完整渲染。
|
||||
|
||||
## 已确认关键符号
|
||||
|
||||
- `public/app.js`: `openSession`、`showCachedSession`、`applySessionSnapshot`、`renderMessages`
|
||||
- `public/app.js`: `collectCurrentPendingCcwebPrompts`、`renderPendingCcwebPrompts`、`renderPendingNotes`
|
||||
- `scripts/regression.js`: `assertSessionSwitchResilienceContract`
|
||||
|
||||
## 根因结论
|
||||
|
||||
1. `beginSessionSwitch()` 在请求意图阶段执行 `renderEpoch++`;如果新快照尚未提交、请求被快速切换覆盖或失败,旧 DOM 仍展示但其剩余批次已永久失效。真正替换 DOM 的 `renderMessages()` 本身已经推进并校验 `renderEpoch`,所以前者属于重复且有害的失效动作。
|
||||
2. `session_info` 的 `canSwitchToSessionInfo` 对当前 `sessionId` 无条件放行,没有阻止同一会话旧 `requestId` 响应重绘当前 DOM。
|
||||
3. `handleLoadSession()` 给 `session_info` 附带了客户端 `requestId`,但 `session_history_chunk` 没有;前端无法区分 `A→B→A` 中两次 A 加载的历史块。
|
||||
4. `finalizeLoadedSession()` 只按 `sessionId` 完成加载,旧 A 尾块可误完成新 A 请求。
|
||||
|
||||
## 测试策略
|
||||
|
||||
- 在 `scripts/regression.js` 新增无依赖、确定性的渲染 epoch 调度测试:发起 B 但不提交 B 时,A 的延迟表单批次必须继续;真正提交 B 后,A 批次必须停止。
|
||||
- 扩展会话切换静态契约:`beginSessionSwitch` 不推进 epoch;带请求 ID 的旧 `session_info` 不得仅因 sessionId 当前而放行;历史块及 finalize 路径校验请求 ID。
|
||||
- 扩展 WebSocket 集成断言:`session_info` 与同次 `session_history_chunk` 回传相同请求 ID。
|
||||
|
||||
## 仓库协作状态
|
||||
|
||||
- `.planning/.active_plan` 当前指向其他任务 `subagent-card-metadata`,本次不改动该指针。
|
||||
- `.trellis/.current-task` 当前指向其他任务 `07-17-gilded-wasteland-theme`,本次不覆盖。
|
||||
- 工作树已有无关未跟踪 CSV,必须保留。
|
||||
25
.planning/session-form-switch-race/progress.md
Normal file
25
.planning/session-form-switch-race/progress.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 进度日志
|
||||
|
||||
## 2026-07-18
|
||||
|
||||
- 已读取并启用 `planning-with-files` 与 `todo-list-csv` 技能。
|
||||
- 已分析用户提供的两张截图,确认 F5 可恢复表单渲染。
|
||||
- 已隔离创建本任务规划文件,未覆盖其他会话的活动计划。
|
||||
- 当前阶段:定位表单计数与消息渲染的会话切换链路。
|
||||
- 计划审查代理已通过计划,无阻塞问题。
|
||||
- `codebase-memory-mcp` 索引 `home-cc-web` 状态为 ready(3950 节点、8664 边)。
|
||||
- 已确认表单角标读取完整 `sessionCache`,消息区历史采用带 `renderEpoch` 的异步分批 DOM 渲染,二者存在可观察状态分叉。
|
||||
- 两条独立只读调查均确认 `beginSessionSwitch` 过早失效渲染批次;另确认同一会话旧 `requestId` 响应与无请求 ID 历史块存在竞态。
|
||||
- 已完成步骤 1,进入步骤 2:先编写可确定性失败的回归测试。
|
||||
- 已在 `scripts/regression.js` 新增 `session-switch-race` 定向回归入口,并接入默认回归。
|
||||
- 已运行 `node scripts/regression.js --target session-switch-race`,当前生产代码如预期失败:渲染 epoch、旧 session_info、历史块/finalize 请求归属、服务端历史块 requestId 五类断言全部命中。
|
||||
- 已完成步骤 2、3,进入步骤 4:修改 `public/app.js` 与 `server.js`。
|
||||
- 注意:`scripts/regression.js` 同时出现其他会话新增的侧栏测试改动,本任务不覆盖、不回退。
|
||||
- 已修改 `public/app.js`:渲染 epoch 只在真实 DOM 替换时推进;`session_info`、历史块与 finalize 按加载 requestId 拒绝旧代次。
|
||||
- 已修改 `server.js`:`session_history_chunk` 透传 `load_session` 的 requestId。
|
||||
- 已完成步骤 4;定向 `session-switch-race` 回归、三份 JS 语法检查及 diff whitespace 检查均通过,进入步骤 5 完整回归。
|
||||
- 第一次完整回归失败于旧源码字符串契约 `!messageRequestId && !currentSessionId && !activeLoad && !pendingNewSession`;行为兼容分支仍存在,已改为具名布尔量以恢复旧契约,待重跑。
|
||||
- 定向 `session-switch-race` 回归再次通过。
|
||||
- 完整 `npm run regression` 通过并输出 `Regression checks passed.`,退出码 0。
|
||||
- 已完成步骤 5,进入步骤 6:静态检查与影响面审查。
|
||||
- `codebase-memory-mcp detect_changes` 在影响分析阶段返回 `Transport closed`;已按降级策略改用本地静态检查与独立只读审查。
|
||||
48
.planning/session-form-switch-race/task_plan.md
Normal file
48
.planning/session-form-switch-race/task_plan.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 会话切换后表单不渲染修复计划
|
||||
|
||||
## 目标
|
||||
|
||||
修复频繁切换会话后“表单”角标已有待处理数量、但消息区未渲染表单的问题,确保切回会话后无需 F5 即可看到并操作待处理表单。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 频繁切换包含待处理表单和普通消息的多个会话后,当前会话的表单立即渲染。
|
||||
- 旧会话异步请求晚到时,不得覆盖当前会话的消息或表单派生状态。
|
||||
- 表单角标数量与消息区实际可见待处理表单保持一致。
|
||||
- 页面刷新后的恢复行为与 SPA 内切换行为一致。
|
||||
- 新增竞态回归测试,并通过相关前端测试与静态检查。
|
||||
|
||||
## 工作步骤
|
||||
|
||||
1. [DONE] 定位表单计数与消息渲染的会话切换链路
|
||||
2. [DONE] 编写可复现竞态的回归测试
|
||||
3. [DONE] 运行回归测试并确认当前实现失败
|
||||
4. [DONE] 修复会话切换时的表单状态同步与过期请求覆盖
|
||||
5. [DONE] 运行前端相关测试并确认回归通过
|
||||
6. [IN_PROGRESS] 执行静态检查并审查影响面
|
||||
7. [TODO] 使用真实浏览器验证频繁切换无需刷新
|
||||
|
||||
## 关键假设
|
||||
|
||||
- 后端数据完整,因为 F5 后同一待处理表单能够显示。
|
||||
- 缺陷位于前端会话切换、消息加载或表单派生渲染状态的同步路径。
|
||||
- 修复应优先采用请求归属校验或会话级状态隔离,不依赖强制刷新。
|
||||
|
||||
## 已确认根因与修复边界
|
||||
|
||||
- `beginSessionSwitch()` 在切换请求刚发起、DOM 尚未替换时提前执行 `renderEpoch++`,会永久取消当前会话尚未完成的历史消息批次;表单若位于该批次,便形成“缓存角标有 1、DOM 卡片为 0”。
|
||||
- `session_info` 对 `msg.sessionId === currentSessionId` 无条件放行,同一会话旧 `requestId` 响应可能覆盖新加载。
|
||||
- 服务端 `session_history_chunk` 未回传 `requestId`,前端历史块也只按 `sessionId` 判断;`A→B→A` 时旧 A 尾块可能误完成新 A 加载。
|
||||
- 修复同时覆盖:渲染代次只在真实 DOM 提交时推进;加载响应按 `sessionId + requestId` 归属;历史分块透传请求 ID。
|
||||
|
||||
## 风险
|
||||
|
||||
- 消息加载、流式事件和表单计数可能来自不同异步通道,需要避免只修复其中一条路径。
|
||||
- 仓库由多个会话共享,不能覆盖现有 `.planning/.active_plan`、Trellis 当前任务或无关工作树改动。
|
||||
|
||||
## 错误记录
|
||||
|
||||
| 错误 | 尝试 | 处理 |
|
||||
|---|---:|---|
|
||||
| 完整回归的旧静态断言要求保留 requestless idle 分支原字符串 | 1 | 将兼容分支拆为具名布尔量,保留语义和可审查契约后重跑 |
|
||||
| `codebase-memory-mcp detect_changes` 返回 `Transport closed` | 1 | 不重复同一失败调用,降级为本地 git diff/rg/语法检查和独立审查代理 |
|
||||
Reference in New Issue
Block a user