fix(web): keep composer enabled while generating and treat stop as neutral (BUG-551, BUG-552)

Enter now queues one follow-up instead of dropping it, and a rectification stop leaves the streamed reply with a grey notice instead of an error alert.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-06 13:08:43 +08:00
parent f5566c06ef
commit 055b7adca9
18 changed files with 534 additions and 74 deletions
+33
View File
@@ -8517,3 +8517,36 @@
- 复发自:BUG-546(计划层继续采集,决策层已经可采用)
- 修复版本:`ca4e2408`
## BUG-551 | 生成中禁用输入框,焦点丢失,回车消息被丢弃
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:`ChatComposer`、主咨询 `page.tsx``use-consultation-run.ts`、生时校正 `rectification-agentic-chat.tsx`
- 用户现象:发出消息后输入框变灰,不能继续打字;回答结束后必须再点一次输入框。生成中按回车,消息被丢掉。
- 触发条件:任一对话面正在生成回答时打字或按回车。
- 根因:两个对话面把 `isLoading` / `busy` 写进 `<textarea disabled>`。浏览器会立刻移走焦点且解禁后不归还。`send()` 在已有请求时直接 `return false`,没有任何排队。
- 修复:输入框只在只读、会话加载、onboarding 或另一面打开时禁用。生成中回车把文字排成一条待发送,正常结算后自动发出;停止、失败或断线恢复时放回输入框。发送键在有排队时禁用。
- 验证:`frontend/tests/chat-composer-queue.test.ts``composer-ime-contract``composer-isolation-contract``rectification-surface-contract`
- 防复发:不得再用生成中状态禁用 textarea;生成中的发送必须进入排队或明确放回草稿,不得静默丢弃。
- 相关记录:BUG-216、BUG-329
- 复发自:无
- 修复版本:待发布
## BUG-552 | 生时校正停止穿报错衣服,选择题与采用等待中停止无效
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:`rectification-agentic-chat.tsx``ChatMessageRow`、选择题与采用 `fetch`
- 用户现象:生成中点停止后对话区出现红色告警,气泡按失败处理;还没有正文时思考行消失。回答选择题或点采用后的等待里,停止键在但点了没反应。
- 触发条件:生时校正流式回答中点停止;或选择题 / 采用请求尚未回到 `send()` 时点停止。
- 根因:`AbortError` 把气泡写成 `failed``setError(RECTIFICATION_STOPPED_NOTICE)`,唯一出口是 `role="alert"``.error-message`。空正文时 `flatMap` 丢掉该行。选择题与采用两条 `fetch` 没有挂 `AbortController``runAbort`,停止是空操作。BUG-329 的防复发只锁了 `send()` 那一路 fetch abort。
- 修复:停止后气泡 `settled` + `stopped`,灰字说明已停止且不扣点,不再 `setError`。无正文也保留气泡。选择题与采用的 fetch 挂上同一个 `runAbort`
- 验证:`frontend/tests/chat-composer-queue.test.ts``rectification-agentic-entry.test.ts``rectification-surface-contract.test.ts``chat-notice-and-scroll-contract.test.ts`
- 防复发:停止不得走 `role="alert"``busy` 时可见的停止键必须 abort 当前 fetch,包括选择题与采用,而不仅是 `send()`
- 相关记录:BUG-329、BUG-551
- 复发自:BUG-329(停止按钮只绑了 `send()` 的 abort,没覆盖选择题与采用)
- 修复版本:待发布
@@ -0,0 +1,39 @@
# PROGRESS · 生成中输入框可打字、排队发送、停止中性态(2026-09-06)
工作树:`.worktrees/composer-live-input-and-stop-20260906`
分支:`codex/composer-live-input-and-stop-20260906`
基线:任务书写 `origin/staging` @ `b8ea3112`;开工时 `origin/staging` 已到 `d2a209d3`(后续任务书文档),以当时远端为准。
`page.tsx` 收尾 2040 行(上限 2041)。未改服务端、未改 `.gitea/workflows/**`、未 bump Skill。
| 任务 | 状态 | BUG |
| --- | --- | --- |
| 5.1 输入框不再禁用 + 排队发送 | 完成 | BUG-551 |
| 5.2 停止 = 中性状态 | 完成 | BUG-552 |
| 5.3 BUG_HISTORY / CHANGELOG / DESIGN / VOICE | 完成 | — |
## 实现要点
- 共用 `queued-draft.ts` + `useQueuedMessage`。主咨询在 `useConsultationRun` 里结算排队;校正在 `busy` 下降后按 `lastRunOutcome` 发送或放回。
- `ChatComposer` 增加可选 `queued` 卡片;textarea 只接受结构性 `inputDisabled`
- 校正 `AbortError`:气泡 `stopped`,灰字 `RECTIFICATION_STOPPED_NOTICE`,不再 `setError`。选择题 / 采用 fetch 挂 `runAbort`
- 发送键在有排队时禁用。停止后不抢焦点;撤回才聚焦。
## 既有断言改动
| 文件 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `rectification-surface-contract` | `if (raw.trim()) setError(RECTIFICATION_STOPPED_NOTICE)` | 气泡 `stopped` + `stoppedNotice`,无 setError | 停止不能穿报错衣服(BUG-552) |
| `rectification-agentic-entry` | `caught.name === "AbortError"` | `isAbortError(caught)` | 选择题与采用共用中止判定 |
| `chat-notice-and-scroll-contract` | 未改断言正文 | `JumpToLatestButton` 仍保持原多行 JSX | 曾误压成一行导致 `sourceBetween` 找不到 |
## 测试
| 命令 | 结果 |
| --- | --- |
| `npx tsx --test tests/chat-composer-queue.test.ts tests/composer-*.test.ts tests/chat-notice-and-scroll-contract.test.ts tests/rectification-surface-contract.test.ts tests/rectification-agentic-entry.test.ts tests/agent-voice-copy-contract.test.ts` | 81 项 pass 81 fail 0 |
| `./node_modules/.bin/tsc --noEmit` | 0 错 |
| 改动文件 eslint `--quiet` | 0 error |
| `tests/class-name-definition-contract.test.ts` | 3/3 |
| `page.tsx` | 2040 行 |
| `npm run build -- --webpack` | 编译通过;随后在既有 `dossierResponse` 路由导出类型上失败,与本次无关 |
@@ -0,0 +1,50 @@
# Staging 人肉复核 · 生成中可打字、排队发送、停止中性态(2026-09-06)
给产品负责人。不要把真实出生资料或对话正文写进任何记录。
对应 BUG-551、BUG-552。测之前先做第 0 条。
## 0. 确认测的是新版本
浏览器打开 `https://staging.jyotisha.chat/api/health`,看 `deployment.gitCommit` 前 8 位是否等于本单合入 staging 后的提交。不一致 = 先别测。
## 1. 生成中连续打字不丢焦点(P0)
1. 打开一次普通咨询,发出一条问题。
2. 回答还在生成时,立刻在输入框里打几个字,不要先点输入框。
- ✅ 预期:输入框没有变灰,字能打进去,光标还在。
- ❌ 失败:输入框是灰的,或必须再点一次才能打字。
## 2. 回车排队,回答结束后自动发出(P0)
1. 生成中输入一句新问题,按回车。
2. 看输入框上方是否出现「已排队,回答结束后发出」。
3. 等当前回答正常结束。
- ✅ 预期:排队卡片出现;当前回答结束后,那句话自动发出,卡片消失。
- ❌ 失败:回车没反应;或回答结束后排队文字还停着不发。
## 3. 撤回(P0
1. 生成中再排一条。
2. 点「撤回」。
- ✅ 预期:卡片消失,文字回到输入框,输入框重新聚焦。
- ❌ 失败:文字丢了,或焦点不在输入框。
## 4. 两个对话面停止后无红条(P0)
1. 普通咨询生成中点停止。
2. 生时校正生成中点停止,包括还没有正文的工具阶段。
3. 生时校正点一道选择题后、下一问还没出来时点停止。
- ✅ 预期:已生成内容还在;一句灰字说明已停止、不扣点;没有红色告警条。选择题等待中一点停止就停,`busy` 马上结束。
- ❌ 失败:红色告警、气泡被标成失败、思考行凭空消失,或停止键没反应。
## 5. 停止后排队不自动发(P1)
生成中先排队,再点停止。
- ✅ 预期:排队文字回到输入框,不会自动发出。
- ❌ 失败:停止后仍把排队文字发出去。