fix(study): 회차 변경 race 제거 — $effect → 명시적 onchange 핸들러

$effect 가 examRounds fetch 전 첫 실행되며 f_qnum=1 로 reset 하는 race 가
이전 fix(lastExamRound sync) 만으로 완전히 막히지 않음. effect 자체를
제거하고 select onchange + applyNewRound 에서 명시 호출하는 onRoundChange()
로 변경. examRounds 미적재 시 (length=0) 는 skip — onMount fallback 이 처리.

이제 흐름:
- 진입 (sessionStorage prefill 만 있음) → onMount await fetch 후 fallback
  으로 next_question_number 적용
- dropdown 으로 회차 변경 → onchange={onRoundChange}
- 새 회차 입력 → applyNewRound() 안에서 직접 f_qnum=1
- examRounds 변경 (저장 후 refreshExamRounds) → 어떤 자동 reset 도 발생 안 함
This commit is contained in:
Hyungi Ahn
2026-04-28 13:14:52 +09:00
parent 5b7e06abc1
commit 0d66107743
@@ -158,21 +158,27 @@
return Array.from(set);
});
// 회차 변경 감지 → 문항 번호 자동 reset
// PR-7 fix: 회차 변경은 명시적 onchange 핸들러로 처리 (이전 $effect 패턴은
// examRounds fetch 전에 첫 실행되며 f_qnum=1 로 reset 하는 race 발생).
// 사용자가 dropdown 으로 회차를 바꿀 때 + applyNewRound() 에서만 호출.
let lastExamRound = $state('');
$effect(() => {
if (f_exam_round && f_exam_round !== lastExamRound) {
// 기존 회차면 next_question_number, 아니면 1
const found = examRounds.find((r) => r.exam_round === f_exam_round);
if (found && found.next_question_number) {
f_qnum = found.next_question_number;
} else if (!found) {
f_qnum = 1;
}
function onRoundChange() {
if (!f_exam_round) return;
if (f_exam_round === lastExamRound) return;
if (examRounds.length === 0) {
// examRounds 미적재 → onMount fallback 이 처리. 현재 호출은 skip.
lastExamRound = f_exam_round;
refreshCompleteFlag();
return;
}
});
const found = examRounds.find((r) => r.exam_round === f_exam_round);
if (found && found.next_question_number) {
f_qnum = found.next_question_number;
} else if (!found) {
f_qnum = 1;
}
lastExamRound = f_exam_round;
refreshCompleteFlag();
}
function refreshCompleteFlag() {
const found = examRounds.find((r) => r.exam_round === f_exam_round);
@@ -370,7 +376,7 @@
<label class="text-xs text-dim">회차</label>
{#if f_exam_round_mode === 'select'}
<div class="flex gap-2">
<select bind:value={f_exam_round}
<select bind:value={f_exam_round} onchange={onRoundChange}
class="flex-1 px-3 py-2 bg-surface border border-default rounded text-sm text-text outline-none focus:border-accent">
<option value="">— 선택 —</option>
{#each examRounds as r}