Changelog
v0.5.0 (2026-05-07)
Contributors: Philipp Emmo Tobias Risius (@PhilippRisius)
Developed with assistance from Claude (Anthropic) — see commit trailers for per-commit attribution.
Note
Compatibility: a v0.5.0 release notebook embeds a redacted
answer key (no correct flags) that v0.4.0’s display JS does
not tolerate. Students rendering a v0.5.0-generated assignment
must install nbgrader-jupyterquiz ≥ v0.5.0. Instructors can
still autograde older releases with v0.5.0 — the autograder
side is unaffected.
Changes
Parser rework — paired delimiters (
(...),[...],{...},<...>) balance their own pair, so(Correct (with caveats))parses as one feedback field with contentCorrect (with caveats). Other delimiter characters inside a paired field are inert:(feedback { )parses cleanly. Backslash escapes (\(,\),\\) handle deliberately unmatched same-pair characters, so an emoticon like:(inside feedback can be written as:\((PR/26).Same-character delimiters (
"...") accept\"for a literal"and\\for a literal\; other backslash sequences (\int,\alpha, etc.) pass through unchanged so LaTeX content authors don’t need to double their backslashes (PR/26).Multi-line content is supported in any field — continuation lines must be indented strictly deeper than the opener (matching markdown-list semantics). Code blocks (``
`...`) ignore the indentation rule and tolerate any character until the closing triple-backtick, so multi-line fenced code works without escaping. An unclosed field raises a clearer ``ParseErrornaming the missing delimiter (PR/26).Documentation reorganised along Diataxis: the toctree groups pages under Get started, Tutorial, How-to, Worked examples, Reference, Explanation, and Project captions. The ad-hoc
usagelanding page is removed; the example downloads moved to a proper Worked examples.New Tutorial page walks through install → quiz authoring → release → submit → autograde end-to-end in about ten minutes.
New How-to section with three focused recipes: displaying a quiz without nbgrader, writing a numeric range question, and mixing graded with manually-graded content in one task cell.
Graded Quizzes “Where the answer key lives” section gains a Threat model subsection spelling out what the redaction protects against (DOM inspection, source reading, base64 reverse-engineering) and what it doesn’t (per-answer feedback strings as a leaky channel, stand-alone
display_quiz).nbgrader Pipeline “Collecting and grading” section replaced — it described the pre-v0.4.0 self-checking-only world. It now describes the actual sidecar + autograde flow.
New worked example
nb3-physics-rich-content.ipynb(in the Worked examples page) combines every feature in one annotated notebook: MathJax in question and answer labels, numeric value and range matching with precision, a code-block question, per-question points (including fractional weights), and a self-check warm-up inside a graded task (PR/24).
Fixes
The instructor-declared question type (
SC/MC) is now authoritative at both parse time and render time. Previously the display silently switched aSCquestion with two+answers to many-choice semantics (and vice-versa forMCwith one+), producing responses the grader would then score as 0 because of thetypemismatch.SCwith anything other than exactly one correct answer is now a hardParseError;MCwith 0 or 1 correct answers is allowed but logs a warning. Both warnings and errors are now routed throughCreateQuiz.logso they appear innbgrader’s UI output with the offending cell’sgrade_id(PR/21).hide_correctness=true(auto-on for graded quizzes) now strips the answer key from the display JSON embedded into the release notebook. Previously, the fullcorrectflags on multiple-choice answers and thevalue/rangematchers on numeric answers shipped to the student’s browser; only the visual feedback was hidden. The autograder receives its copy from a separate hidden-tests block (stripped byClearHiddenTests, restored byOverwriteCells), so this change does not affect grading. Per-answerfeedbackstrings are intentionally preserved. Self-check quizzes (hide_correctness=false) still ship the full key, since the JS needs it to colour buttons (PR/22).Deselected MC/many-choice answer buttons keep their dark text colour instead of inheriting the surrounding container’s light text — previously invisible on dark JupyterHub themes after a select-then-deselect. A new
--jq-mc-button-textpalette variable drives both the default and deselected states (PR/23).MathJax expressions (
$...$) in question and feedback strings render correctly withencoded=falsequizzes. Previously MathJax rewrote the embedded JSON in place, breakingJSON.parse; the hidden span now carriestex2jax_ignore/mathjax_ignoreclasses so MathJax skips it. Defaultencoded=truequizzes were not affected (PR/23).
v0.4.0 (2026-04-17)
Contributors: Philipp Emmo Tobias Risius (@PhilippRisius)
Developed with assistance from Claude (Anthropic) — see commit trailers for per-commit attribution.
Changes
Added graded-quiz mode: quizzes inside nbgrader Manually Graded Task cells are now auto-graded end-to-end. Student responses are persisted to a
responses.jsonsidecar as they answer;nbgrader autogradereads the sidecar and awards partial credit. Introducesgrade_quiz(), theQuizResult/QuestionResultdataclasses, and a static HTML review rendered into the autograded cell output (visible ingenerate_feedback). Cross-frontend (JupyterLab 4, Notebook 7, classic Notebook); VS Code’s Jupyter extension is not supported.Added the
{N}question-line marker for per-question points, supporting fractional weights (e.g.{0.5}). Points render as a badge next to each question; the badge-display rule is “show on every question iff at least one question in the quiz carries an explicit{N}”.Added quiz-level
graded=falseoption to opt a single quiz out of auto-grading inside a task cell (self-check mode) while leaving the task cell’s ownpointsintact for any manual grading of surrounding content. Added quiz-levelhide_correctnessoverride, independently toggleable fromgraded.Added hide-correctness feedback mode (auto-enabled for graded quizzes) — MC/many-choice/numeric questions show a neutral “Selected: … / Deselected: …” state instead of green/red so students can’t guess their way to the right answer.
Replaced
<label>``+hidden ``<input type=radio>with a plain<button type=button>for MC answers. Eliminates the label→radio click synthesis that otherwise double-fired the click handler in hide-mode toggles.Added a dedicated graded-quizzes documentation page covering the workflow end-to-end.
Instructor config changed: register
CreateQuizwithc.GenerateAssignment.preprocessors.insert(0, ...)(not.append(...)). The new auto-generated autograder cells must run before nbgrader’sSaveCellsandClearHiddenTests.Added string question schema (
validate.Schema.STR). The previousSCHEMATAdispatch was missing the string type — constructing a string-type question dict raisedKeyErrorbefore schema validation.Removed the
capture_responsespublic API. It was a browser- DOM-only shim that never integrated withnbgrader autogradeand whose interface was incompatible with caller code (prev_div_idwas interpolated as a JS expression, not a string). The graded- quiz workflow is the supported replacement.Published the two end-to-end validation notebooks (
nb1-geography.ipynbandnb2-python.ipynb) as documentation downloads — they exercise every graded/self-check mode and serve as copyable starting templates for instructors.
Fixes
nbgrader-pipeline.rstno longer refers to the deprecatednbgrader assigncommand — it’sgenerate_assignmentin nbgrader ≥ 0.9.Numeric range display bug:
+ [0, 10]now includes the upper bound (10was previously rejected due to a strict<comparison) and the match is no longer gated onanswer.feedbackbeing set. Reported during v0.3.0 end-to-end validation.
v0.3.0 (2026-04-14)
Contributors: Philipp Emmo Tobias Risius (@PhilippRisius)
Developed with assistance from Claude (Anthropic) — see commit trailers for per-commit attribution.
Changes
Implemented quiz-level option parsing (
parse_quiz_options) — the#### Quizheader now acceptsencoded,inline,hidden, andfilenameoptions as space-separatedkey=valuepairs (PR/12).Added a preprocessor test suite covering the happy path, cell transformation, multi-quiz notebooks,
enforce_metadata,ParseErrorhandling, all four option modes, and filesystem edge cases — 21 tests, 100 % line coverage ofgrader/preprocessor.py(PR/12).Added a display-module test suite covering colour-palette key symmetry,
display_quizparameter guards,build_stylesCSS injection, andload_questions_scriptloader paths — 9 tests (PR/12).Added a
docsCI job (tox -e docswith-W --keep-going) so Sphinx build failures are caught on every PR, not only after ReadTheDocs runs (PR/13).Refactored the display colour palettes
_DEFAULT_COLORSand_FDSP_COLORSto module-level constants (no behaviour change; enables palette-symmetry tests) (PR/12).Added the initial public documentation set: quiz-syntax reference, nbgrader-pipeline integration guide, and display-options reference; plus a Diataxis-guided correction pass against the actual code behaviour (PR/11).
Documented quiz-level options with a reference table in
docs/quiz-syntax.rst(PR/12).
Fixes
Registered an
autodoc-skip-memberhandler indocs/conf.pyto prevent duplicate-object-description warnings for symbols re-exported via__init__.__all__; the ReadTheDocs build now passes underfail_on_warning: true(PR/13).Resolved all outstanding Sphinx build warnings in the initial docs set (PR/11).
Dropped
Exception.add_note()fromgrader/preprocessor.py— the call required Python 3.11+ (PEP 678) but the project targets 3.10+ (PR/12).Widened the
sphinxversion constraint from<8.2to>=8.1.3so thetox -e docsenvironment installs the same major version as ReadTheDocs (PR/13).Bumped
pipto 26.0 to address GHSA-4xh5-x5gv-qwph and GHSA-6vgw-5pg2-w6jp (PR/9).
v0.2.0 (2026-04-13)
Contributors: Philipp Emmo Tobias Risius (@PhilippRisius)
Changes
Merged fork of jupyterquiz (v2.9.6.4) as
nbgrader_jupyterquiz.displaysubpackage, removing the external dependency.Ported nbgrader preprocessor and quiz parsing from the legacy plugin into
nbgrader_jupyterquiz.grader.Added unit tests for quiz parsing, schema validation, and encoding.