Skip to content

feat: 시험모드(일괄채점·타이머) + 회차별 진입 + 즐겨찾기 왕복토글/정렬 - #14

Merged
ginaseo merged 9 commits into
mainfrom
exam-mode-and-fixes
Jul 30, 2026
Merged

feat: 시험모드(일괄채점·타이머) + 회차별 진입 + 즐겨찾기 왕복토글/정렬#14
ginaseo merged 9 commits into
mainfrom
exam-mode-and-fixes

Conversation

@ginaseo

@ginaseo ginaseo commented Jul 30, 2026

Copy link
Copy Markdown
Owner

기능 설명

우선순위 1(시험모드)·2(회차별 진입)·3(즐겨찾기 왕복토글+정렬) 처리.

  1. 시험모드: 정답/해설 숨김, 답 자유 변경, 제한시간 선택(없음/30분/60분/150분 실전), 종료 시 일괄채점+기록
  2. 회차별 진입: 연도-회차 목록에서 실제 기출 그대로 풀기(qnum 순서 고정), 완료/진행중/미응시 상태 표시
  3. 즐겨찾기 왕복 토글: 추가/해제 다 가능하게, 세션 시작 시 기존 즐겨찾기 상태 하이드레이션
  4. 오답노트/즐겨찾기 목록을 addedAt 내림차순(최신 우선) 정렬로 변경

작업내용

  • src/lib/examStatus.ts(신설) — 회차별 완료/진행중/미응시 계산, 테스트 4개
  • src/lib/timer.tsremainingMs/formatDuration 추가, 테스트 5개
  • src/features/practice/QuestionCard.tsx — 채점표시를 showFeedback prop으로 분리, 즐겨찾기 버튼 상시 클릭 가능
  • src/repositories/QuestionRepository.tsgetExamIndex() 추가
  • src/features/practice/PracticeSetup.tsx — 모드/진입방식/회차목록/제한시간 UI
  • src/app/practice/page.tsx — 랜덤/회차별 분기
  • src/features/practice/PracticeSession.tsx — 모드별 채점/제출, 타이머, 즐겨찾기 왕복+하이드레이션
  • src/app/review/page.tsx — PracticeSession mode/timeLimitMs prop 추가, addedAt 정렬

검증: npx tsc --noEmit, npm run lint(경고 2개뿐, 기존/의도된 것), npm run build, npx vitest run(60개 전부 통과) 다 통과. Codex 리뷰 1회 돌림 — 즐겨찾기 하이드레이션 누락(P2) 지적받아 그 자리에서 수정·커밋 완료.

수동 브라우저 QA(타이머 카운트다운, 시험모드 실제 흐름, 회차 상태 갱신)는 이 세션에 브라우저 조작 도구가 없어서 못 함 — PR 머지 전 직접 확인 필요.

참고문서

docs/superpowers/plans/2026-07-31-exam-mode-and-fixes.md (로컬 전용, repo 미포함)

Summary by CodeRabbit

  • 새 기능

    • 학습 및 시험 모드를 선택해 문제를 풀 수 있습니다.
    • 랜덤 문제 또는 회차별 문제를 선택하고 시간 제한을 설정할 수 있습니다.
    • 시험 회차별 상태(미응시, 진행 중, 완료)를 확인할 수 있습니다.
    • 시험 모드에서 답안을 수정하고 제출 시 한 번에 채점할 수 있습니다.
    • 풀이 화면에 남은 시간이 표시되며, 시간 종료 시 자동으로 세션이 종료됩니다.
  • 개선

    • 오답 및 즐겨찾기 문제를 최근 추가된 순서로 표시합니다.
    • 학습 모드에서 답변 직후 해설과 정오 피드백을 제공합니다.

@github-actions github-actions Bot added the enhancement New feature or request label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ginaseo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c5811a00-a6d7-443a-bcec-2c8ea656dea9

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2e70f and 3db9807.

📒 Files selected for processing (1)
  • src/app/practice/page.tsx
📝 Walkthrough

Walkthrough

