fix(rectification): keep the adopt-card lock in state so lint can pass (BUG-622)
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

Writing selectionOfferLockRef during render tripped react-hooks/refs after the two contract tests went green. The lock now updates like seededTurns, and nextSelectionCardLock reuses the same object when the key is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 19:34:39 +08:00
co-authored by Cursor
parent 3582c3ea99
commit 15cede97e7
5 changed files with 27 additions and 10 deletions
@@ -519,7 +519,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const stepStartedAtRef = useRef(0);
const liveToolRef = useRef<string | null>(null);
const liveBaseLabelRef = useRef("正在处理…");
const selectionOfferLockRef = useRef<SelectionCardLock | null>(null);
const [selectionOfferLock, setSelectionOfferLock] = useState<SelectionCardLock | null>(null);
const [compactBoard, setCompactBoard] = useState(false);
const [boardOpen, setBoardOpen] = useState(false);
const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null));
@@ -1458,14 +1458,19 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const persistedOfferKey = [...messages].reverse().find((message) => message.candidateOffer)?.renderKey;
const liveSelectionCardKey = persistedOfferKey
?? (canOfferCards && !candidateResult?.selectedTime ? latestSettledAssistant?.renderKey : undefined);
selectionOfferLockRef.current = nextSelectionCardLock(selectionOfferLockRef.current, {
// Same render-time store update as seededTurns: the lock must be current
// before this paint (BUG-620). A ref write here trips react-hooks/refs.
const nextOfferLock = nextSelectionCardLock(selectionOfferLock, {
resultId: candidateResult?.resultId,
key: liveSelectionCardKey,
});
if (nextOfferLock !== selectionOfferLock) {
setSelectionOfferLock(nextOfferLock);
}
const selectionCardMessageKey = resolveSelectionCardMessageKey({
persistedOfferKey,
fallbackKey: latestSettledAssistant?.renderKey,
locked: selectionOfferLockRef.current,
locked: nextOfferLock,
resultId: candidateResult?.resultId,
selectedTime: candidateResult?.selectedTime,
canOffer: canOfferCards,
@@ -203,7 +203,10 @@ export function nextSelectionCardLock(
input: { resultId: string | null | undefined; key: string | undefined },
): SelectionCardLock | null {
if (!input.resultId) return null;
if (input.key) return { resultId: input.resultId, key: input.key };
if (input.key) {
if (current?.resultId === input.resultId && current.key === input.key) return current;
return { resultId: input.resultId, key: input.key };
}
if (current?.resultId === input.resultId) return current;
return null;
}