Files
Jyotisha/frontend/src/components/rectification-question-gap-notices.tsx
T
Jesse_ChenandClaude Opus 5.5 3a66c39f48 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
2026-09-26 19:23:52 +08:00

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>
)}
</>
);
}