Files
cc-web/.planning/2026-08-11-failed-insert-card-actions/findings.md

8.9 KiB
Raw Blame History

失败插入卡片重试与删除:发现记录

用户需求

  • 用户希望“插入失败”的卡片增加“重试”和“删除”按钮。
  • 截图中的失败项位于用户消息卡片内,包含图片预览、文件名、尺寸/状态信息和红色“插入失败”状态。

视觉观察

  • 当前失败状态只有红点和“插入失败”文本,没有恢复或清理入口。
  • 操作应靠近失败状态区域,保持卡片宽度和当前主题视觉,不应抢占消息正文。

工作区约束

  • 当前分支为 main,已有另一任务留下的未提交改动。
  • 已确认 .trellis/.current-task 指向 08-11-task-board,本任务不会覆盖该指针。
  • 与本任务可能重叠的已修改文件包括 public/app.js、public/index.html、scripts/regression.js 和 server.js;后续必须做局部差异审查。

待确认的代码事实

  • 失败状态的数据结构、稳定标识和持久化位置。
  • 原始插入请求的入口、请求参数及重试时可复用的数据。
  • 删除应落在草稿状态、消息附件状态还是服务端持久化层。
  • 现有测试中与运行时图片插入相关的覆盖位置。

初步代码定位

  • codebase-memory-mcp 项目 home-cc-web 索引状态为 ready(5737 节点、12312 边)。
  • “插入失败”不是普通上传失败文案,而是 Codex App 运行中消息插入(steer)状态:前端入口命中 codexAppSteerStatusLabel 与相邻的 setCodexAppSteerStatusElement。
  • 服务端入口命中 handleCodexAppSteerMessage:turn/steer 成功发送 inserted,失败发送 failed,且消息会先以 persistedUserMessage 写入会话。
  • 下一步需要确认状态事件携带的稳定标识、前端如何找到对应用户消息,以及重试是否可以复用现有 handleCodexAppSteerMessage 消息形状。

已确认的数据与渲染契约

  • 前端 updateCodexAppSteerMessage(clientMessageId, status, message) 使用 userMessageIndex 或消息元素的 data-message-id 精确定位卡片,稳定标识已经存在,无需按文件名匹配。
  • setCodexAppSteerStatusElement 目前只切换 pending / inserted / failed 三种 CSS class,并把状态区域写成纯文本;操作按钮应由这里或紧邻的单一渲染函数统一维护,避免重复绑定。
  • 服务端回归 runCodexAppRuntimeImageSteerRegression 已覆盖 clientMessageId、失败状态和成功插入;失败的“附件不可用”消息明确不会持久化,说明截图里的失败卡片至少有一类是客户端临时项。
  • 现有成功分支会持久化一条带附件的用户消息,并最终发送 inserted;重试实现必须避免把失败项或成功项重复写入会话。

调用链与主要风险

  • handleServerMessage 仅在 codex_app_steer_status 分支调用 updateCodexAppSteerMessage;当前状态事件已是最小可扩展协议面。
  • handleCodexAppSteerMessage 在真正调用 turn/steer 之前 把用户消息写入会话。若 turn/steer 异步失败,该持久化消息当前不会回滚,因此“重试”若直接重发原消息会造成重复持久化。
  • 失败分为两类:附件解析/空消息等同步前置失败(未持久化)与准备/turn/steer 异步失败(可能已持久化)。删除和重试必须能区分或统一修正这两类,否则刷新后的行为会不一致。
  • 下一步要确认本地消息对象是否保存 clientMessageId、服务端是否已有单条消息删除/替换协议,以及附件引用在失败后是否仍可用。

客户端入口补充

  • sendMessage 为运行中 Codex App 插入创建本地 messageId,用该值注册 userMessageIndex,并作为 WebSocket 消息的 clientMessageId 发送;因此失败卡片已有天然的单项操作键。
  • 当前前后端没有通用的“删除单条消息”协议;仅有排队消息、提示卡等其他局部删除实现,不能直接用于持久化的 steer 用户消息。
  • 需要在服务端把 clientMessageId 与持久化消息关联起来,或在 steer 失败时回滚该消息,才能让删除和重试同时满足“不重复”和“刷新一致”。

