feat(quiz): inline code context per question (anchors) - #26
Merged
Conversation
Adds an optional `anchor: {path, start_line, end_line}` to every question type
so the browser can show the exact diff hunk inline next to the question — the
reader no longer has to hold the diff in their head or switch to GitHub.
- models: new `Anchor` model + optional `anchor` field on all four question
types. Optional (defaults to None) → old cached quizzes load unchanged; the
range is validated (start ≤ end, lines ≥ 1) but a path need not be in the diff
(a question may anchor surrounding context).
- web: `GET /diff?path=` serves one file's unified-diff section; `_DiffProvider`
is hoisted into `main()` and shared by the MCP tools and the web app (one
fetch/cache). 503 when no diff source is wired.
- frontend: collapsible per-question code panel, fetched on first expand and
rendered DOM-built (textContent only, never innerHTML); +/- lines and `@@`
headers are styled via CSS classes. Present on both the answering and the
results-review cards; absent when a question has no anchor.
- generation prompt: instructs the author to emit a tight `anchor` whenever a
question probes specific lines; never affects grading.
Foundation for the diff coverage map (#9) and drill-this (#5).
Tested: models round-trip + backward-compat (no anchor) unit tests; /diff
endpoint tests (hit + miss + 503); full mcp/engine suite green (ruff, mypy
strict). Anchor UI verified in a real browser (panel renders, expands, loads
the hunk, color-codes lines; absent on anchorless questions).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review feedback on the anchor UI: - The panel showed the whole file's diff; the anchor's line range was only a cosmetic label. Now the diff is parsed into hunks and scoped to the hunk(s) overlapping the anchor's new-side range (falls back to all hunks if none overlap), the anchored new-side lines are highlighted, and the file-level header lines (diff --git/index/---/+++) are dropped as noise. - A `/diff` miss (renamed file, or a binary/minified file filtered out of the diff) returned the "No changed file matches…" sentence with 200, which the UI rendered as a fake one-line diff. Now the UI shows a clean "Not part of the PR diff." note instead. - loadHunk now logs the error on a fetch failure (was silently swallowed). - Added a guard test that a question's anchor survives set_quiz + snapshot. Verified in a real browser: a question anchored to the 2nd hunk shows only that hunk with its added lines highlighted; a question anchored to a non-diff path shows the note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jonasbrami
added a commit
that referenced
this pull request
May 29, 2026
Shows which of the PR's changed files have a question anchored to them, so the reader can see at a glance what the quiz does and doesn't probe. - web: `GET /changed-files` returns the PR's changed-file paths (`changed_files` callable, wired in server.py to the shared `_DiffProvider`). 503 when unwired. - frontend: a "Diff coverage" sidebar block (answering view) listing each changed file with a covered/uncovered marker + an "N of M files probed" count. Coverage is computed client-side by matching question anchors to changed files with the same exact / repo-relative-suffix / basename logic the server's do_file_diff uses. Display only — the "ask the host to cover this file" steer is Track B. Builds on the anchors feature (#26). Tested: /changed-files endpoint (list + 503). Verified in a real browser: exact and basename-only anchors both mark their file covered; files with no anchored question show uncovered; count is correct. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an optional
anchoron every question so the browser shows the exact diff hunk inline, next to the question. The reader no longer has to hold the diff in their head or switch to GitHub.Changes
Anchor {path, start_line, end_line}+ optionalanchorfield on all four question types. Optional (defaults toNone) → old cached quizzes load unchanged; the range is validated (start ≤ end, lines≥ 1), but a path need not be in the diff (a question may anchor surrounding context).GET /diff?path=serves one file's unified-diff section._DiffProvideris hoisted intomain()and shared by the MCP tools and the web app (one fetch/cache). Returns503when no diff source is wired.textContentonly, neverinnerHTML);+/-lines and@@headers styled via CSS classes. Shown on both the answering and results-review cards; absent when a question has no anchor.anchorwhenever a question probes specific lines. Never affects grading.Invariants preserved
QuizState(the/diffread is browser↔web, in-process).textContent-only frontend; diff hunks are DOM-built, not string-injected.anchorwork exactly as before.Testing
Anchorround-trip, single-line range, reversed-range + non-positive rejections, every type carries an optional anchor, and backward-compat (no-anchor questions + a legacy quiz dict load withanchor=None).GET /diffhit + miss message +503when unavailable.mcp/enginesuite green;ruff+ruff format --check+mypy --strictclean./diff, color-codes+/-/@@lines, and is correctly absent on an anchorless open question.🤖 Generated with Claude Code