From d5a3deaacbd4cfc28dde34a8c39dbb6c64775afc Mon Sep 17 00:00:00 2001 From: jmj Date: Wed, 1 Jul 2026 12:28:31 +0900 Subject: [PATCH] =?UTF-8?q?fix(interview):=20=EC=9E=90=EA=B8=B0=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20=EB=8B=B5=EB=B3=80=20=EC=98=A4=EB=A1=A4=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=20=E2=80=94=20=EB=A1=A4=EB=B0=B1=EC=9D=84=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=81=8A=EA=B9=80=EC=97=90=EB=A7=8C=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #136(#9 라이브 답변 롤백)의 회귀를 회의적 리뷰(3/3)가 발견해 수정. 문제: 낙관적 답변 프루닝 트리거는 SESSION_MESSAGE(→메시지 리페치→pendingAnswers content 매칭)뿐인데, 자기소개(모든 세션 첫 답변)는 placeholder/에코 없이 SelfIntroAnsweredEvent 만 발행되고 첫 SESSION_MESSAGE 는 Pro 모델 질문 풀 생성 이후에야 온다. 풀 생성이 10초를 넘기면 성공한 자기소개 답변에 ACK 타이머가 발화 → 오롤백 + 입력 복원 + 실패 토스트, 재제출은 SESSION_INVALID_STATE 로 드롭되어 오류 루프. 수정: 타임아웃 시 연결이 'open'이면 전송 실패가 아니라 '서버가 아직 에코 전'이므로 롤백하지 않는다(이후 SESSION_MESSAGE 가 프루닝). 연결이 끊긴(closed/connecting) 경우에만 진짜 실패로 보고 롤백. connectionRef 미러 추가. 일반 꼬리질문(즉시 에코)은 무영향, 진짜 전송 실패(연결 끊김) 안전망도 유지. build 통과, lint 신규 에러 없음. Co-Authored-By: Claude Opus 4.8 --- frontend/src/features/interview/model/useLiveInterview.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/features/interview/model/useLiveInterview.ts b/frontend/src/features/interview/model/useLiveInterview.ts index 3bd36624..a34931f4 100644 --- a/frontend/src/features/interview/model/useLiveInterview.ts +++ b/frontend/src/features/interview/model/useLiveInterview.ts @@ -53,6 +53,9 @@ export function useLiveInterview(sessionId: number, deliveryMode: DeliveryMode = // 안 되면 stuck 된 낙관적 답변을 롤백한다. tempId 별 타이머 + 최신 optimistic 미러 ref. const optimisticRef = useRef([]) optimisticRef.current = optimistic + // 타임아웃 클로저에서 최신 연결 상태를 읽기 위한 미러 ref. + const connectionRef = useRef(connection) + connectionRef.current = connection const answerTimers = useRef>>(new Map()) if (queueRef.current === null) { queueRef.current = createSegmentQueue(async (url) => { @@ -183,6 +186,11 @@ export function useLiveInterview(sessionId: number, deliveryMode: DeliveryMode = const timer = setTimeout(() => { answerTimers.current.delete(tempId) if (!optimisticRef.current.some((o) => o.tempId === tempId)) return // 이미 반영됨 + // 연결이 정상('open')이면 전송 실패가 아니라 '서버가 아직 에코 안 함'일 뿐이다. + // 특히 자기소개(첫 답변)는 프루닝 트리거인 SESSION_MESSAGE 가 Pro 모델 질문 풀 + // 생성 이후에야 오므로 10초를 넘길 수 있다 → 오롤백 금지, 이후 메시지가 프루닝한다. + // 연결이 끊긴 경우(closed/connecting)에만 진짜 전송 실패로 보고 롤백한다. + if (connectionRef.current === 'open') return setOptimistic((prev) => prev.filter((o) => o.tempId !== tempId)) setRestoreDraft({ content, nonce: Date.now() }) toast.error('답변 전송에 실패했어요. 입력을 복원했으니 다시 시도해 주세요.')