可行实现方向

  • 本地卡片由 sendMessage 创建,包含完整 text 与克隆后的 attachments;扩展 userMessageIndex 即可保存重试载荷,不需要从 DOM 反解析。
  • createMsgElement 是失败状态和附件预览的共同装配点,setCodexAppSteerStatusElement 是状态区域的唯一更新点,适合统一创建/移除按钮并维护 disabled / aria-busy。
  • 推荐服务端在真实 turn/steer 最终失败时按对象引用回滚刚持久化的用户消息。这样所有失败卡片都保持客户端临时态,删除无需新增持久化删除协议,重试也不会制造历史重复项。
  • 重试仍需校验当前 Codex App turn 是否在运行;若运行已结束,应给出明确失败状态,不能让卡片永久停在 pending。

本地源码校验

  • 当前样式集中在 public/style.css 的 .codex-steer-* 区域,失败卡片已有红色边框/背景和 11px 状态行;新操作可在同一区域追加,避免影响其他消息卡片。
  • 项目没有 DOM 测试依赖,仅有 node scripts/regression.js;前端交互测试需要使用轻量假 DOM/纯函数抽取,或在现有回归中增加协议和静态契约断言。
  • 本任务开始时的重叠文件基线:public/app.js 已有 +155 行、server.js +532/-7 行、scripts/regression.js +73/-11 行、public/index.html +14 行,均来自既有“任务看板”工作,后续差异必须按目标函数/样式区段核对。
  • public/style.css 当前没有未提交改动,是本任务新增样式的低冲突落点。

测试与冲突边界

  • 现有 runCodexAppStaleRunningRegression 已有一个稳定的 expectedTurnId 不匹配失败夹具,适合新增“失败后持久化消息已回滚”的服务端断言。
  • 现有成功恢复分支已断言用户消息只持久化一次,可继续保护“重试/恢复不重复”。
  • 既有未提交 diff 的 hunk 不覆盖 setCodexAppSteerStatusElement、sendMessage 或 handleCodexAppSteerMessage 主体;可以对这些区段做局部补丁,但最终仍需逐 hunk 复核。
  • 仓库已有未跟踪的任务看板 MiniDOM 单测脚本,但它属于另一任务;本功能不能修改或依赖该未提交测试文件。若需要 DOM 单测,应建立独立、最小的测试入口。

失败夹具与规范结论

  • mock app-server 的 mismatch 夹具会保留一个不同的活动 turn,因此每次 turn/steer 都稳定返回 expectedTurnId does not match active turn;可用于重复验证失败与回滚,但不能作为成功重试夹具。
  • 前端 Trellis 规范除“静态资源版本握手”外大部分仍是模板占位,没有额外组件框架约束;本任务应遵循仓库现有原生 DOM + CSS 组织方式。
  • 回归脚本已有按 target 执行的分支,可为本功能提供小范围测试入口,避免每次执行完整回归超过 60 秒。

资源

  • 用户截图:sessions/_attachments/8da11ed4-5989-4d30-a12a-f27522138799.png
  • Trellis 任务:.trellis/tasks/08-11-failed-insert-card-actions/

最终实现状态

  • 前端新增 codexAppSteerRecords,保存失败项的原文本、附件、会话、模式和 agent;重试沿用同一 clientMessageId,并通过 status + inFlight 阻止重复发送。
  • failed 状态统一渲染“重试”“删除”;pending/inserted 移除操作,卡片使用 aria-busy,状态行使用 role=status 与 aria-live=polite。
  • 本地运行中插入不再提前增加 currentSessionMessageCount,只有收到 inserted 才提交消息索引;失败/删除不会制造普通路径的索引漂移。
  • 服务端为临时 steer 用户消息写入稳定 id,真正的 turn/steer 最终失败时先按 id 从最新会话回滚,再发 failed 状态;stale recovery 成功路径保持一次持久化。
  • 前端 MiniDOM 单测覆盖失败操作、双击保护、载荷复用、再次失败恢复、删除单项、断线/跨会话/turn 结束保护与成功索引提交。

验证结果

  • node scripts/failed-insert-card-unit.js:通过。
  • node scripts/regression.js --target runtime-image-send:通过。
  • node --check public/app.js、server.js、scripts/regression.js:通过。
  • node scripts/regression.js:因首次定向命令参数错误而实际执行,完整回归通过(约 46 秒)。

独立审查修正

  • 检查代理发现 options.emitUserMessage 的程序化 session_message 虽会显示失败操作,但缺少原始重试载荷;现已由服务端附加临时 steer 元数据,前端据此建立同样的重试记录。
  • 主线程进一步将这类 session_message 在结果未定时保持为临时项:不提前写入消息缓存、不提前占用 currentSessionMessageCount,收到 inserted 后才提交,避免失败删除后的缓存幽灵和索引漂移。