--- /** * Auto-injected /answers route — the answer-rationale back-appendix * (v4.21.0, #114). * * The Sybex "Answers to Review Questions" appendix: every question grouped by * chapter, with the stem, the option list (correct one marked), the * frontmatter answer line, and the full body — everything * REVEALED, unlike /practice-exam's delayed-feedback bank. Authors who want * chapter bodies clean of inline rationales point readers here via * (which renders as a link to * /answers#answer- everywhere EXCEPT on this route, where the full * rationale renders — see Rationale.astro). * * Gated on routes.answers (default false on every profile). Twin-gate + * fail-loud identical to practice-exam.astro: presence-probe the questions * directory; assertKnownDomain throws on an unregistered domain. * * A tiny inline script force-opens the rationale/answer
— the * rendered MDX body carries them collapsed, and props can't be threaded * through render(); without JS the appendix still works (details are * clickable), it just isn't pre-expanded. */ import Base from '../layouts/Base.astro'; import { render } from 'astro:content'; import bookConfig from 'virtual:book-scaffold/book-config'; import { getAllQuestions, groupByChapter } from '../src/lib/questions'; import { assertKnownDomain } from '../src/lib/exam-domains'; import { normalizeBase } from '../src/lib/nav-href'; import { corpusBookHasApparatusRoute } from '../src/lib/corpus'; interface Props { bookId?: string; } const { bookId } = Astro.props; // Presence-gate: only touch the collection when the directory holds files. const questionModules = import.meta.glob('/src/content/questions/**/*.{md,mdx}', { query: '?raw', import: 'default', eager: true, }); const hasQuestions = bookId ? Object.keys(questionModules).some((path) => path.includes(`/questions/${bookId}/`)) : Object.keys(questionModules).length > 0; const entries = hasQuestions ? await getAllQuestions(bookId) : []; for (const entry of entries) { assertKnownDomain(bookConfig.examDomains, entry.data.domain, { id: entry.data.id }); } const rendered = await Promise.all( entries.map(async (entry) => ({ data: entry.data, reserved: entry.data.type === 'cloze', Content: entry.data.type === 'cloze' ? null : (await render(entry)).Content, })), ); const byChapter = groupByChapter(rendered); const total = rendered.length; // #142: practice-exam + chapter links must prefix the deploy base. const baseUrl = normalizeBase(import.meta.env.BASE_URL); const bookRoutePrefix = bookId ? `${bookId}/` : ''; const hasPracticeExam = bookId && bookConfig.corpus ? corpusBookHasApparatusRoute( bookConfig.corpus, bookId, 'practice-exam', bookConfig.apparatusRoutes, ) : (bookConfig.enabledRoutes ?? []).includes('practiceExam'); const practiceHref = hasPracticeExam ? `${baseUrl}${bookRoutePrefix}practice-exam` : null; /** A string chapter ref is a slug (schema kebab-case branch) → linkable. */ function chapterHref(chapter: number | string): string | null { return typeof chapter === 'string' ? `${baseUrl}chapters/${bookRoutePrefix}${chapter}/` : null; } ---

Answers & rationales

{!hasQuestions && (

No questions found under src/content/questions/ — this appendix collects the question bank's answers and rationales.

)} {hasQuestions && total > 0 && (

{total} question{total === 1 ? '' : 's'}, grouped by chapter, with answers and rationales expanded. {practiceHref && ( Test yourself first in the practice question bank. )}

)} {[...byChapter.entries()].map(([chapterKey, qs]) => { const firstQuestion = qs[0]; const href = firstQuestion ? chapterHref(firstQuestion.data.chapter) : null; return (

Chapter {href ? {chapterKey} : chapterKey}

{qs.map((q) => { const Content = q.Content; const correct = q.data.type === 'mcq' && q.data.options ? q.data.options.find((o) => o.correct) : undefined; return (
{q.data.id} {q.data.domain} {practiceHref && ( ↑ question in bank )}
{q.reserved ? (

Cloze question — rendering deferred.

) : ( Content &&
)} {q.data.type === 'mcq' && q.data.options && (
    {q.data.options.map((o) => (
  1. {o.text ?? o.id}{o.correct && }
  2. ))}
)} {q.data.type === 'mcq' && correct && (

Correct: {correct.text ?? correct.id}

)} {q.data.type === 'free' && q.data.answer && (

{q.data.answer}

)}
); })}
); })}