Skip to content

Exams phase 2: course exams at /courses/<course>/exam - #58

Open
A7med7x7 wants to merge 2 commits into
autopilot/exams-1-question-bankfrom
autopilot/exams-2-exam-page
Open

A7med7x7 wants to merge 2 commits into
autopilot/exams-1-question-bankfrom
autopilot/exams-2-exam-page

Conversation

@A7med7x7

@A7med7x7 A7med7x7 commented Nov 4, 2026

Copy link
Copy Markdown
Member

Exam phase 2 of 6 (docs/exams.md): course exams.

Stacked on #53 (phase 1, the question bank). This PR targets that branch, so the diff shows only phase 2. Merge #53 first; GitHub then retargets this one to main.

What students get

  • /courses/<course>/exam, linked from every course page that has questions ("Take a course exam", with the best score so far on that device).
  • 20 questions drawn across all modules, in proportion to each module's size, with at least one from every module. Every attempt draws a fresh set in a new order, with shuffled options. For example, Discrete Mathematics splits 4 / 4 / 4 / 3 / 3 / 2 over its six modules.
  • No feedback until you submit, like a real exam. A numbered palette shows which questions are answered and jumps between them. Submitting with gaps asks for confirmation first and counts the gaps as wrong.
  • Exam conditions (optional): 90 seconds a question (30 minutes for 20), a visible countdown that turns orange in the last minute, screen-reader announcements at 5 and 1 minutes left, and an automatic submit at zero.
  • Results: total score, a bar per module with "Review this module", and every missed question with your answer, the right answer, the explanation, and a link to re-read the lesson.
  • Keyboard: number keys pick an answer, Enter moves on.
  • Attempts are kept in localStorage (decoded:exams:v1, last 20 per course). Syncing them to accounts is phase 6.

How it's built

  • src/lib/exams.ts: pure, seeded logic (allocate uses the largest-remainder method, plus buildExam and scoreExam) and the attempt storage.
  • src/content/questionBanks.ts: loads a course's bank on demand, so lesson pages never download question banks (checked in the build output: each bank is its own chunk).
  • src/pages/ExamPage.tsx: lazy-loaded route, registered before the lesson route. A test checks no lesson is ever named exam.

Also in this PR

Before stacking, I rebased #53 onto main, where a Computer Graphics quiz option had been reworded ("slants" → "leans sideways"). I carried that change into the graphics bank and re-checked all 259 questions against main's quiz files: identical.

Tests (37 new)

  • src/test/exams.test.ts: allocation adds up, respects limits and gives every module a question; on every real course bank, an exam has the right size, no repeats, covers every module that has questions, and keeps each question's right answer through the shuffle; seeding is deterministic; scoring by module adds up; attempts storage keeps 20 and survives corrupt data.
  • src/test/exam-page.test.tsx: a perfect 20/20 run, submitting with gaps (confirmation, then "No answer" for each and explanations shown), number keys, auto-submit when the timer runs out (fake timers), and a course with no questions yet.

Checked

npm run lint (0 errors), tsc (app and node), npm run test (52 files, 2,433 tests), npm run build. In a browser at 390 px, light and dark: course page entry, intro, a timed exam answered by keyboard, results, and a direct load of /courses/logic-design/exam. No page errors, no horizontal overflow. The selected answer was too subtle at first, so its number now gets a filled badge.


Generated by Claude Code

Exam phase 2 (docs/exams.md). Each course with a question bank gets an
exam: 20 questions drawn across its modules in proportion to their
size (at least one per module), in random order with shuffled options,
and no feedback until you submit. An optional exam-conditions timer
gives 90 seconds a question and submits when it runs out. Results show
a score per module, every missed question with your answer, the right
one and the explanation, and links back to the lessons. Attempts are
kept on the device, and course pages link to the exam with the best
score so far.

The logic is pure and seeded in src/lib/exams.ts; banks load on demand
through src/content/questionBanks.ts, so lesson pages don't download
them. Number keys pick an answer and Enter moves on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EC6Zzt5akQu7TEHdGquZzp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EC6Zzt5akQu7TEHdGquZzp

This branch has not been deployed

No deployments
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.

2 participants