feat: overhaul task board and cross-conversation workflows
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"执行前端质量检查清单"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"检查渲染结构、事件归属和可维护性"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"检查回归覆盖、错误处理和无障碍状态"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"检查状态流转和并发保护"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"检查附件状态数据的边界处理"}
|
||||
{"file":".trellis/tasks/08-11-failed-insert-card-actions/research/failed-insert-chain.md","reason":"逐项核对重试、删除、回滚和回归验收契约"}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"执行前端预开发清单与完成后质量检查"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"保持失败卡片渲染和事件边界清晰"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"遵循项目测试、错误处理和代码质量要求"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"正确管理失败、重试中、成功和删除状态"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"校验附件状态字段和事件数据边界"}
|
||||
{"file":".trellis/tasks/08-11-failed-insert-card-actions/research/failed-insert-chain.md","reason":"提供已验证的调用链、状态契约、持久化风险和测试范围"}
|
||||
53
.trellis/tasks/08-11-failed-insert-card-actions/prd.md
Normal file
53
.trellis/tasks/08-11-failed-insert-card-actions/prd.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 失败插入卡片重试与删除
|
||||
|
||||
## 背景
|
||||
|
||||
图片或附件插入失败后,消息内卡片目前只显示“插入失败”,用户既不能在原位重试,也不能清理该失败项,只能重新组织消息或容忍残留卡片。
|
||||
|
||||
## 目标
|
||||
|
||||
为失败插入卡片提供“重试”和“删除”两个就地操作,使失败可恢复、残留可清理,同时保持原消息文本和其他附件不变。
|
||||
|
||||
## 用户故事
|
||||
|
||||
- 作为发送图片/附件的用户,我可以点击“重试”,沿用原失败项再次执行插入,不必重新选择文件。
|
||||
- 作为发送图片/附件的用户,我可以点击“删除”,只移除目标失败项,不影响同一消息中的文本和其他附件。
|
||||
- 作为键盘或辅助技术用户,我可以识别按钮用途和重试中的忙碌状态。
|
||||
|
||||
## 功能要求
|
||||
|
||||
1. 仅状态为失败的插入卡片展示“重试”和“删除”操作。
|
||||
2. 重试必须以失败项的稳定标识为目标,复用现有插入链路及原始附件数据。
|
||||
3. 重试开始后卡片进入处理中状态;在请求完成前禁用重复重试和删除,并暴露可访问的忙碌状态。
|
||||
4. 重试成功后,卡片按现有成功状态渲染,不再展示失败操作。
|
||||
5. 重试再次失败后,卡片恢复失败操作,并显示既有或更明确的失败原因。
|
||||
6. 删除只移除目标失败项;同名附件、其他附件、消息文本及其他消息不得受影响。
|
||||
7. 操作必须遵循现有状态持久化边界:若失败项已持久化,刷新后仍能正确重试/删除;若现有设计只保存于客户端,则不得伪造跨刷新保证。
|
||||
8. 事件处理必须防止重复绑定和并发提交。
|
||||
|
||||
## 视觉与交互
|
||||
|
||||
- 两个操作放在失败状态附近,文本分别为“重试”“删除”。
|
||||
- “重试”为主要恢复操作,“删除”为克制的危险操作;不引入与当前卡片风格冲突的大面积按钮。
|
||||
- 重试期间显示明确的处理中反馈,并设置 `disabled` 与 `aria-busy`(或等价可访问语义)。
|
||||
- 按钮应支持键盘聚焦与激活,并具有明确的可访问名称。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不重构整个消息或附件系统。
|
||||
- 不删除用户本地原始文件,也不清理与目标失败项无关的服务器资源。
|
||||
- 不新增独立的附件管理面板。
|
||||
- 不改变成功卡片和进行中卡片的既有操作语义。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 失败卡片可见两个操作,其他状态不可见。
|
||||
- 连续快速点击重试只产生一次有效插入请求。
|
||||
- 成功、再次失败、删除三个分支均有自动化测试。
|
||||
- 删除一项后,同消息中的文字和其他附件保持不变。
|
||||
- 定向测试、相关回归和语法/静态检查通过,单次测试最长 60 秒。
|
||||
- 最终差异不覆盖工作区中已有的“独立任务看板”改动。
|
||||
|
||||
## 实施隔离
|
||||
|
||||
`.trellis/.current-task` 当前属于另一个活动会话。本任务不切换该全局指针;实施与检查代理必须显式读取本目录下的 `prd.md`、上下文清单和研究材料。
|
||||
@@ -0,0 +1,74 @@
|
||||
# 失败插入卡片链路与实现契约
|
||||
|
||||
## 现状链路
|
||||
|
||||
```text
|
||||
sendMessage
|
||||
→ 创建本地用户卡片 + clientMessageId
|
||||
→ WebSocket type=message
|
||||
→ handleMessage
|
||||
→ 活动 Codex App turn 时进入 handleCodexAppSteerMessage
|
||||
→ codex_app_steer_status(pending / inserted / failed)
|
||||
→ handleServerMessage
|
||||
→ updateCodexAppSteerMessage
|
||||
→ setCodexAppSteerStatusElement
|
||||
```
|
||||
|
||||
关键位置:
|
||||
|
||||
- `public/app.js`:`sendMessage`、`setCodexAppSteerStatusElement`、`updateCodexAppSteerMessage`、`createMsgElement`、`clearUserMessageIndex`。
|
||||
- `public/style.css`:`.codex-steer-*` 状态样式。
|
||||
- `server.js`:`handleMessage`、`handleCodexAppSteerMessage`。
|
||||
- `scripts/regression.js`:`runCodexAppRuntimeImageSteerRegression`、`runCodexAppStaleRunningRegression`。
|
||||
|
||||
## 已确认事实
|
||||
|
||||
1. 本地失败卡片以 `clientMessageId` / `data-message-id` 稳定定位,不应按文件名或 DOM 顺序匹配。
|
||||
2. 当前 `userMessageIndex` 只保存内容与时间;重试还需要保存附件、会话、模式和 agent。
|
||||
3. 当前状态渲染只有文本,没有操作容器、忙碌语义和并发保护。
|
||||
4. 当前服务端在调用 `turn/steer` 前持久化用户消息,最终失败时不回滚,会留下模型未接收的幽灵消息。
|
||||
5. 同步前置失败不会持久化;两类失败必须统一为“失败卡片仅存在于当前客户端”的语义。
|
||||
6. 本地临时卡片当前提前增加 `currentSessionMessageCount`,失败后会造成消息索引漂移。
|
||||
|
||||
## 推荐实现
|
||||
|
||||
### 前端
|
||||
|
||||
- 新增独立的运行中插入记录 Map,以 `clientMessageId` 保存:元素、文本、附件克隆、sessionId、mode、agent、pending/committed 状态。
|
||||
- `clearUserMessageIndex` 同时清理该 Map,防止切换会话后重试旧请求。
|
||||
- `setCodexAppSteerStatusElement` 统一维护:
|
||||
- `role=status`、`aria-live=polite`;
|
||||
- 卡片 `aria-busy`;
|
||||
- 失败态的“重试”“删除”按钮;
|
||||
- 非失败态移除操作容器;
|
||||
- 按钮使用 `type=button`、明确 `aria-label`,并阻止重复绑定。
|
||||
- `retryCodexAppSteerMessage` 只在记录存在、仍属于当前会话、当前为 Codex App 运行中且 WebSocket 可用时发送;先切到 pending 并禁用操作,发送失败则恢复 failed。
|
||||
- `deleteCodexAppSteerMessage` 只允许删除 failed 临时项,移除 DOM、两个索引记录并刷新轮廓/滚动条。
|
||||
- 临时插入不提前递增 `currentSessionMessageCount`;收到 `inserted` 后才标记 session message 并递增,失败/删除不改变持久化索引。
|
||||
|
||||
### 服务端
|
||||
|
||||
- 给待持久化的 steer 用户消息写入稳定 `id`(优先使用 `clientMessageId`)。
|
||||
- 最终 `turn/steer` 失败时,在发送 failed 状态前,从最新会话中按该 id 精确移除本次用户消息并保存。
|
||||
- stale no-active-turn 自动恢复成功分支不得回滚;同步前置失败和 ready 超时本就没有持久化项。
|
||||
- 异步错误消息补带 `clientMessageId`,便于前端和回归关联。
|
||||
|
||||
## 测试契约
|
||||
|
||||
- 前端独立单测或可执行契约测试:
|
||||
- failed 创建两个按钮,pending/inserted 不展示;
|
||||
- 快速双击重试只发送一次,载荷复用原文本与附件;
|
||||
- retry 时 `aria-busy=true` 且操作禁用/移除;
|
||||
- failed 恢复操作;
|
||||
- delete 只移除目标项及对应索引;
|
||||
- turn 已结束或 WebSocket 断开时不发送并保留可恢复失败态。
|
||||
- 服务端定向回归:
|
||||
- 现有同步附件失败仍不持久化;
|
||||
- mismatch 异步失败后同名用户消息为 0;
|
||||
- stale recovery / 成功 steer 用户消息仍恰好为 1。
|
||||
- 静态契约:按钮文本、CSS 作用域和 status handler 保持存在。
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 附件已经从服务端过期时,单纯重试仍会失败;按钮主要恢复临时 turn/server 错误。再次失败后必须恢复可操作状态和明确错误提示。
|
||||
- 失败项按现有持久化边界保持客户端临时态,刷新后会消失;不新增任意历史消息删除协议。
|
||||
32
.trellis/tasks/08-11-failed-insert-card-actions/task.json
Normal file
32
.trellis/tasks/08-11-failed-insert-card-actions/task.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "failed-insert-card-actions",
|
||||
"name": "failed-insert-card-actions",
|
||||
"title": "失败插入卡片重试与删除",
|
||||
"description": "为 Codex App 插入失败卡片增加重试与删除,并修复失败消息持久化回滚。",
|
||||
"status": "completed",
|
||||
"dev_type": "bugfix",
|
||||
"scope": "frontend,server,regression",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shiyue",
|
||||
"assignee": "shiyue",
|
||||
"createdAt": "2026-08-11",
|
||||
"completedAt": "2026-08-11",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [
|
||||
"public/app.js",
|
||||
"public/style.css",
|
||||
"server.js",
|
||||
"scripts/regression.js",
|
||||
"scripts/failed-insert-card-unit.js"
|
||||
],
|
||||
"notes": "未切换 .trellis/.current-task;ccweb 重启因其他运行中对话暂缓。",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user