연습 설정에 학습·시험 모드, 랜덤·회차 진입, 시간 제한 및 시험 상태가 추가되었습니다. PracticeSession은 모드별 채점과 자동 종료를 처리하고, QuestionCard는 피드백 표시 여부에 따라 선택과 해설 UI를 제어합니다.

Changes

연습 설정 및 시험 상태

Layer / File(s) Summary
시험 상태와 설정 계약
src/features/practice/PracticeSetup.tsx, src/lib/examStatus.ts, src/lib/examStatus.test.ts, src/repositories/QuestionRepository.ts
PracticeSetupValue가 랜덤·회차 진입을 구분하는 유니온 타입으로 변경되고, 시험 인덱스 조회와 시도 기반 상태 계산 및 테스트가 추가되었습니다.
설정 UI와 시작값 구성
src/features/practice/PracticeSetup.tsx
학습·시험 모드, 시간 제한, 랜덤 과목·문항 수, 회차 선택 UI와 시작 조건 검사가 추가되었습니다.

연습 문제 로딩

Layer / File(s) Summary
진입 방식별 문제 구성
src/app/practice/page.tsx
랜덤 진입은 주제별 문제를 샘플링하고, 회차 진입은 examId로 문제를 조회해 qnum 기준으로 정렬합니다. 활성 세션에 모드와 시간 제한을 전달합니다.

세션 진행과 표시

Layer / File(s) Summary
모드별 채점과 종료
src/features/practice/PracticeSession.tsx, src/lib/timer.ts, src/lib/timer.test.ts
학습 모드는 선택 즉시 기록하고, 시험 모드는 제출 시 일괄 기록합니다. 시간 제한, 남은 시간 표시, 자동 종료 및 타이머 유틸 테스트가 추가되었습니다.
피드백과 복습 세션
src/features/practice/QuestionCard.tsx, src/app/review/page.tsx
showFeedback에 따라 정답 표시·선택 비활성화·해설 노출을 제어하며, 복습 항목은 addedAt 내림차순으로 정렬되고 학습 모드 설정이 전달됩니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PracticeSetup
  participant PracticePage
  participant PracticeSession
  participant ProgressRepository
  PracticeSetup->>PracticePage: 설정값 전달
  PracticePage->>PracticeSession: 질문, mode, timeLimitMs 전달
  PracticeSession->>PracticeSession: 답안 선택 및 시간 갱신
  PracticeSession->>ProgressRepository: 학습 즉시 기록 또는 시험 제출 기록
  PracticeSession-->>PracticePage: 세션 요약 전달
Loading

Possibly related PRs

  • ginaseo/passflow#6: PracticeSession의 세션 종료와 onFinish 호출 흐름을 함께 변경합니다.
  • ginaseo/passflow#8: PracticeSession, QuestionCard, /review의 즐겨찾기 및 복습 기능을 직접 수정합니다.
  • ginaseo/passflow#13: PracticeSession의 키보드 입력 처리 로직을 함께 변경합니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 시험모드, 회차별 진입, 즐겨찾기 정렬/토글이라는 핵심 변경을 잘 요약한 제목입니다.
Description check ✅ Passed 요약/테스트/참고문서에 해당하는 내용이 대부분 포함되어 있어 템플릿 요구를 거의 충족합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exam-mode-and-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/features/practice/PracticeSession.tsx (1)

141-149: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

시험모드 조기 종료 시 확인 절차 없음.

그만두기(라인 195-197)와 종료(라인 221-223) 버튼이 모두 finish()를 즉시 호출합니다. 시험모드에서는 finish()submitExam()으로 이어져 현재까지의 답안을 되돌릴 수 없이 채점·기록합니다. 실수로 클릭하면 남은 문항을 풀 기회가 사라지므로, 시험모드에서 미응답 문항이 남아있을 때는 확인 다이얼로그를 추가하는 것을 권장합니다.

♻️ 제안: 미완료 시험 조기 종료 시 확인
   function finish() {
     if (finishedRef.current) return;
+    if (mode === "exam" && Object.keys(answers).length < questions.length) {
+      const confirmed = window.confirm("아직 풀지 않은 문제가 있습니다. 지금 제출하시겠습니까?");
+      if (!confirmed) return;
+    }
     finishedRef.current = true;
     if (mode === "exam") {
       submitExam();
     } else {
       onFinish(summarizeSession(questions, answers));
     }
   }

