fix(rectification): unblock range-card tsc and restore post-adopt windows (BUG-583)
Null-check the candidate result before reading resultId so next build can ship. Adopt reconnects prospective windows on the first verify turn, and dismissing the range card now shows the collect hint. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3091,6 +3091,16 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
.rectification-composer-meta .rectification-collect-stop {
|
||||
margin: 0;
|
||||
}
|
||||
.rectification-continue-hint {
|
||||
margin: 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-caption);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.rectification-continue-hint strong {
|
||||
color: var(--color-ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
.rectification-candidates-heading { display: grid; gap: var(--space-1); }
|
||||
.rectification-candidates-heading strong { font-size: var(--type-title-sm); font-family: var(--font-display); font-weight: 600; letter-spacing: -.2px; }
|
||||
.rectification-candidates-heading span,
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
workingRectificationHouseTable,
|
||||
type RectificationCandidateResult,
|
||||
} from "@/lib/rectification-candidate-result";
|
||||
import { RectificationRangeDelivery } from "@/components/rectification-range-delivery";
|
||||
import { RangeDeliveryContinueHint, RectificationRangeDelivery } from "@/components/rectification-range-delivery";
|
||||
import {
|
||||
diffRectificationBoard,
|
||||
RECTIFICATION_BOARD_SPLIT_MIN_PX,
|
||||
@@ -1506,6 +1506,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
void submitStructuredChoice(STOP_ACTION, "stop");
|
||||
}
|
||||
|
||||
const rangeCardDismissed = Boolean(
|
||||
candidateResult
|
||||
&& dismissedRangeResultId === candidateResult.resultId
|
||||
&& !candidateResult.selectedTime
|
||||
&& savedStatus !== "accepted"
|
||||
&& savedStatus !== "confirmed"
|
||||
&& !readonly
|
||||
);
|
||||
const spokenCollectOpen = currentQuestion?.kind === "collect_spoken" && !readonly && !busy;
|
||||
const boardPeek = compactBoard && !boardOpen ? (
|
||||
<RectificationBoardPeek
|
||||
result={candidateResult}
|
||||
@@ -1652,7 +1661,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onRegenerate={() => void regenerateMessage(message)}
|
||||
/>
|
||||
)}
|
||||
{showSelectionCards && dismissedRangeResultId !== candidateResult.resultId && message.renderKey === selectionCardMessageKey && candidateResult && (
|
||||
{showSelectionCards && candidateResult && dismissedRangeResultId !== candidateResult.resultId && message.renderKey === selectionCardMessageKey && (
|
||||
<RectificationRangeDelivery
|
||||
result={candidateResult}
|
||||
acceptingCandidateId={acceptingCandidateId}
|
||||
@@ -1724,8 +1733,10 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
<span className="rectification-adopt-status__hint">之后新建对话即按此时间排盘。</span>
|
||||
</div>
|
||||
)}
|
||||
{currentQuestion?.kind === "collect_spoken" && !readonly && !busy && (
|
||||
{(rangeCardDismissed || spokenCollectOpen) && (
|
||||
<div className="rectification-composer-meta">
|
||||
<RangeDeliveryContinueHint visible={rangeCardDismissed && !busy} />
|
||||
{spokenCollectOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="rectification-collect-stop"
|
||||
@@ -1733,6 +1744,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
>
|
||||
{CHOICE_STOP_LABEL}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ChatComposer
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
rangeDeliverySensitiveCopy,
|
||||
rangeDeliveryStableCopy,
|
||||
} from "@/lib/rectification-agentic/user-copy";
|
||||
import { STEP_STATE_COPY } from "@/lib/rectification-agentic/v9/step-state";
|
||||
import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel";
|
||||
|
||||
function adoptCandidateId(
|
||||
@@ -25,6 +26,17 @@ function adoptCandidateId(
|
||||
?? null;
|
||||
}
|
||||
|
||||
export function RangeDeliveryContinueHint({ visible }: Readonly<{ visible: boolean }>) {
|
||||
if (!visible) return null;
|
||||
return (
|
||||
<p className="rectification-continue-hint" role="status">
|
||||
<strong>{STEP_STATE_COPY.collect.headline}</strong>
|
||||
{" "}
|
||||
{RECTIFICATION_USER_COPY.continueCollectFallback}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function RectificationRangeDelivery({
|
||||
result,
|
||||
acceptingCandidateId,
|
||||
|
||||
@@ -92,7 +92,7 @@ import {
|
||||
import { followupCaseArgs, isBlockChoiceSchema, isWidenWindowSchema } from "./block-scan.ts";
|
||||
import { mutateCaseForBlockChoice, mutateCaseForWidenWindow, rescoreStaleMinuteSnapshotIfNeeded } from "./block-scan-answer.ts";
|
||||
import type { SessionOutcomeKind } from "./confirmation-gate";
|
||||
import { refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import { prospectiveWindowsNarration, refinementFromDecisionReceipt } from "./refinement-packet";
|
||||
import { projectCurrentQuestion } from "./turn-decision";
|
||||
|
||||
const EXHAUSTION_DELIVERY_ACTIONS = new Set([
|
||||
@@ -143,6 +143,18 @@ function withDeferredCareerWindow(base: string): string {
|
||||
return `${base} ${extra}`.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
export function appendPostAdoptProspectiveWindows(
|
||||
narration: string,
|
||||
receipt: Readonly<Record<string, unknown>> | null | undefined,
|
||||
): string {
|
||||
const extra = prospectiveWindowsNarration(
|
||||
refinementFromDecisionReceipt(receipt).prospective_probes,
|
||||
);
|
||||
if (!extra) return narration;
|
||||
if (narration.includes(extra)) return narration;
|
||||
return `${narration} ${extra}`.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
async function withRangeReadingNarration(
|
||||
base: string,
|
||||
input: {
|
||||
@@ -1105,21 +1117,26 @@ export async function persistNextInterviewIfIdle(input: {
|
||||
caseId: input.caseId,
|
||||
askedTurnId: input.askedTurnId ?? null,
|
||||
});
|
||||
let dossier = rescored.dossier;
|
||||
const finishIdle = <T extends { hostNarration: string | null }>(result: T): T => {
|
||||
let hostNarration = result.hostNarration;
|
||||
if (dossier.case.acceptedTime && hostNarration) {
|
||||
hostNarration = appendPostAdoptProspectiveWindows(
|
||||
hostNarration,
|
||||
dossier.latestResult?.decisionReceipt,
|
||||
);
|
||||
}
|
||||
if (
|
||||
input.userStopped === true
|
||||
&& rescored.rescoreAttempted
|
||||
&& !rescored.snapshotCurrent
|
||||
&& result.hostNarration
|
||||
&& hostNarration
|
||||
) {
|
||||
return {
|
||||
...result,
|
||||
hostNarration: withLastSuccessfulCompareNotice(result.hostNarration),
|
||||
};
|
||||
hostNarration = withLastSuccessfulCompareNotice(hostNarration);
|
||||
}
|
||||
return result;
|
||||
if (hostNarration === result.hostNarration) return result;
|
||||
return { ...result, hostNarration };
|
||||
};
|
||||
let dossier = rescored.dossier;
|
||||
const staleFocus = dossier.conversationSummary.activeFocus;
|
||||
const staleFocusId = staleFocus?.id;
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user