Files
Jyotisha/PROGRESS-chat-streaming-ux-20260901.md
T
Jesse_ChenandClaude Fable 5.1 eeb82dfdc4 docs(chat): record streaming-ux progress, block task 3 on batch three, align DESIGN.md
DESIGN.md gains the streaming-states table, the thinking-collapse and
label-swap motion rows, the 17px body correction and the single live
marker rule; task 3 is registered in BLOCKED.md until the home split
batch three lands; PROGRESS lists every touched assertion with its
former value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-02 04:29:01 +00:00

96 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROGRESS · Agent 聊天流式体验与双会话面统一(2026-09-01)
工作树:`.worktrees/streaming-ux-20260901`
分支:`codex/streaming-ux-20260901`
基线:`origin/staging` @ `fbb80fa3`(任务书写 `80e77361`,开工时远端已多一条任务书本身的 docs commit)。
未改 `.gitea/workflows/**`。未合并 staging,未碰 main。未手写 `useCallback` / `useMemo``rectification-agentic-chat.tsx` 既有的 `useCallback` 原样保留)。未动服务端事件语义;校正 `thinking.delta``stream-mapping.ts` 公开边界的丢弃原样保留。`page.tsx` 一行未动。
| 任务 | 状态 | commit | BUG |
| --- | --- | --- | --- |
| 0 · 流式按帧合并 + 匀速吐字 + Markdown 前缀记忆 | 完成 | `dd4e3b37` | BUG-473 |
| 1 · 结算不闪、时间线开合过渡、流式可折叠、queued 行 | 完成 | `69d19e0f` | BUG-474、BUG-475 |
| 2 · 校正接入同一套时间线、删三条死路径、移除 thinking-orbs | 完成 | `cc17d971` | BUG-476 |
| 3 · 校正复用 ChatComposer / 统一滚动跟随 / JumpToLatest / 900px | **未做**,见 `BLOCKED.md` | — | BUG-476/475 未占号 |
| 4 · DESIGN.md | 与 0/1/2 对应的条目已改;与 3 对应的条目随 3 延后 | 本文件同 commit | — |
## 任务 0 · 流式渲染
改动:`src/lib/stream-frame-buffer.ts`(新)、`src/lib/chat-markdown-split.ts`(新)、`src/components/chat-message-content.tsx``src/components/chat-message-row.tsx``streaming` prop)、`src/hooks/use-consultation-run.ts``src/components/rectification-agentic-chat.tsx`
- 两个会话面的事件回调都不再直接 `setState`:事件写入累加器,`requestAnimationFrame` 每帧最多一次 flush`run.completed` / `run.failed` / abort / 读流结束时 `settle()` 同步冲干净;`finally``dispose()`;校正 `attempt.reset``reset()`
- 释放节奏:`max(2, ceil(积压 ÷ 12))` 字/帧,积压按「新文本到达时的未释放量」记,所以 3,000 字突发恰好 12 帧追平(测试锁死),稳态约落后网络 12 帧(≈200ms);页面隐藏走 250ms `setTimeout` 并一次放完。
- 重活只做一次:`parseAgentReply` / `applyThinkingSectionProgress` 只在 flush 里对已释放文本跑。
- Markdown:流式时在最后一个空行切「稳定前缀 / 尾块」,不切进代码围栏、同一列表的项之间、表格行之间;前缀交给 `memo``StableMarkdownPrefix`,只有尾块每帧 parse;切分对象是 `splitSpokenAnswerAndTechniqueAudit(text).spoken`,技法审计表留在审计折叠里。结算后仍整篇一次 parse,DOM 结构与流式时一致(react-markdown 不加包裹元素)。
- 没有另写"薄 hook"`use-consultation-run` 按前两轮拆页公证是"内部 0 个 React hook",缓冲器按每次 send 创建/销毁的工厂用法更贴合,两处调用都是这个形态。
渲染次数探针(`tests/home-streaming-render-split.test.ts` 新增用例,假 rAF 驱动):200 个单字 token、每帧到 4 个、共 50 帧 → **`streamingRowRenders = 50`**(改前的架构每 token 一次 = 200)。`tests/stream-frame-buffer.test.ts` 同一场景 flush 50 次,且每次释放 ≥2 字、结算后与网络全文一致。
Performance 面板录制(3k 字回答无 >50ms 长任务)**未做**:无登录态、无 Chrome,见 `BLOCKED.md`
## 任务 1 · 结算与时间线
改动:`src/components/chat-transcript.tsx`(重写)、`src/lib/chat-message-view.ts``latestAssistantView`)、`src/lib/home-streaming-render-probe.ts``latestEntryMounts`)、`src/components/consultation-run-timeline.tsx`(重写)、`src/components/chat-message-row.tsx`GSAP 0.16)、`src/app/globals.css``scripts/home-streaming-render-benchmark.mts`(改用 `LatestAssistantEntry`)。
- `ChatTranscript` = `SettledMessageList excludeLatestAssistant` + 一个 `LatestAssistantEntry key={renderKey}`。同一个 `MessageEntry` 渲染历史行、流式行、刚结算行,动作条与追问只在 settled 出现。`renderKey` 在流式(`message-N`,N=历史条数)与结算(末条下标 N)下相同,纯函数 `latestAssistantView` 有测试锁。
- `.message` 的 CSS `message-enter` 关键帧删除,只剩 GSAP 一份,时长 0.18 → 0.16DESIGN.md §6 = 160ms)。
- 时间线:去掉 `key={live…}`;外层 `<section>` + `button[aria-expanded][aria-controls]``open = userOpen ?? live`;折叠是 `grid-template-rows 0fr↔1fr` 180ms,折叠后内容 `inert`summary 文案换行 120ms 淡入;live 行与 summary 文案共用既有 `agent-activity-status__text` shimmer`rows` 为空且 live 时渲染 `QUEUED_TIMELINE_ROW`「正在处理…」。reduced-motion 下三处过渡为 0(全局 `*` 规则之外再显式写了一次)。
- `latestEntryMounts` 探针只能在浏览器里手动读(无 DOM 测试库,`renderToString` 不跑 effect),见 `BLOCKED.md`
## 任务 2 · 双会话面统一
改动:`src/lib/rectification-timeline-adapter.ts`(新)、`src/components/rectification-agentic-chat.tsx``src/components/agent-activity-status.tsx`(重写为兜底)、`src/components/chat-message-row.tsx``src/app/globals.css``package.json` / `package-lock.json``npm uninstall thinking-orbs`);删除 `consultation-thinking-report.tsx``thinking-step-tree.tsx``completed-activity-receipt.tsx`
- 适配:tool trace → `calculate` 行(started=live / completed=done,标签沿用 `RECTIFICATION_TOOL_*_LABELS`);`failedTool` 的行标签加「未完成」且不挂 chips;回执 `methods``PUBLIC_RECTIFICATION_METHOD_LABELS` 去重 ≤8 作为最后一条完成 calculate 行的 `sources`;没有 live 行且 `activity.label` 以「正在」开头时补一条 live 行(`answer-composition``write`,其余 → `calculate`);think 类 trace 项映射为 think 行(现网不会出现,服务端丢弃)。
- 校正结算后与普通会话一样收成「已完成 N 步」一行,方法 chips 在行内展开;第二个回执折叠块、消息上方红字失败行、重新生成时的假「正在组织回答…」全部删除;失败走既有 `error` 通知;重新生成显示 queued 行。
- `chat-message-row.tsx` 只剩 timeline 一条主路径 + `AgentActivityStatus` 兜底(无 trace 分支、无 orb);live 标记全部 `InlineSpinner``.conversation.is-rectification .agent-thinking-step/.agent-thinking-marker` 14px 覆写删除。
- 校正的「已用时 N 秒」计时(`ActivityElapsed`)随 trace 面板一起退出——时间线本来就没有这个元素,两面一致优先。
## 任务 3 · 未做
`git log origin/staging -- frontend/src/app/page.tsx` 最新 `551d6317`(第二批),第三批未合入。按红线 7 登记 `BLOCKED.md`,未占 BUG-476/475。
## 任务 4 · DESIGN.md
已改:§3 思考正文 13px 写成显式例外;§5 Message 正文 16px → 17px(≤767px 时 16px,与 `globals.css` 一致)、Motion 改为"只有 GSAP 一份 160ms、结算不重放"、新增「Streaming states」七态表(含 failed 不用消息内红字、pacing 公式、reduced-motion 行为、校正只差不显示思考正文);§6 表新增 Thinking collapse 180ms / Timeline label swap 120ms / Text reveal;§9 等待表把时间线 live 步写成两面共用,注明 live 标记只有 `InlineSpinner`、orb 已移除。
随任务 3 延后:§4 阅读宽度(720 vs 900)、§5 composer 共用一句、§5 Jump to latest 条目、§7 `--shadow-soft`
## 被触碰的既有断言(红线 3,每处源码上方均有原值与错因注释)
| 文件 | 原断言 | 现在 | 理由 |
| --- | --- | --- | --- |
| `tests/home-streaming-render-split.test.ts` | `split.streamingRowRenders === tokens.length` | `≤ tokens.length && ≥ 1`,另加按帧驱动的新用例 | 原值是"每 token 一次提交"的现状快照(BUG-473 |
| `tests/chat-bundle-splitting-contract.test.ts` | `duration: 0\.18` | `0\.16` | DESIGN.md §6 160ms0.18 是 GSAP 副本漂移(BUG-474 |
| `tests/session-conversation-layout.test.ts` | `duration:\s*0\.18` | `0\.16` | 同上 |
| `tests/class-name-definition-contract.test.ts` | `knownUnstyled``consultation-run-timeline__summary``consultation-step-tree` | 两条移除 | 前者现在有样式(按钮),后者类名已随组件删除;该测试本身要求列表无过期项 |
| `tests/chat-bundle-splitting-contract.test.ts` | "the thinking orb loads on demand…"`OrbState` 导入、`dynamic(...)`、20px 占位、`prefetchOnIdle(importThinkingOrb)` | 改为"live 标记是共享 InlineSpinner,无 orb chunk" | orb 是第二种 live 标记且被 14px 裁切(BUG-476 |
| `tests/chat-stream-layout.test.ts` | `reportSource`/`treeSource` 读取;`<ThinkingOrb … size={20}``ConsultationThinkingReport``hasTrace``activityTrace={message.activityTrace}``activityTrace ?? []``function TraceStep``.agent-thinking-step.is-think:has(...)`、report/tree 内容锁、`consultation-step-tree__*` CSS 锁、`.consultation-thinking-report .message-thinking` | 改为锁"只剩一条路径"(`doesNotMatch` 这些符号)+ `InlineSpinner size={12}` | 锁的正是被删除的第二/第三条思考渲染路径(BUG-476) |
| `tests/rectification-agentic-entry.test.ts` | 读取 `completed-activity-receipt.tsx` 并锁其 `<details>`/不 open/「本轮完成 · N 个步骤」;`activityStatus /activityTrace/` | 改为锁 `chat /rectificationTimelineRows/``chat /completedReceipt/``activityStatus` 不含 `activityTrace` | 锁的是校正专属第二折叠块与 trace 面板(BUG-476 |
| `tests/rectification-activity-receipt.test.ts` | `deltaBranch``state: raw.trim() ? "streaming" : "thinking"``text: raw,``message.state === "settled" && message.failed` | 状态派生改锁 frame flush 段(`state: text.trim() ? …``text,``frames.setAnswer(raw)`);失败行改锁 `&& !message.failed` 且无 `rectification-activity-failure` | 前者锁的是每 chunk 一次 setMessagesBUG-473),后者锁的是校正专属红字失败行(BUG-476)。**注**:这个文件在任务 0 的聚焦跑里漏跑了,任务 2 跑全量前才发现,改在 `cc17d971` |
新增测试:`tests/stream-frame-buffer.test.ts`7)、`tests/chat-markdown-split.test.ts`4)、`tests/chat-stream-settle-contract.test.ts`4)、`tests/rectification-timeline-adapter.test.ts`6)、`home-streaming-render-split` +1。
## 命令输出
`./node_modules/.bin/tsc --noEmit`**0**(任务 0/1/2 各跑一次,均无诊断)。
`./node_modules/.bin/eslint` 触及文件 → **0 error**`use-consultation-run.ts` 有 9 条既有 `no-unused-vars` warning(未使用的导入/`startNewChat`),本轮只加了一行 import,与 `origin/staging` 一致;`stream-frame-buffer.ts` 一条 `dirty` 未用已在本 commit 清掉。
`./node_modules/.bin/tsx --test tests/*.test.ts`
| | 基线(开工时 `fbb80fa3` | 现在 |
| --- | ---: | ---: |
| tests | 2460 | 2482 |
| pass | 2426 | 2448 |
| fail | 24 | 24 |
| skipped | 10 | 10 |
24 条失败逐条比对**完全同一集合**(`comm` 差集为空):全部是本机无 Docker / PostgreSQL / 部署夹具的数据库、Better Auth、staging workflow YAML、live sync 类(admin code functions、Owner recovery、local PostgreSQL、redeem security、personal report job/sections RLS、migration runner、v9 migration 等),与上一轮 PROGRESS 记录的环境缺口同类。10 条 skipped 同基线。无产品逻辑新增失败。
`./node_modules/.bin/next build`**0**`✓ Compiled successfully in 21.7s``Finished TypeScript in 58s``Generating static pages (62/62)``/` 仍是 `○ (Static)`。5 条既有 `skill-package-registry.ts` filesystem-tracing warning,未失败。
## 未做与原因
`BLOCKED.md` 顶部三条:任务 3 等第三批拆页;浏览器级手工验收(Performance 录制、结算录屏、两面并排截图、流式期间折叠)无登录态无 Chrome;`latestEntryMounts` 探针无 DOM 测试库不可驱动。