fix(rectification): settled replies carry no 「已完成 N 步」 receipt; steps stay the waiting state (BUG-1137)
Consultation replies keep their receipt (scope-lock test). Two source/DOM assertions updated with original/new/reason notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bdf4c31bc4
commit
408b22749e
@@ -115,16 +115,21 @@ function RectificationMessageEntryView({
|
||||
&& message.state === "settled"
|
||||
&& !message.failed
|
||||
&& Boolean(message.text);
|
||||
// BUG-1137 (D3): the step rows are the waiting state while the reply is
|
||||
// written; once it settles the rectification reply carries no
|
||||
// 「已完成 N 步」 receipt — those steps are the system's own bookkeeping.
|
||||
// Consultation replies keep theirs (ChatMessageRow is unchanged).
|
||||
const settled = message.state === "settled";
|
||||
const displayedMessage: RenderMessage = {
|
||||
...message,
|
||||
timeline: rectificationTimelineRows({
|
||||
timeline: settled ? [] : rectificationTimelineRows({
|
||||
trace: message.activityTrace,
|
||||
receipt: message.completedReceipt,
|
||||
activity: message.activity,
|
||||
settled: message.state === "settled",
|
||||
settled,
|
||||
}),
|
||||
};
|
||||
const vargaSentence = !message.failed
|
||||
const vargaSentence = !message.failed && !settled
|
||||
? vargaSentenceFromMethods(message.completedReceipt?.methods)
|
||||
: null;
|
||||
const question = message.question;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* TASK-rectification-message-cleanup-20261001.
|
||||
* BUG-1137 (D3): a settled rectification reply carries no 「已完成 N 步」
|
||||
* receipt; while it is written the steps are its waiting state. Consultation
|
||||
* replies keep their settled receipt.
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { createElement, type MutableRefObject } from "react";
|
||||
import { renderToString } from "react-dom/server";
|
||||
|
||||
import { ChatMessageRow } from "../src/components/chat-message-row.tsx";
|
||||
import {
|
||||
RectificationMessageEntry,
|
||||
type RectificationMessageActions,
|
||||
type RenderMessage,
|
||||
} from "../src/components/rectification-message-entry.tsx";
|
||||
|
||||
const actionsRef = { current: {
|
||||
submitChoice() {}, submitStop() {}, copyMessage() {}, onFeedback() {},
|
||||
} } as MutableRefObject<RectificationMessageActions>;
|
||||
|
||||
const TRACE = [
|
||||
{ id: "s1", kind: "tool" as const, tool: "rectification-read-case", status: "done" as const, label: "看了你的资料" },
|
||||
{ id: "s2", kind: "tool" as const, tool: "rectification-record-evidence-batch", status: "done" as const, label: "记录你说的经历" },
|
||||
{ id: "s3", kind: "tool" as const, tool: "rectification-set-focus", status: "done" as const, label: "准备好下一个问题" },
|
||||
];
|
||||
|
||||
function entry(state: RenderMessage["state"]): string {
|
||||
const message = {
|
||||
role: "assistant",
|
||||
text: state === "settled" ? "记下了 3 件事。" : "",
|
||||
renderKey: "m1",
|
||||
state,
|
||||
activityTrace: TRACE,
|
||||
} as unknown as RenderMessage;
|
||||
return renderToString(createElement(RectificationMessageEntry, {
|
||||
message,
|
||||
busy: state !== "settled",
|
||||
readonly: false,
|
||||
currentQuestionFocusId: null,
|
||||
interactive: state === "settled",
|
||||
liveChoiceCard: null,
|
||||
choiceNonce: 0,
|
||||
savedTime: null,
|
||||
copied: false,
|
||||
feedback: undefined,
|
||||
actionsRef,
|
||||
}));
|
||||
}
|
||||
|
||||
test("a settled rectification reply renders no step receipt (BUG-1137)", () => {
|
||||
const html = entry("settled");
|
||||
assert.match(html, /记下了 3 件事/);
|
||||
assert.doesNotMatch(html, /已完成 \d+ 步/);
|
||||
assert.doesNotMatch(html, /consultation-run-timeline/);
|
||||
for (const step of ["看了你的资料", "记录你说的经历", "准备好下一个问题"]) assert.doesNotMatch(html, new RegExp(step));
|
||||
});
|
||||
|
||||
test("while a rectification reply is written its steps stay the waiting state (BUG-1137)", () => {
|
||||
const html = entry("streaming");
|
||||
assert.match(html, /consultation-run-timeline/);
|
||||
assert.match(html, /正在分析/);
|
||||
assert.match(html, /准备好下一个问题/);
|
||||
});
|
||||
|
||||
test("a settled consultation reply keeps its 「已完成 N 步」 receipt (BUG-1137 scope lock)", () => {
|
||||
const html = renderToString(createElement(ChatMessageRow, {
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: "回答正文。",
|
||||
text: "回答正文。",
|
||||
renderKey: "c1",
|
||||
state: "settled",
|
||||
timeline: [{ id: "method", kind: "method", status: "done", label: "已加载方法" }],
|
||||
} as never,
|
||||
}));
|
||||
assert.match(html, /已完成 1 步/);
|
||||
});
|
||||
@@ -164,16 +164,19 @@ for (const [name, streamed, stored] of [
|
||||
assert.equal(text.split(GENERIC_COLLECT_QUESTION).length - 1, 1, text);
|
||||
for (const example of EXAMPLES) assert.equal(text.split(example).length - 1, 1, example);
|
||||
assert.match(text, /例如「2015 年夏天换了工作」/);
|
||||
// The opening turn (receipt + body + stem). The side board keeps its own
|
||||
// The opening turn (body + stem). The side board keeps its own
|
||||
// 「当前盘面」 heading; that panel is not part of this turn.
|
||||
const turn = text.slice(0, text.indexOf(GENERIC_COLLECT_QUESTION) + GENERIC_COLLECT_QUESTION.length);
|
||||
assert.ok(turn.startsWith("已完成 2 步"), turn);
|
||||
// 原值: turn 以「已完成 2 步」开头;「看了你的资料」「准备好下一个问题」各出现 1 次(收起的步骤回执)。
|
||||
// 新值: 结算后无「已完成」回执,两条步骤名出现 0 次;turn 以开场正文开头。
|
||||
// 原因: BUG-1137(TASK-rectification-message-cleanup-20261001 D3)——步骤只在生成中作等待态,结算后不渲染;BUG-1050 的「回执去重」随回执一起不再出现。
|
||||
assert.ok(turn.startsWith(BODY_SENTENCES[0]!), turn);
|
||||
for (const jargon of ["大运", "盘面", "代表分钟", "精确到秒", "对话焦点", "校正记录"]) {
|
||||
assert.equal(turn.includes(jargon), false, jargon);
|
||||
}
|
||||
assert.match(text, /已完成 2 步/);
|
||||
assert.equal(text.split("看了你的资料").length - 1, 1);
|
||||
assert.equal(text.split("准备好下一个问题").length - 1, 1);
|
||||
assert.doesNotMatch(text, /已完成 \d+ 步/);
|
||||
assert.equal(text.split("看了你的资料").length - 1, 0);
|
||||
assert.equal(text.split("准备好下一个问题").length - 1, 0);
|
||||
} finally {
|
||||
await chat.close();
|
||||
}
|
||||
|
||||
@@ -165,7 +165,10 @@ test("both chat surfaces render one timeline, one live marker, and no second rec
|
||||
// 新值: 同一调用在行组件内;容器不得再每帧派生。
|
||||
// 原因: BUG-725 已结算行记忆化。
|
||||
assert.match(entrySource, /rectificationTimelineRows\(\{/);
|
||||
assert.match(entrySource, /timeline: rectificationTimelineRows/);
|
||||
// 原值: /timeline: rectificationTimelineRows/(结算后也派生步骤行)。
|
||||
// 新值: /timeline: settled \? \[\] : rectificationTimelineRows/。
|
||||
// 原因: BUG-1137(TASK-rectification-message-cleanup-20261001 D3)——校正回复结算后不再显示「已完成 N 步」,生成中照旧。
|
||||
assert.match(entrySource, /timeline: settled \? \[\] : rectificationTimelineRows/);
|
||||
assert.doesNotMatch(chatSource, /rectificationTimelineRows\s*\(/);
|
||||
assert.doesNotMatch(chatSource, /rectification-activity-failure|<CompletedActivityReceipt/);
|
||||
assert.doesNotMatch(chatSource, /label: "正在组织回答…",\s*\}\),\s*\}\s*:\s*message;/);
|
||||
|
||||
Reference in New Issue
Block a user