Single-instance ToastProvider with requestAnimationFrame countdown,
hover-pause, per-variant SVG icons, circular SVG progress ring,
and slide-in-from-bottom animation. Wraps children in layout.tsx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
트랙 1 — Pretendard 웹폰트 확실히 로드
- frontend/src/app/layout.tsx: Pretendard Variable CDN link 추가
- frontend/src/styles/theme.ts: font.sans 에 'Pretendard Variable' 우선
트랙 3 — 통계 재설계 (까먹음 → 마스터리 경로)
- backend/src/stats/stats.service.ts: masteryPath() 추가. 이번 복습부터 계속
correct 를 가정한 best-case 궤적을 10회차까지 시뮬레이션해서 각 회차별
s0/p/daysFromNow 를 반환. P >= targetP 도달 시 iterationsToTarget +
daysToTarget 도 함께 전달.
- backend/src/stats/stats.controller.ts: GET /api/stats/mastery-path 추가.
- frontend/src/lib/api.ts: MasteryPathResponse 타입 + ForgetCurveResponse
의 tag.subject 를 실제 select 에 맞춰 좁힘.
- frontend/src/components/charts/MasteryPathChart.tsx: recharts LineChart
로 X=iteration, Y=P% + 목표 수평선 + S₀ 보조 라인.
- frontend/src/app/stats/page.tsx: [마스터리 경로 | 망각 곡선] 토글.
마스터리 뷰는 'N회차 + K일 뒤 마스터 예상' 카피 + 회차별 요약 테이블.
트랙 5 — 드래그/선택 기본 금지 + 예외
- frontend/src/styles/GlobalStyle.ts: body 에 user-select:none +
input/textarea/[data-selectable='true'] 만 예외 허용. img 드래그 금지.
- frontend/src/components/ui/primitives.tsx: Selectable 컴포넌트 추가.
- review/page.tsx, review/history/page.tsx, study/history/page.tsx:
문제 제목과 메모에 data-selectable 부여 (검색용 복사 허용).
components/ui/Modal.tsx (new)
- ConfirmDialog: overlay + card backed by theme tokens, role=dialog +
aria-modal, backdrop click = cancel, stopPropagation on card.
app/subjects/[id]/page.tsx
- subjectId = Number.isFinite(raw) && raw > 0 ? raw : null
- Early return rendering InvalidWrap when subjectId is null; no API call
with NaN, no infinite 'loading' deadend for /subjects/abc.
- Tag delete uses ConfirmDialog via confirmDelete state (no confirm()).
app/subjects/page.tsx
- Native confirm()/alert() eliminated. Delete flow uses ConfirmDialog
with danger tone + ToastBar for success/failure feedback.
app/review/history/page.tsx
app/study/history/page.tsx
app/stats/page.tsx
- Fetches wrapped in useCallback, .catch() sets an error state, render
shows ErrorBlock with a '다시 시도' retry button.
- stats/page.tsx gets both subjectsError (full-page) and curveError
(inline in the curve card) plus independent retries.
- Cancelled-flag pattern (let cancelled = false) prevents stale
setState after unmount across all four pages.