Acceptance fix for28113fa0(TASK-consult-conversational-answer-fix-20261006): - F1: chat-answer-detail no longer pins 'Every H2 must start its own line' (first turns carry no H2), assertion change noted in three columns. - F2: Good examples separate paragraphs with a blank line and the shape says so; .message p is pre-wrap, so a single newline only wraps. - Product request: remove the assistant avatar on every surface (consult, rectification, onboarding); --assistant-content-inset back to 0px. Full suite 4941 / fail 24, identical to baseline86cafe68minus a timing flake; build keeps / Static; first-load gzip -0.1%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
74 lines
4.4 KiB
TypeScript
74 lines
4.4 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
/**
|
||
* 原值:这份文件断言回答的第二层是一个默认收起的 `<details class="answer-detail">`
|
||
* 「完整分析」控件(`chat-answer-detail.tsx`),并锁住它的 caption、流式期
|
||
* 不渲染子节点、以及它与 technique-audit 是两套样式。
|
||
* 新值:控件与组件文件一并删除,报告层直接渲染进正文 `.answer-report-body`。
|
||
* 原因:产品在真机走查后拍板——首个 H2 之后的全部内容(也就是带推理的那一半)
|
||
* 默认不可见,用户「输出根本看不到」。回答本身就是产品,不该藏在一次点击后面。
|
||
* 技法审计表保留它自己的折叠:那是给人核对的证据,不是回复正文。
|
||
*
|
||
* 下面保留的断言是原文件里与折叠无关、仍然成立的那些(记忆化、复制取原文、
|
||
* onboarding 走同一渲染链),并新增两条锁住「不得再把回答折起来」。
|
||
*/
|
||
|
||
const contentSource = readFileSync(new URL("../src/components/chat-message-content.tsx", import.meta.url), "utf8");
|
||
const transcriptSource = readFileSync(new URL("../src/components/chat-transcript.tsx", import.meta.url), "utf8");
|
||
const onboardingSource = readFileSync(new URL("../src/components/onboarding-chat-message.tsx", import.meta.url), "utf8");
|
||
const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||
|
||
test("the report layer renders inline; the answer is never folded away", () => {
|
||
assert.match(contentSource, /<div className="answer-report-body">\{reportBody\}<\/div>/);
|
||
assert.doesNotMatch(contentSource, /AnswerDetailDisclosure/);
|
||
// 只查真正的折叠元素——解释这段历史的注释本身会提到「完整分析」四个字。
|
||
assert.doesNotMatch(contentSource, /<details/);
|
||
assert.match(globalStyles, /\.answer-report-body \{/);
|
||
assert.doesNotMatch(globalStyles, /\.answer-detail\b/);
|
||
});
|
||
|
||
test("the model is told the answer renders inline, so it stops writing for a fold", () => {
|
||
const voice = readFileSync(new URL("../src/mastra/product-voice.ts", import.meta.url), "utf8");
|
||
assert.match(voice, /renders the whole answer inline/);
|
||
assert.doesNotMatch(voice, /folds everything from the first H2/);
|
||
// 原值: assert.match(voice, /Every H2 must start its own line/)
|
||
// 新值: 不再要求(首轮正文不写 ##,这句没有对象了)
|
||
// 原因: TASK-consult-conversational-answer-20261006 D1(BUG-1244);修复单 F1
|
||
assert.doesNotMatch(voice, /Every H2 must start its own line/);
|
||
});
|
||
|
||
test("ChatMessageContent still splits spoken from report and keeps the audit fold", () => {
|
||
assert.match(contentSource, /splitSpokenAndReport\(split\.spoken\)/);
|
||
assert.match(contentSource, /: foldedAudit/);
|
||
assert.match(contentSource, /showFoldedAudit = rows\.length > 0 && split\.rows\.length === 0/);
|
||
});
|
||
|
||
test("the settled report layer stays memoised by text", () => {
|
||
// 原值/新值不变,原因仍是 BUG-725:结算态 Markdown 按文本记忆化,不每次重 parse。
|
||
assert.match(contentSource, /<div className="message-markdown">\s*<StableMarkdownPrefix text=\{report\} renderMarkdown=\{renderMarkdown\} \/>/);
|
||
assert.match(contentSource, /const reportBody = !streaming && report/);
|
||
});
|
||
|
||
test("spoken streaming still uses StreamingMarkdown so the memoised prefix stays intact", () => {
|
||
assert.match(contentSource, /streaming\s*\n\s*\? <StreamingMarkdown text=\{spoken\} renderMarkdown=\{renderMarkdown\} \/>/);
|
||
});
|
||
|
||
test("copy still sends the original message text, not the spoken layer", () => {
|
||
assert.match(transcriptSource, /onCopy=\{\(\) => actionsRef\.current\.onCopy\(feedbackKey, message\.text\)\}/);
|
||
});
|
||
|
||
test("onboarding typewriter messages still go through ChatMessageContent", () => {
|
||
assert.match(onboardingSource, /<ChatMessageContent text=\{protectedVisibleText\} \/>/);
|
||
});
|
||
|
||
test("assistant turns carry no avatar; the reply starts at the column edge (2026-10-06)", () => {
|
||
const row = readFileSync(new URL("../src/components/chat-message-row.tsx", import.meta.url), "utf8");
|
||
const onboarding = readFileSync(new URL("../src/components/onboarding-chat-message.tsx", import.meta.url), "utf8");
|
||
for (const source of [row, onboarding, globalStyles]) {
|
||
assert.doesNotMatch(source, /AgentAvatar|agent-avatar/);
|
||
}
|
||
assert.doesNotMatch(globalStyles, /--assistant-content-inset: calc\(32px/);
|
||
});
|