Skip to content

feat(frontend): 오답노트 복습 드릴 + 연습 답변 유실 수정 (A-7) - #179

Merged
i3months merged 1 commit into
devfrom
feature/practice-drill-and-persistence
Aug 19, 2026
Merged

feat(frontend): 오답노트 복습 드릴 + 연습 답변 유실 수정 (A-7)#179
i3months merged 1 commit into
devfrom
feature/practice-drill-and-persistence

Conversation

@i3months

Copy link
Copy Markdown
Member

변경 사항

지난 PR(#178)에서 미뤄둔 드릴 추출과 감사 백로그 A-7을 함께 정리했다. 둘 다 같은 코드(연습 러너)를 건드려서 한 묶음으로 갔다.

1. 드릴 상태 기계 추출

usePracticeSession 안에 섞여 있던 "한 문제씩 넘기며 답을 적고 정답을 확인하는" 상태를 useQuestionRunner(questionIds, storageKey?) 로 뽑았다. 질문 id 목록만 받고 질문을 어디서 얻는지는 모른다.

shared/hooks 에 뒀다. 처음엔 features/practice 에 두고 오답노트에서 가져다 썼는데, features 끼리 import 는 FSD 위반이다(frontend/src/features/CLAUDE.md §3). 도메인 타입에 의존하지 않으므로 shared 가 맞는 자리다.

PracticeRunner 컴포넌트는 손대지 않았다 — 정적 질문 은행을 쓰는 전체 화면 레이아웃 그대로고, 내부 상태만 공용 훅으로 바뀌었다. #178 에서 "이 리팩터를 섞으면 연습 모드까지 회귀 위험"이라고 적었던 부분이라, 이번엔 훅만 갈라내고 표현은 건드리지 않는 선으로 제한했다.

2. A-7 — 연습 모드 답변이 새로고침하면 사라지던 문제

storageKey 를 주면 답변 메모를 localStorage 에 남긴다. 사용자가 직접 쓴 것이라 잃으면 손실이 크다(질문 목록은 다시 뽑으면 그만). 트랙별로 키를 나눈다.

  • 저장 실패(용량 초과·프라이빗 모드)는 무시한다 — 드릴 자체가 멈추면 안 된다
  • 읽을 때 형식을 검사한다. 다른 코드가 같은 키를 썼거나 형식이 바뀌어도 빈 상태로 시작할 뿐

reset() 에서 removeItem 을 호출했다가 걷어냈다. answers 변경으로 저장 이펙트가 곧바로 "{}" 를 다시 써서 무의미한 코드였다 — 테스트가 잡았다. 저장은 이펙트 하나가 단독으로 책임진다.

3. 오답노트 복습 드릴

목록 위 '복습 시작' 으로 진입. 한 문제씩 다시 답해 본 뒤 정답 확인을 눌러야 그때 내 답변·모범 답안·코칭이 보인다. 여기 적는 답도 localStorage 에 남는다.

기록이 없는 질문(답변 전에 담았거나 피드백 생성 전)도 드릴에 포함하고, 펼쳤을 때 이유를 알려준다.

테스트

  • useQuestionRunner.test.ts 신설 (7) — 순서·마지막·완료, 다음 질문에서 정답 재차단, 저장/복원, storageKey 없으면 미저장, reset, 망가진 저장값 방어 3종(non-JSON / 배열 / 문자열 아닌 값 섞임), 질문 목록이 짧아질 때 범위 접힘
  • BookmarkDrill.test.tsx 신설 (6) — 정답 확인 전 미노출, 다음 질문에서 재차단, 기록 없는 질문 안내, 완료 화면, 나가기, 재진입 후 답변 유지

vitest 111/111 · eslint 0 · tsc · build 통과.

영향 범위

  • DB 마이그레이션: 없음
  • API contract 변경: 없음 (프론트 전용)
  • 환경변수: 없음
  • localStorage 키 신설: stackup:practice-answers:{track}, stackup:bookmark-drill-answers

리뷰어 체크포인트

  • 질문 목록이 짧아졌을 때 index 를 렌더 중 파생으로 접었다(Math.min). 이펙트로 되돌리면 한 프레임 깜빡이고 react-hooks/set-state-in-effect 에도 걸린다
  • 답변 메모를 서버가 아니라 localStorage 에 둔 판단 — 연습 모드는 서버리스(정적 JSON)라 서버에 붙이면 그 전제가 깨진다. 기기 간 동기화가 필요하다면 별도 논의

## 드릴 상태 기계 추출 (#178 에서 미뤘던 것)

`usePracticeSession` 안에 섞여 있던 "한 문제씩 넘기며 답을 적고 정답을 확인하는" 상태를
`useQuestionRunner(questionIds, storageKey?)` 로 뽑았다. 질문 id 목록만 받고 질문을 어디서
얻는지는 모른다.

**`shared/hooks` 에 뒀다.** 처음엔 `features/practice` 에 두고 오답노트에서 가져다 썼는데,
features 끼리 import 는 FSD 위반이다(§3). 도메인 타입에 의존하지 않으므로 shared 가 맞다.

`PracticeRunner` 는 손대지 않았다 — 정적 질문 은행을 쓰는 전체 화면 레이아웃 그대로고,
내부 상태만 공용 훅으로 바뀌었다.

## A-7 — 연습 모드 답변이 새로고침하면 사라지던 문제

`storageKey` 를 주면 답변 메모를 localStorage 에 남긴다. 사용자가 직접 쓴 것이라 잃으면
손실이 크다(질문 목록은 다시 뽑으면 그만). 트랙별로 키를 나눈다.

- 저장 실패(용량 초과·프라이빗 모드)는 무시한다 — 드릴이 멈추면 안 된다
- 읽을 때 형식을 검사한다. 다른 코드가 같은 키를 썼거나 형식이 바뀌어도 빈 상태로 시작할 뿐

`reset()` 에서 `removeItem` 을 호출했다가 걷어냈다 — answers 변경으로 저장 이펙트가 곧바로
`"{}"` 를 다시 써서 무의미했다(테스트가 잡았다). 저장은 이펙트 하나가 책임진다.

## 오답노트 복습 드릴

목록 위 '복습 시작' 으로 진입. 한 문제씩 다시 답해 본 뒤 **정답 확인**을 눌러야 그때 내 답변·
모범 답안·코칭이 보인다. 여기 적는 답도 localStorage 에 남는다.

기록이 없는 질문(답변 전에 담았거나 피드백 전)도 드릴에 포함하고, 펼쳤을 때 이유를 알려준다.

## 테스트

- `useQuestionRunner.test.ts` 신설 (7) — 순서·마지막·완료, 다음 질문에서 정답 재차단,
  **저장/복원**, storageKey 없으면 미저장, reset, **망가진 저장값 방어 3종**,
  질문 목록이 짧아질 때 범위 접힘(오답노트에서 항목을 빼는 경우)
- `BookmarkDrill.test.tsx` 신설 (6) — 정답 확인 전 미노출, 다음 질문에서 재차단,
  기록 없는 질문 안내, 완료 화면, 나가기, 재진입 후 답변 유지

vitest 111/111 · eslint 0 · tsc · build 통과.
@i3months
i3months merged commit 84466e9 into dev Aug 19, 2026
5 checks passed
@i3months
i3months deleted the feature/practice-drill-and-persistence branch August 19, 2026 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant