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
152 lines
5.7 KiB
TypeScript
152 lines
5.7 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
RECTIFICATION_DELIVERED_COPY,
|
|
RECTIFICATION_QUESTION_PREPARING_LABEL,
|
|
RECTIFICATION_QUESTION_RELOAD_LABEL,
|
|
RECTIFICATION_QUESTION_REPAIR_FAILED_COPY,
|
|
RECTIFICATION_QUESTION_REPAIR_LIMIT,
|
|
RECTIFICATION_QUESTION_UNAVAILABLE_COPY,
|
|
RECTIFICATION_SNAPSHOT_RETRY_LABEL,
|
|
RECTIFICATION_SNAPSHOT_UNAVAILABLE_COPY,
|
|
persistedQuestionSurface,
|
|
rectificationReadonlyRangeCopy,
|
|
type RectificationSnapshotFailure,
|
|
} from "@/lib/rectification-surface-state";
|
|
import type { ChoiceKey, RectificationChoiceCard as ChoiceCardModel } from "@/lib/rectification-agentic/v9/choice-card";
|
|
import type { CurrentQuestionModel } from "@/lib/rectification-chat-snapshot";
|
|
import type { RectificationChatView } from "@/lib/rectification-chat-view";
|
|
import { Button } from "@/components/ui/button";
|
|
import { ConsultationTimelineLiveRow } from "./consultation-run-timeline";
|
|
import { RectificationChoiceCard } from "./rectification-choice-card";
|
|
|
|
export function RectificationReadonlyRange({
|
|
range,
|
|
sessionOutcome,
|
|
discriminatingEventProbes,
|
|
contrastProbes,
|
|
}: Readonly<{
|
|
range: readonly [string, string];
|
|
sessionOutcome: string | null;
|
|
discriminatingEventProbes: unknown;
|
|
contrastProbes: unknown;
|
|
}>) {
|
|
return (
|
|
<p className="rectification-readonly-range" role="status">
|
|
{rectificationReadonlyRangeCopy({
|
|
range,
|
|
sessionOutcome,
|
|
discriminatingEventProbes,
|
|
contrastProbes,
|
|
})}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The blocks under the transcript that stand in for a missing next question:
|
|
* the standalone persisted question (only when no assistant message can carry
|
|
* it), the preparing row, the delivered note, the snapshot-failure retry, the
|
|
* repair button, and the verified-idle note. Moved out of
|
|
* RectificationAgenticChat unchanged (TASK-rectification-code-split-20260926).
|
|
*/
|
|
export function RectificationQuestionGapNotices({
|
|
view,
|
|
currentQuestion,
|
|
choiceCard,
|
|
choiceNonce,
|
|
busy,
|
|
readonly,
|
|
snapshotFailure,
|
|
questionRepairAttempts,
|
|
questionRepairing,
|
|
submitChoice,
|
|
submitStop,
|
|
refetchQuestion,
|
|
repairQuestion,
|
|
}: Readonly<{
|
|
view: Pick<RectificationChatView, "showStandaloneQuestion" | "questionGap" | "showSelectionCards" | "verifiedIdleCopy">;
|
|
currentQuestion: CurrentQuestionModel | null;
|
|
choiceCard: ChoiceCardModel | null;
|
|
choiceNonce: number;
|
|
busy: boolean;
|
|
readonly: boolean;
|
|
snapshotFailure: RectificationSnapshotFailure | null;
|
|
questionRepairAttempts: number;
|
|
questionRepairing: boolean;
|
|
submitChoice: (key: ChoiceKey) => void;
|
|
submitStop: () => void;
|
|
refetchQuestion: () => Promise<void>;
|
|
repairQuestion: () => Promise<void>;
|
|
}>) {
|
|
const { showStandaloneQuestion, questionGap, showSelectionCards, verifiedIdleCopy } = view;
|
|
return (
|
|
<>
|
|
{showStandaloneQuestion && currentQuestion?.prompt && (
|
|
<div className="rectification-message-wrap rectification-message-entry" data-testid="persisted-question">
|
|
<div className="rectification-message-question is-standalone">
|
|
{persistedQuestionSurface({
|
|
questionKind: currentQuestion.kind,
|
|
hasChoiceCard: Boolean(choiceCard),
|
|
questionPersisted: true,
|
|
}) === "choice_card" && choiceCard ? (
|
|
<>
|
|
<p className="rectification-message-question__prompt">{currentQuestion.prompt}</p>
|
|
<RectificationChoiceCard
|
|
key={`${choiceCard.question_id}:${choiceNonce}:persisted`}
|
|
variant="embedded"
|
|
card={choiceCard}
|
|
pending={busy}
|
|
disabled={busy || readonly}
|
|
selectedKey=""
|
|
onSelect={submitChoice}
|
|
onStop={submitStop}
|
|
/>
|
|
</>
|
|
) : (
|
|
<p className="rectification-message-question__prompt">{currentQuestion.prompt}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{questionGap === "preparing" && (
|
|
<div className="rectification-message-wrap rectification-message-entry rectification-question-gap">
|
|
<ConsultationTimelineLiveRow id="question-preparing" label={RECTIFICATION_QUESTION_PREPARING_LABEL} />
|
|
</div>
|
|
)}
|
|
{questionGap === "delivered" && !showSelectionCards && (
|
|
<p className="rectification-pending-note" role="status">
|
|
{RECTIFICATION_DELIVERED_COPY}
|
|
</p>
|
|
)}
|
|
{snapshotFailure && (
|
|
<div className="rectification-message-wrap rectification-message-entry rectification-question-gap" role="status">
|
|
<p className="rectification-question-gap__copy">{RECTIFICATION_SNAPSHOT_UNAVAILABLE_COPY}</p>
|
|
<Button type="button" variant="outline" onClick={() => void refetchQuestion()}>
|
|
{RECTIFICATION_SNAPSHOT_RETRY_LABEL}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{questionGap === "unavailable" && !snapshotFailure && (
|
|
<div className="rectification-message-wrap rectification-message-entry rectification-question-gap" role="status">
|
|
{questionRepairAttempts >= RECTIFICATION_QUESTION_REPAIR_LIMIT ? (
|
|
<p className="rectification-question-gap__copy">{RECTIFICATION_QUESTION_REPAIR_FAILED_COPY}</p>
|
|
) : (
|
|
<>
|
|
<p className="rectification-question-gap__copy">{RECTIFICATION_QUESTION_UNAVAILABLE_COPY}</p>
|
|
<Button type="button" variant="outline" onClick={() => void repairQuestion()} disabled={questionRepairing}>
|
|
{RECTIFICATION_QUESTION_RELOAD_LABEL}
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
{questionGap === "verified_idle" && verifiedIdleCopy && !showSelectionCards && (
|
|
<p className="rectification-pending-note" role="status">
|
|
{verifiedIdleCopy}
|
|
</p>
|
|
)}
|
|
</>
|
|
);
|
|
}
|