Files
Jyotisha/docs/tasks/PROGRESS-rectification-ux-20260903.md
T

12 KiB
Raw Blame History

PROGRESS · 生时校正会话面:消除空白假死与交互摩擦(重做,2026-09-03)

工作树:.worktrees/rectification-ux-20260903 分支:codex/rectification-ux-20260903 基线:origin/staging @ af411f59(任务书重启说明所在),收尾时 rebase 到 0cefaea6(两次均为文档提交:任务书搬家、任务状态板;无代码变化)。 任务书:docs/tasks/TASK-rectification-ux-20260902.md,以其「2026-09-03 重启说明」为准。第一次执行的分支 codex/rectification-ux-20260902 @ fec000f7 只作参考,未 cherry-pick。

未改服务端与 SQL。未改 .gitea/workflows/**。未合并 staging,未碰 main。未手写 useCallback / useMemorectification-agentic-chat.tsx 既有的原样保留;新增的 refetchQuestion / reloadQuestion / startOpeningManually 是普通函数)。BUG 编号:开工确认远端最大号 504,本轮 505–509。未复活问题槽、未复活 consult handoff。

任务 状态 commit BUG
0.2 一次揭幕、面板不重挂、深链/popstate 并入 prepare 阶段 完成 53017d41 BUG-505
0.1 入口有反馈不转圈 完成(同 commit 53017d41 BUG-505
0.3 问题槽只有生成中态、在对话流内 前半由 staging d9404976 完成(题目内嵌消息,槽已删);后半"没有等待快照这一态"随 0.2 成立
0.4 「等待服务端更新」→ live 行 + 重试 + 重新加载 完成 360989ca BUG-505
0.6 空 turns 会话的起点 完成 360989ca BUG-507
0.7 停止后的文案 完成(现状是静默返回,已补通知) 360989ca
1.4 开场 live 文案 完成 360989ca
1.5 402 跳转前提示 完成 360989ca
0.5 选择题/采用不留缝不闪卡 完成(接 beginLiveRun / rememberLiveActivity 1e50007c BUG-506
1.1 选择卡确认感 补差额完成(选中态进按钮由 35688015、内嵌变体由 d9404976 已做;本轮补对勾「已选择」与卡顶「正在记录…」行) 3c33e598 BUG-508
1.2 用户选择回显 跳过d9404976 已把题目内嵌进 assistant 消息,答过的卡以 question.answer_option 标出所选项,持久化 turn 的 question 也带 answer_option,刷新前后都能看到自己选了什么;再加一条用户气泡会重复
1.3 盘面首态 完成(文案 + 收窄 + peek;宫位表 API 不提供,未造数据) 3c33e598 BUG-509
2 DESIGN.md 完成 本 docs commit

六段空白与死角:各自的消除证据

空白 消除方式 证据
1 首页卡片无反馈 rectificationCardLabelrectificationLoading 时为「正在打开…」;卡片 data-opening + cursor: progress;侧栏目标行「打开中」+ aria-busy rectification-surface-contract → "entry feedback is static…"
2 侧栏进入:普通对话 → 空白 → 重挂 selectSession 对未打开的校正会话不先 setActiveSessionId/写 URLhook 在 /cases/openhydrateRectificationCase(一次 Case 读取 turns + 快照,BOOTSTRAP_PREPARE_TIMEOUT_MS 上限)再一次性切换;面板 key 去掉 -ready/-loading 同上 → "…revealed once…"rectification-surface-state → hydration 两条
2 深链 / 刷新 / popstate page 的 resume effect 从"揭幕后"改为"prepare 阶段起"bootstrapPrepareSettled 新增 rectificationApplicable/Settled,校正会话 hydration 完成(或 open 报错 / 资料不全)才揭幕;popstate 走 selectSession,同样延后切换 rectification-surface-contract → "…hydrated before the reveal, and popstate defers…"home-bootstrap-reveal 既有用例不变(新字段可选)
3 第一轮结束整面重挂 key 只剩 ${sessionId}-${caseId};后到的 turns 经渲染期按上一 prop 调整只填空 transcript;卸载 abort() 同上(doesNotMatch(page, /"ready" : "loading"/)seededTurns 锁)
4 快照回来前问题区空 快照随揭幕到位(initialSnapshot 初始化 statecaseSnapshotLoaded 初值 initialSnapshot !== null);面板不再挂载自拉;hydration 超时时走 0.4 的缺口重试 同上(useState(initialSnapshot !== null)、无挂载 fetch);rectification-surface-state → "a snapshot that never arrived is a gap…"
5 选择题两段缝 + 闪卡 send(action, text, continuation) 续接同一条 live 行并跳过 busy 守卫;willContinue 在同一 async 链里 await send("read_only", …)choiceContinuationPending 与 effect 删除;busy 全程 true 同上 → "…keeps one live row through the follow-up turn"
6 采用只有按钮变灰 点击即追加「正在采用 HH:MM…」行,POST → 快照 → 复用该行续接 read_only;失败移除该行 同上;rectification-surface-staterectificationAdoptingLabel
死角 空 turns 会话 rectificationConversationStateempty → 「这段校正还没有开始。」+「开始提问」(send("opening")openingStarted 守卫) rectification-surface-state → "conversation state…";契约 → "an empty Case…"
文案 「等待服务端更新」 rectificationQuestionGapStatepreparingtranscript 末尾一条时间线 live 行「正在准备下一个问题…」+ 2s 定时重拉 ≤2 次)→ unavailable(「没有拿到下一个问题。」+ 44px「重新加载」);question_source: "unavailable" 直接 unavailable;候选卡待采用时不算缺口 rectification-surface-state → 缺口三条;契约 → "…never tells the reader to wait for the server"

改动文件

  • 新建:frontend/src/lib/rectification-surface-state.ts(常量、hydration、turn 解析含 question/offer_result_id/tool_activities、会话态与缺口态纯函数、板文案、填报时间派生)、frontend/tests/rectification-surface-state.test.ts10 用例)、frontend/tests/rectification-surface-contract.test.ts8 用例)。
  • 改动:use-rectification-surface.tsuse-session-management.tshome-bootstrap.ts(可选字段)、page.tsx2 个 state、hook 参数、卡片文案、resume effect 门、prepare 判定、面板 key/props、侧栏 prop)、app-sidebar.tsxsidebar-session-row.tsxstarter-home.tsxconversational-birth-time-rectification.tsxprops 与 turn 类型)、rectification-agentic-chat.tsxconsultation-run-timeline.tsx(导出 ConsultationTimelineLiveRow)、rectification-choice-card.tsxrectification-board.tsxrectification-board-model.tsrectificationBoardPeekCopy 第二参可选)、globals.cssDESIGN.mddocs/BUG_HISTORY.mddocs/testing/staging-manual-walkthrough-20260901.md(第 8 节)、docs/tasks/README.md(索引)、BLOCKED.md

与任务书的书面偏差(均登记 BLOCKED.md)

  1. hydration 是一次 GET /api/rectification/cases/:id 套 4 秒 Promise.race,不是 Promise.allSettled([turns, snapshot])(同一端点,两次请求会重复);上限常量直接等于 BOOTSTRAP_PREPARE_TIMEOUT_MS,只有一个。
  2. turns 后到的填充与重试计数复位不用 useEffectreact-hooks/set-state-in-effect):前者渲染期按上一 prop 调整,后者在 applyCaseSnapshot(有问题到达)与 setPending(true) 里复位。
  3. 首页卡片不带 aria-busytests/home-bootstrap-reveal.test.ts 锁死 starter-home.tsx 不得出现 aria-busy={。卡片只用 data-opening + 静态文案 + cursor: progress;侧栏行仍带 aria-busy
  4. 缺口的 live 行放在 transcript 末尾(.rectification-message-wrap.rectification-question-gap),不是 staging 原来的 composer 上方状态行;.rectification-composer-status 样式删除。

1.3 核实

frontend/src/app/api/rectification/cases/[caseId]/route.ts 的 dossier 响应只带 accepted_time / confirmed_time / candidate_range,无 natal/declared 宫位表;按任务书只做文案与收窄,declaredTimepage.tsx 从 profilereportedTime || time,须为 H:MM)派生。

被触碰的既有断言(红线 4,每处源码上方均注明原值与错因)

文件 原断言 现在 理由
tests/rectification-agentic-entry.test.ts :85、:194 附近 key={…-${rectificationTurns.length > 0 ? "ready" : "loading"}} key 只含 session/CasedoesNotMatch /"ready" : "loading"/ 锁的正是重挂缺陷(BUG-505
同上 :87 hook 内联 methods: Array.isArray((turn.receipt as { methods?: unknown }).methods) parsePersistedRectificationTurns(payload?.turns) + lib 里 methods: stringList(receipt.methods) 解析器搬到 lib 供 hydration 与刷新共用(BUG-505
同上 :233:236 await loadCaseSnapshot(); choiceContinuationPending.current = true;、effect 内 void send("read_only", "") await loadCaseSnapshot(); … await send("read_only", "", { reuseAssistantRenderKey: assistantRenderKey, label: adoptingLabel })doesNotMatch /choiceContinuationPending/;原 doesNotMatch 裸调用锁保留 锁的是 effect 中转(BUG-506
同上 workspace class is-board-empty 的类模板 ${candidateResult ? "" : " is-board-empty"} 板收窄(BUG-509
tests/rectification-question-in-message.test.ts :136137 /当前没有可回答的问题,正在等待服务端更新//题目加载失败,请刷新/ doesNotMatch 两句 + 锁 RECTIFICATION_QUESTION_PREPARING_LABEL / _UNAVAILABLE_COPY 锁的是裸等待文案(BUG-505
tests/rectification-spoken-collect.test.ts "missing current_question…" showMissingQuestion = Boolean(…) 与同两句文案 rectificationQuestionGapState({ … questionMissing: currentQuestion === null … resumableCase })doesNotMatch 两句 同上;仍锁 resumable 门
tests/rectification-answer-choice.test.ts :1201 /send\("read_only", ""\)/ send("read_only", "", { reuseAssistantRenderKey: assistantRenderKey, label: recordingLabel }) 后续轮带续接行(BUG-506

新增测试:tests/rectification-surface-state.test.ts10)、tests/rectification-surface-contract.test.ts8)。

命令输出

./node_modules/.bin/tsc --noEmit0(每个 commit 前各一次,rebase 后再一次)。

npm run lint0 error73 warning;触及文件里唯一的 warning 是 rectification-agentic-chat.tsx submitStructuredChoice 的既有 exhaustive-deps(staging 同在,依赖表原本就缺 busy / actionIdForChoice,本轮未动);新建的三个文件 0 warning。

./node_modules/.bin/tsx --test tests/*.test.ts(基线在干净 af411f59 上跑):

基线 af411f59 现在
tests 2584 2602
pass 2550 2568
fail 24 24
skipped 10 10

24 条失败与基线逐条比对完全同一集合comm -3 差集为空):全部是本机无 Docker / PostgreSQL / 部署夹具的既有缺口。10 条 skipped 同基线。rebase 到 8db71aaf / 0cefaea6 只改了 md 文件,src/tests 逐字节相同,全量数字沿用;rebase 后复跑 tsc 与校正相关聚焦套件(见下)。

聚焦套件(rebase 后,15 个文件):rectification-surface-staterectification-surface-contractrectification-agentic-entryrectification-question-in-messagerectification-spoken-collectrectification-answer-choicerectification-board-modelrectification-varga-style-copyhome-bootstrap-revealchat-session-urlchat-notice-and-scroll-contractstarter-questionsclass-name-definition-contracttouch-target-contractdark-theme-contract173 / 173

./node_modules/.bin/next build0✓ Compiled successfully in 17.8sFinished TypeScript in 41sGenerating static pages (62/62)/ 仍是 ○ (Static)

未做与原因

BLOCKED.md 顶部:浏览器级手工验收(六条手工项 + 深链/刷新 + 缺口重试 + 整轮录屏)无登录态无 Chrome,已写进 docs/testing/staging-manual-walkthrough-20260901.md 第 8 节;1.3 的宫位表 API 不提供;四条书面偏差。