import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { homeSurface as page } from "./home-surface.ts"; const read = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), "utf8"); const sidebar = read("../src/components/app-sidebar.tsx"); const sidebarRow = read("../src/components/sidebar-session-row.tsx"); const starter = read("../src/components/starter-home.tsx"); const styles = read("../src/app/globals.css"); const pageSource = read("../src/app/(app)/page.tsx"); const hook = read("../src/hooks/use-rectification-surface.ts"); const sessions = read("../src/hooks/use-session-management.ts"); const caseService = read("../src/lib/rectification-agentic/v9/case-service.ts"); test("history session open uses the bound skill identity instead of the live registry version", () => { // 原值:openRectificationCase 对 session 也把 resolveActiveSkillPackage() 交给 v2 // 新值:session 先 v1 取 Case,再读绑定身份,注册表核验后把绑定身份交给 v2 // 决策 1:打开 RPC 不再要求等于当前版本 assert.match(caseService, /request\.intent === "session"/); assert.match(caseService, /open_agentic_rectification_case"/); assert.match(caseService, /get_agentic_rectification_skill_identity"/); assert.match(caseService, /resolveExactSkillPackage\(/); assert.match(caseService, /skill_identity_missing/); const homepageOpen = caseService.slice( caseService.indexOf("export async function openRectificationCase"), caseService.indexOf("export async function getRectificationEntrySummary"), ); assert.match(homepageOpen, /intent === "homepage" \|\| request\.intent === "new"/); assert.match(homepageOpen, /open_agentic_rectification_case_v2/); }); test("skill_identity_mismatch maps to 409 instead of the 500 fallback", () => { // 原值:KNOWN_RPC_ERROR_CODES 没有 mismatch,落到 校正服务暂时不可用 // 新值:409 skill_identity_mismatch,文案要求刷新 // 决策 2:修完决策 1 后不应再出现;映射只为不再露 500 assert.match( caseService, /"agentic_rectification_skill_identity_mismatch", \{ status: 409, code: "skill_identity_mismatch", message: "该校正绑定的规则版本与当前不一致,请刷新后重试" \}/, ); }); test("a history-list open failure shows under the clicked row, not on the starter card", () => { // 原值:rectificationError 只由 starter-home 画;selectSession 不切会话,表现为点了没反应 // 新值:session 失败绑到被点的那一行;首页起始卡不重复同一条 // 决策 3:错误就地显示 // 原值:const [rectificationErrorSessionId, setRectificationErrorSessionId] = useState(...) // 新值:外壳 9 个校正字段合成一个 rectification 对象;errorSessionId 仍叫 rectificationErrorSessionId,setter 仍叫 setRectificationErrorSessionId // 原因:外壳也要读的校正状态合并成一个对象,散装 useState 下降 assert.match(pageSource, /errorSessionId: rectificationErrorSessionId,/); assert.match(pageSource, /setRectificationErrorSessionId,/); // 原值:失败写在被点的侧栏行下(.session-open-error,role="alert",layout / 外壳注册 / AppSidebar 传 // openErrorSessionId + openErrorMessage),首页起始卡画 rectificationError(.rectification-entry-error) // 新值:打开失败在 assignOpenError 里弹一次 error toast(notifyError(message)),侧栏行与起始卡都不再画; // rectificationError / rectificationErrorSessionId 状态保留给输入框上方的「重新打开」卡 // 原因:2026-09-30 产品「报错和提示不要占页面布局」;BUG-621 的主语「点了要有反馈、不能没反应」由 toast 保证 const layout = read("../src/app/(app)/layout.tsx"); const shell = read("../src/hooks/use-home-shell-registration.ts"); assert.match(hook, /function assignOpenError\(message: string\) \{[\s\S]{0,160}notifyError\(message\);\n\s*setRectificationError\(message\);/); assert.match(hook, /setRectificationErrorSessionId\(intent === "session" \? exactSessionId : null\)/); assert.match(sessions, /setRectificationErrorSessionId\(null\)/); assert.doesNotMatch(layout, /openErrorSessionId|openErrorMessage/); assert.doesNotMatch(shell, /openErrorSessionId|openErrorMessage/); assert.doesNotMatch(sidebar, /openErrorSessionId/); assert.doesNotMatch(sidebarRow, /session-open-error/); assert.doesNotMatch(starter, /rectification-entry-error/); assert.doesNotMatch(styles, /\.session-open-error/); });