fix(rectification): unblock range-card tsc and restore post-adopt windows (BUG-583)
Independent Staging Quality Gate / validate (push) Successful in 13m23s
Independent Staging Quality Gate / publish (push) Successful in 30m54s

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:
Jesse_Chen
2026-09-07 20:22:24 +08:00
co-authored by Cursor
parent f3e7270237
commit a00b069dcc
13 changed files with 194 additions and 16 deletions
+10
View File
@@ -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 (