refactor(rectification): split the chat component into hooks, parameter functions and blocks
Move-only (TASK-rectification-code-split-20260926 T1/T4). No behavior, API,
copy, DB, Skill or scoring change.
- rectification-agentic-chat.tsx: 2043 -> 737 lines; component body
1625 -> 630; useState 35 -> 12, useRef 16 -> 9, effects 9 -> 5,
useCallback 12 -> 6.
- Snapshot sync -> hooks/use-rectification-case-snapshot.ts; board layout
and live label clock -> two small hooks.
- send / submitStructuredChoice / acceptCandidate bodies ->
lib/rectification-chat-{turn,choice,accept}-run.ts parameter functions
(bodies verbatim, deps destructured to the same names); copy/regenerate,
question repair, pure transcript and snapshot helpers and the per-render
view derivation -> lib/rectification-chat-*.ts (no React hooks).
- Question-gap blocks and the read-only range line ->
components/rectification-question-gap-notices.tsx.
- Dependency arrays kept exactly as before (lint warnings +3, listed in
PROGRESS); no dep added to appease the linter.
- Tests: whole-source contracts read tests/rectification-chat-surface.ts
(container + split files, like home-surface.ts); slices of moved code now
call the extracted functions or render the extracted block, each with a
原值/新值/原因 comment. New tests/rectification-growth-contract.test.ts.
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
12cbe6f868
commit
3a66c39f48
@@ -3,9 +3,15 @@ import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import { homeSurface as page } from "./home-surface.ts";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { rectificationChatSurface, readRectificationChatFile } from "./rectification-chat-surface.ts";
|
||||
import { RectificationQuestionGapNotices } from "../src/components/rectification-question-gap-notices.tsx";
|
||||
import { RECTIFICATION_QUESTION_PREPARING_LABEL } from "../src/lib/rectification-surface-state.ts";
|
||||
|
||||
const read = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), "utf8");
|
||||
const chat = read("../src/components/rectification-agentic-chat.tsx");
|
||||
// 原值: 读 rectification-agentic-chat.tsx 单文件。新值: rectificationChatSurface(容器 + 拆出的 hook / 参数函数 / 块,见 rectification-chat-surface.ts)。原因: TASK-rectification-code-split-20260926 只搬不改,整文件断言跟着代码走;切片断言另行改为调用函数。
|
||||
const chat = rectificationChatSurface;
|
||||
const messageEntry = read("../src/components/rectification-message-entry.tsx");
|
||||
const hook = read("../src/hooks/use-rectification-surface.ts");
|
||||
const sessions = read("../src/hooks/use-session-management.ts");
|
||||
@@ -127,9 +133,31 @@ test("the question gap is a live row with retries, then a reload; it never tells
|
||||
assert.match(chat, /if \(value\) setQuestionRetryAttempts\(0\);/);
|
||||
assert.doesNotMatch(chat, /useEffect\(\(\) => \{\s*if \(currentQuestion !== null/);
|
||||
// In flow: the gap is the last entry of the transcript, after the message loop, before the saved-time line.
|
||||
const gapIndex = chat.indexOf('{questionGap === "preparing" && (');
|
||||
assert.ok(gapIndex > chat.indexOf("{messages.map((message) => {"));
|
||||
assert.ok(gapIndex < chat.indexOf("{savedTime && savedStatus === \"confirmed\""));
|
||||
// 原值: chat 源码里 `{questionGap === "preparing" && (` 位于 `{messages.map` 之后、`{savedTime && savedStatus === "confirmed"` 之前。
|
||||
// 新值: 容器里 `<RectificationQuestionGapNotices` 位于同两个锚点之间;再真渲染该块,preparing 态出 live 行和准备文案。
|
||||
// 原因: TASK-rectification-code-split-20260926 把缺题块抽成组件,渲染位置不变;渲染断言比源码顺序更强。
|
||||
const container = readRectificationChatFile("../src/components/rectification-agentic-chat.tsx");
|
||||
const gapIndex = container.indexOf("<RectificationQuestionGapNotices");
|
||||
assert.ok(gapIndex > container.indexOf("{messages.map((message) => {"));
|
||||
assert.ok(gapIndex < container.indexOf("{savedTime && savedStatus === \"confirmed\""));
|
||||
const preparingHtml = renderToStaticMarkup(createElement(RectificationQuestionGapNotices, {
|
||||
view: { showStandaloneQuestion: false, questionGap: "preparing", showSelectionCards: false, verifiedIdleCopy: null },
|
||||
currentQuestion: null,
|
||||
choiceCard: null,
|
||||
choiceNonce: 0,
|
||||
busy: false,
|
||||
readonly: false,
|
||||
snapshotFailure: null,
|
||||
questionRepairAttempts: 0,
|
||||
questionRepairing: false,
|
||||
submitChoice() {},
|
||||
submitStop() {},
|
||||
async refetchQuestion() {},
|
||||
async repairQuestion() {},
|
||||
}));
|
||||
assert.match(preparingHtml, /class="rectification-message-wrap rectification-message-entry rectification-question-gap"/);
|
||||
assert.match(preparingHtml, /data-status="live"/);
|
||||
assert.ok(preparingHtml.includes(RECTIFICATION_QUESTION_PREPARING_LABEL));
|
||||
assert.match(styles, /\.rectification-question-gap \{/);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user