"use client"; /** * The roadmap's practice loop: a progressive quiz (question first, reveal, * honest self-mark, score at the end) and spaced-repetition flashcards * (flip, then "knew it / again" drives a 1d/3d/7d Leitner schedule with a * due-today queue). Replaces the old static lists that printed answers and * card backs in plain sight. All state persists through the roadmap the * same way topics do. Editorial ledger styling, no accent colour. */ import Link from "next/link"; import { useMemo, useState } from "react"; import { dueFlashcards, recordRoadmapQuizResult, reviewFlashcard, type LearnAnythingRoadmap, type RoadmapQuizResult, } from "@/lib/phase3-learning"; import { buildStudyIntentUrl } from "@/lib/study-intents"; export function RoadmapPracticeSection({ roadmap, onChange, onQuizSubmitted, }: { roadmap: LearnAnythingRoadmap; onChange: (next: LearnAnythingRoadmap) => void; onQuizSubmitted?: (result: RoadmapQuizResult) => void; }) { return (

Practice and revision

{roadmap.practiceTask}

); } function FlashcardReview({ roadmap, onChange, }: { roadmap: LearnAnythingRoadmap; onChange: (next: LearnAnythingRoadmap) => void; }) { const due = useMemo(() => dueFlashcards(roadmap), [roadmap]); const [reviewing, setReviewing] = useState(false); const [flipped, setFlipped] = useState(false); const card = due[0]; const retired = roadmap.flashcards.filter((c) => roadmap.srState?.[c.front]?.retired).length; function handleMark(remembered: boolean) { if (!card) return; setFlipped(false); onChange(reviewFlashcard(roadmap, card.front, remembered)); } return (

Flashcards

{due.length} due · {retired} retired

{!reviewing ? (

{due.length > 0 ? `${due.length} card${due.length === 1 ? "" : "s"} due for review. Remembered cards come back in 1, 3, then 7 days.` : "All caught up — nothing due right now. Cards return on their schedule."}

{due.length > 0 ? ( ) : null}
) : card ? (

Card · {due.length} left

{card.front}

{flipped ? ( <>

{card.back}

) : ( )}
) : (

Session done — everything due is reviewed. Come back tomorrow.

)}
); } function ProgressiveQuiz({ roadmap, onChange, onQuizSubmitted, }: { roadmap: LearnAnythingRoadmap; onChange: (next: LearnAnythingRoadmap) => void; onQuizSubmitted?: (result: RoadmapQuizResult) => void; }) { const questions = roadmap.quiz; const [state, setState] = useState<"idle" | "running" | "done">("idle"); const [index, setIndex] = useState(0); const [revealed, setRevealed] = useState(false); const [gotIt, setGotIt] = useState([]); const last = roadmap.quizResult; function start() { setState("running"); setIndex(0); setRevealed(false); setGotIt([]); } function mark(correct: boolean) { const marks = [...gotIt, correct]; if (index + 1 < questions.length) { setGotIt(marks); setIndex(index + 1); setRevealed(false); return; } const missed: string[] = []; questions.forEach((q, i) => { if (!marks[i]) missed.push(q.question); }); const next = recordRoadmapQuizResult(roadmap, { correct: marks.filter(Boolean).length, total: questions.length, missedQuestions: missed, }); onChange(next); if (next.quizResult) onQuizSubmitted?.(next.quizResult); setGotIt(marks); setState("done"); } const question = questions[index]; const correctCount = gotIt.filter(Boolean).length; const missed: string[] = []; questions.forEach((q, i) => { if (!gotIt[i]) missed.push(q.question); }); return (

Quiz

{last ? (

last run {last.correct}/{last.total}

) : null}
{state === "idle" ? (

{questions.length} questions. See each answer only after committing, then mark yourself honestly.

) : null} {state === "running" && question ? (

Q {String(index + 1).padStart(2, "0")} / {String(questions.length).padStart(2, "0")}

{question.question}

{revealed ? ( <>

{question.answer}

Did you have it?

) : ( )}
) : null} {state === "done" ? (

{correctCount}/{questions.length}

{missed.length === 0 ? "Clean run — everything held." : "Missed questions to close out:"}

{missed.length > 0 ? ( <>
    {missed.map((q) => (
  • {q}
  • ))}
Practice the missed ones in Study Chat ) : null}
) : null}
); }