Found while enabling strictTemplates (#662). The reading-journey quiz review UI reads correctAnswerIndex/explanation from QuestionForTakingDto, but that DTO intentionally excludes answers ("excluded for security" per backend comment) and no controller endpoint serves the existing QuizForReviewDto. Result: the correct-answer highlight and explanations have always rendered as undefined — the review feature is visually non-functional.
Frontend is now honestly typed (type-guard helpers correctAnswerIndexOf()/explanationOf() in reading-journey.component.ts), so wiring this up needs:
Frontend: fetch review data after submission and pass it to the review template (the guards will pick it up)
Found while enabling strictTemplates (#662). The reading-journey quiz review UI reads `correctAnswerIndex`/`explanation` from `QuestionForTakingDto`, but that DTO intentionally excludes answers ("excluded for security" per backend comment) and no controller endpoint serves the existing `QuizForReviewDto`. Result: the correct-answer highlight and explanations have always rendered as undefined — the review feature is visually non-functional.
Frontend is now honestly typed (type-guard helpers `correctAnswerIndexOf()`/`explanationOf()` in reading-journey.component.ts), so wiring this up needs:
1. Backend: a post-completion review endpoint returning `QuizForReviewDto` (DTO already exists, unused)
2. Frontend: fetch review data after submission and pass it to the review template (the guards will pick it up)
Backend half up as PR #419 (mergeable). Claim verified: QuizForReviewDto/ToReviewDto() existed with the full answer key but had zero controller callers.
New GET api/quiz/{id}/review: ownership via the same GetQuizQuery-by-username pattern as GetQuiz; 403 until the caller has a completed attempt (new IQuizAttemptService.HasCompletedAttempt) so the taking-DTO's answer exclusion can't be bypassed mid-quiz; 200 → ToReviewDto(). 4 controller tests incl. an explicit no-answer-leak assertion on the 403 path.
Remaining (this ticket stays open): the spikersoft-angular half — fetch /review after submission and feed the existing correctAnswerIndexOf()/explanationOf() guards in reading-journey.component.ts.
**Backend half up as PR [#419](https://git.spikersoft.com/spikerj/spikersoft-backend/pulls/419)** (mergeable). Claim verified: `QuizForReviewDto`/`ToReviewDto()` existed with the full answer key but had zero controller callers.
New `GET api/quiz/{id}/review`: ownership via the same `GetQuizQuery`-by-username pattern as `GetQuiz`; **403 until the caller has a completed attempt** (new `IQuizAttemptService.HasCompletedAttempt`) so the taking-DTO's answer exclusion can't be bypassed mid-quiz; 200 → `ToReviewDto()`. 4 controller tests incl. an explicit no-answer-leak assertion on the 403 path.
**Remaining (this ticket stays open):** the spikersoft-angular half — fetch `/review` after submission and feed the existing `correctAnswerIndexOf()`/`explanationOf()` guards in `reading-journey.component.ts`.
Sweep note: backend PR #419 merged the post-completion review endpoint with the answer key, but I could not find frontend code consuming it in reading-journey — the user-facing 'never shows correct answers' symptom may persist. Leaving open pending the UI half (or evidence it exists).
Sweep note: backend PR #419 merged the post-completion review endpoint with the answer key, but I could not find frontend code consuming it in reading-journey — the user-facing 'never shows correct answers' symptom may persist. Leaving open pending the UI half (or evidence it exists).
Audited against origin/master in both repos — the backend half shipped, the Angular half never did. Staying open. The ticket title's premise ("no backend endpoint ships the answer key") is now stale, so updating the notes:
Correctly gated: :110 checks _quizAttemptService.HasCompletedAttempt(id, username) and returns 403 otherwise, so the taking DTO's answer-exclusion can't be bypassed mid-quiz. Ownership still resolved through GetQuizQuery (404 on miss).
DTOs split as intended: QuizDto.cs:118 keeps CorrectAnswerIndex/Explanation excluded from the taking shape; :145-146 adds them to the review shape.
Angular: NOT DONE — nothing calls the endpoint.git grep -nE "/review|getQuizReview|QuizForReview" origin/master -- '*.ts' over spikersoft-angular returns no quiz hits at all (only an art-studio review link and an unrelated geography.service.ts:316 facts-review POST).
The consequence is a UI that silently renders the empty state forever:
reading-journey.component.ts:724-726 — correctAnswerIndexOf() is return "correctAnswerIndex" in question ? question.correctAnswerIndex : null;, and :728-730explanationOf() is the same shape. Since the client only ever fetches the taking DTO, that in check is always false and both helpers always return null.
So reading-journey.component.html:428[class.correct-answer]="$index === correctAnswerIndexOf(question)" never matches any option, and :277 keeps rendering readingQuiz.explanationPending — "Explanation will be available after quiz completion" (en.json:1752) — which is now a false promise: completion happens, the explanation still never arrives.
Remaining work: add a getQuizReview(id) call to the quiz service, fetch the review DTO once the attempt is complete, and feed it to the review pane so correctAnswerIndexOf/explanationOf receive a QuizQuestion rather than a QuestionForTakingDto. Then drop or re-word explanationPending.
Note the identical dead-helper pattern exists at aptituderator.component.ts:699 — worth fixing in the same pass, or filing separately if aptituderator has no review endpoint of its own.
Audited against `origin/master` in both repos — **the backend half shipped, the Angular half never did.** Staying open. The ticket title's premise ("no backend endpoint ships the answer key") is now stale, so updating the notes:
**Backend: DONE.** spikersoft-backend PR #419 (`d06150b6`, merge `9f335874`).
- `QuizController.cs:85` — `[HttpGet("{id}/review")] GetQuizReview`, returning `QuizForReviewDto`.
- Correctly gated: `:110` checks `_quizAttemptService.HasCompletedAttempt(id, username)` and returns **403** otherwise, so the taking DTO's answer-exclusion can't be bypassed mid-quiz. Ownership still resolved through `GetQuizQuery` (404 on miss).
- DTOs split as intended: `QuizDto.cs:118` keeps `CorrectAnswerIndex`/`Explanation` excluded from the taking shape; `:145-146` adds them to the review shape.
**Angular: NOT DONE — nothing calls the endpoint.** `git grep -nE "/review|getQuizReview|QuizForReview" origin/master -- '*.ts'` over spikersoft-angular returns **no quiz hits at all** (only an art-studio review *link* and an unrelated `geography.service.ts:316` facts-review POST).
The consequence is a UI that silently renders the empty state forever:
- `reading-journey.component.ts:724-726` — `correctAnswerIndexOf()` is `return "correctAnswerIndex" in question ? question.correctAnswerIndex : null;`, and `:728-730` `explanationOf()` is the same shape. Since the client only ever fetches the *taking* DTO, that `in` check is always false and both helpers always return `null`.
- So `reading-journey.component.html:428` `[class.correct-answer]="$index === correctAnswerIndexOf(question)"` never matches any option, and `:277` keeps rendering `readingQuiz.explanationPending` — *"Explanation will be available after quiz completion"* (`en.json:1752`) — which is now a false promise: completion happens, the explanation still never arrives.
**Remaining work:** add a `getQuizReview(id)` call to the quiz service, fetch the review DTO once the attempt is complete, and feed it to the review pane so `correctAnswerIndexOf`/`explanationOf` receive a `QuizQuestion` rather than a `QuestionForTakingDto`. Then drop or re-word `explanationPending`.
Note the identical dead-helper pattern exists at `aptituderator.component.ts:699` — worth fixing in the same pass, or filing separately if aptituderator has no review endpoint of its own.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while enabling strictTemplates (#662). The reading-journey quiz review UI reads
correctAnswerIndex/explanationfromQuestionForTakingDto, but that DTO intentionally excludes answers ("excluded for security" per backend comment) and no controller endpoint serves the existingQuizForReviewDto. Result: the correct-answer highlight and explanations have always rendered as undefined — the review feature is visually non-functional.Frontend is now honestly typed (type-guard helpers
correctAnswerIndexOf()/explanationOf()in reading-journey.component.ts), so wiring this up needs:QuizForReviewDto(DTO already exists, unused)Backend half up as PR #419 (mergeable). Claim verified:
QuizForReviewDto/ToReviewDto()existed with the full answer key but had zero controller callers.New
GET api/quiz/{id}/review: ownership via the sameGetQuizQuery-by-username pattern asGetQuiz; 403 until the caller has a completed attempt (newIQuizAttemptService.HasCompletedAttempt) so the taking-DTO's answer exclusion can't be bypassed mid-quiz; 200 →ToReviewDto(). 4 controller tests incl. an explicit no-answer-leak assertion on the 403 path.Remaining (this ticket stays open): the spikersoft-angular half — fetch
/reviewafter submission and feed the existingcorrectAnswerIndexOf()/explanationOf()guards inreading-journey.component.ts.Sweep note: backend PR #419 merged the post-completion review endpoint with the answer key, but I could not find frontend code consuming it in reading-journey — the user-facing 'never shows correct answers' symptom may persist. Leaving open pending the UI half (or evidence it exists).
Audited against
origin/masterin both repos — the backend half shipped, the Angular half never did. Staying open. The ticket title's premise ("no backend endpoint ships the answer key") is now stale, so updating the notes:Backend: DONE. spikersoft-backend PR #419 (
d06150b6, merge9f335874).QuizController.cs:85—[HttpGet("{id}/review")] GetQuizReview, returningQuizForReviewDto.:110checks_quizAttemptService.HasCompletedAttempt(id, username)and returns 403 otherwise, so the taking DTO's answer-exclusion can't be bypassed mid-quiz. Ownership still resolved throughGetQuizQuery(404 on miss).QuizDto.cs:118keepsCorrectAnswerIndex/Explanationexcluded from the taking shape;:145-146adds them to the review shape.Angular: NOT DONE — nothing calls the endpoint.
git grep -nE "/review|getQuizReview|QuizForReview" origin/master -- '*.ts'over spikersoft-angular returns no quiz hits at all (only an art-studio review link and an unrelatedgeography.service.ts:316facts-review POST).The consequence is a UI that silently renders the empty state forever:
reading-journey.component.ts:724-726—correctAnswerIndexOf()isreturn "correctAnswerIndex" in question ? question.correctAnswerIndex : null;, and:728-730explanationOf()is the same shape. Since the client only ever fetches the taking DTO, thatincheck is always false and both helpers always returnnull.reading-journey.component.html:428[class.correct-answer]="$index === correctAnswerIndexOf(question)"never matches any option, and:277keeps renderingreadingQuiz.explanationPending— "Explanation will be available after quiz completion" (en.json:1752) — which is now a false promise: completion happens, the explanation still never arrives.Remaining work: add a
getQuizReview(id)call to the quiz service, fetch the review DTO once the attempt is complete, and feed it to the review pane socorrectAnswerIndexOf/explanationOfreceive aQuizQuestionrather than aQuestionForTakingDto. Then drop or re-wordexplanationPending.Note the identical dead-helper pattern exists at
aptituderator.component.ts:699— worth fixing in the same pass, or filing separately if aptituderator has no review endpoint of its own.