Also applies to: 195-197, 221-223

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/practice/PracticeSession.tsx` around lines 141 - 149, Update
finish in PracticeSession to require confirmation before submitExam when mode is
"exam" and unanswered questions remain; only proceed with the existing
finishedRef guard and submission after the user confirms, while preserving
immediate completion for fully answered exams and non-exam sessions.
src/features/practice/PracticeSetup.tsx (1)

51-62: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

회차 목록 로딩이 출석 기록(getAttempts) 실패에 불필요하게 종속됨.

Promise.all로 묶여있어 getAttempts()만 실패해도(예: IndexedDB 접근 불가) getExamIndex()가 성공했더라도 exams가 빈 배열로 설정되어 회차별 진입 UI 전체가 비어 보이게 됩니다. 두 데이터는 독립적으로 처리하는 것이 더 안전합니다.

♻️ 제안: 개별 실패를 허용하도록 분리
   useEffect(() => {
-    Promise.all([questionRepository.getExamIndex(), progressRepository.getAttempts()]).then(
-      ([examList, attempts]) => {
-        setExams(examList);
-        setStatuses(computeExamStatuses(examList, attempts));
-      },
-      (err) => {
-        console.error("회차 목록을 불러오지 못했다:", err);
-        setExams([]);
-      }
-    );
+    questionRepository.getExamIndex().then(
+      (examList) => {
+        setExams(examList);
+        progressRepository
+          .getAttempts()
+          .then((attempts) => setStatuses(computeExamStatuses(examList, attempts)))
+          .catch((err) => console.error("풀이 기록을 불러오지 못했다:", err));
+      },
+      (err) => {
+        console.error("회차 목록을 불러오지 못했다:", err);
+        setExams([]);
+      }
+    );
   }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/practice/PracticeSetup.tsx` around lines 51 - 62, Update the
useEffect loading flow in PracticeSetup so getExamIndex() and getAttempts() are
handled independently instead of through Promise.all. Preserve and display the
exam list whenever getExamIndex() succeeds, while allowing getAttempts() failure
to use an appropriate empty/default status input without clearing exams; retain
error handling for exam-index failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/practice/page.tsx`:
- Around line 29-42: Attach an immediate no-op catch handler to theoryMapPromise
when it is created in the practice page flow, while still awaiting the original
promise later for its result. Ensure failures from getTheoryMap are marked
handled if getQuestions rejects before the await, without changing the existing
question-selection behavior.

---

Nitpick comments:
In `@src/features/practice/PracticeSession.tsx`:
- Around line 141-149: Update finish in PracticeSession to require confirmation
before submitExam when mode is "exam" and unanswered questions remain; only
proceed with the existing finishedRef guard and submission after the user
confirms, while preserving immediate completion for fully answered exams and
non-exam sessions.

In `@src/features/practice/PracticeSetup.tsx`:
- Around line 51-62: Update the useEffect loading flow in PracticeSetup so
getExamIndex() and getAttempts() are handled independently instead of through
Promise.all. Preserve and display the exam list whenever getExamIndex()
succeeds, while allowing getAttempts() failure to use an appropriate
empty/default status input without clearing exams; retain error handling for
exam-index failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d8debca7-8569-40fb-9564-f46747a5878e

📥 Commits

Reviewing files that changed from the base of the PR and between 137b944 and 9f2e70f.

📒 Files selected for processing (10)
  • src/app/practice/page.tsx
  • src/app/review/page.tsx
  • src/features/practice/PracticeSession.tsx
  • src/features/practice/PracticeSetup.tsx
  • src/features/practice/QuestionCard.tsx
  • src/lib/examStatus.test.ts
  • src/lib/examStatus.ts
  • src/lib/timer.test.ts
  • src/lib/timer.ts
  • src/repositories/QuestionRepository.ts

Comment thread src/app/practice/page.tsx
@ginaseo
ginaseo merged commit 8420e66 into main Jul 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant