fix(rectification): keep targeted collect cards tappable after snapshot reread (BUG-669~671)
Independent Staging Quality Gate / validate (push) Successful in 25m6s
Independent Staging Quality Gate / publish (push) Successful in 2m9s

GET dropped the A–D card when the recast had no choice_frame, so a refresh left a disabled card and a collect-wait. Project from the persisted copy, rebuild the keep frame, and send a dead choice to repair-exit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-13 21:50:35 +08:00
co-authored by Cursor
parent 3c75643b83
commit 192845ce2f
12 changed files with 426 additions and 11 deletions
@@ -68,6 +68,7 @@ import {
searchWindowFromSnapshot,
caseStageFromSnapshot,
interviewCollectWaiting,
interviewChoiceCardUnavailable,
interviewStopReasonFromSnapshot,
interviewSessionOutcomeFromSnapshot,
type RectificationCaseSnapshotPayload,
@@ -1482,6 +1483,8 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
latestLiveQuestion
&& latestLiveQuestion.options?.length === 4
&& currentQuestion?.focus_id === latestLiveQuestion.focus_id
&& choiceCard
&& choiceCard.focus_id === latestLiveQuestion.focus_id
&& (latestLiveQuestion.kind === "choice" || latestLiveQuestion.kind === "reverse_verify")
&& !busy
&& !readonly
@@ -1541,12 +1544,17 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
? currentQuestion.prompt
: null;
const resumableCase = caseStatus !== null && isResumableStatus(caseStatus);
const deadChoice = interviewChoiceCardUnavailable({
questionKind: currentQuestion?.kind,
hasChoiceCard: Boolean(choiceCard),
});
const liveQuestionOnMessages = Boolean(
latestSettledAssistant
&& latestSettledAssistant.question
&& currentQuestion
&& latestSettledAssistant.question.focus_id === currentQuestion.focus_id
&& !questionIsAnswered(latestSettledAssistant.question),
&& !questionIsAnswered(latestSettledAssistant.question)
&& !deadChoice,
);
// The gap between a settled turn and its next question has two visible
// states: `preparing` (one live timeline row, timed refetches) and
@@ -1562,11 +1570,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const questionGap = rectificationQuestionGapState({
liveQuestionVisible: liveQuestionOnMessages,
questionMissing: currentQuestion === null,
questionLoadFailed: questionSource === "unavailable",
questionPersisted: Boolean(currentQuestion?.prompt && questionSource === "focus"),
questionLoadFailed: questionSource === "unavailable" || deadChoice,
questionPersisted: Boolean(currentQuestion?.prompt && questionSource === "focus") && !deadChoice,
offerAwaitingReader: showSelectionCards && !candidateResult?.selectedTime,
nextUserActionId,
collectWaiting,
collectWaiting: collectWaiting && !deadChoice,
busy,
readonly,
regenerating: regeneratingMessageKey !== null,
@@ -1765,7 +1773,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
)
),
);
const embeddedCard = question ? choiceCardFromQuestion(question, liveQuestion ? choiceCard : null) : null;
const unansweredDeadChoice = Boolean(
question
&& !questionIsAnswered(question)
&& !liveQuestion
&& (question.kind === "choice" || question.kind === "reverse_verify")
);
const embeddedCard = question && !unansweredDeadChoice
? choiceCardFromQuestion(question, liveQuestion ? choiceCard : null)
: null;
const afterAnswer = question && displayedMessage.state === "settled"
? (
<div className="rectification-message-question">