Skip to content

Exercise Standards

This document is the authoritative specification for exercise content in the Berean Bible Bots project. scripts/validate_exercises.py enforces these rules automatically; if a rule is not yet validated, that is a gap in the validator, not an exception to the standard.

Contents


Three-Format Rule

Every exercise must ship in exactly three formats. Never create an exercise without all three.

File Purpose
<name>.md Static reference copy with full answer key at the bottom
<name>.html Self-contained interactive HTML (fillable, self-grading)
<name>.pdf AcroForm fillable PDF for print/download

The exercise directory also contains a README.md with a description, conjugation coverage table, and a files table linking all three formats.

A new exercise directory must ship all three formats from the start — never create one without all three. For an existing exercise, PDF generation is driven by an independent Python data structure in src/bible_grammar/exercise_pdf/*.py (SortEntry/VerbEntry lists, not derived from the .html/.md source), so a content-only fix to .html/.md does not strictly require regenerating the PDF in the same PR — but the resulting drift must be tracked (a GitHub issue naming the specific exercise and what changed) rather than silently left to accumulate. See issue #625 for the ch34/ch35 backlog this created.

Enforced by: validate_exercises.py → check_three_formats


Exercise Directory Layout

data/lessons/<course>/ch<N>/exercises/<exercise-name>/
    <exercise-name>.md        ← static reference + answer key
    <exercise-name>.html      ← interactive HTML
    <exercise-name>.pdf       ← fillable PDF
    README.md                 ← description, coverage table, files table

The exercise name (<exercise-name>) must match the directory name exactly — no spaces, lowercase with hyphens.


HTML Exercise Format

The .html file is fully self-contained — no external dependencies, no CDN links, no separate CSS or JS files.

Structure requirements

  • <input class="parse-field"> in every parse cell (for free-text fields; see Input Field Types for when to use <select> instead)
  • ▶ Answer button per verb/item row — clicking reveals a green answer row beneath it; clicking again hides it
  • Three global controls at the top of the form: Show All Answers, Hide All Answers, Clear All Inputs
  • @media print block: hides all buttons; makes <input> elements render as underlines
  • All answers, styles, and JavaScript embedded inline in the single .html file
  • Answer rows must never carry an inline style="display:..." attribute. Visibility is controlled exclusively by the CSS rule .ans-row { display: none; } and the toggle script. A hardcoded style="display:table-row;" on any ans-row element will make that answer visible on page load.
  • Answer buttons must not be pre-labeled as open. Every ▶ Answer button must start with class="rbtn" and text ▶ Answer. Using class="rbtn on" or text ▼ Hide in the static HTML causes the button state to be out of sync with the hidden answer row, making the first click show the answer without visually changing the button.
  • Input placeholders must not reveal the answer. Never use placeholder="e.g. אֱ-לֹ-הִים" or any placeholder that shows the correct answer (even prefixed with "e.g."). Use generic labels (division, types, root, gloss) that describe the field without giving the answer. This applies equally to every row, especially row 1 where the instructions' worked example is often identical to the first word's answer.

Hebrew/Aramaic/Greek text

  • RTL text: direction:rtl; unicode-bidi:embed on the element
  • Never put a verse range and Hebrew text on the same line. RTL reordering renders it backwards. Put the verse reference on its own line or in its own cell.

PDF Generation

All PDFs are generated by src/bible_grammar/exercise_pdf.py.

Chapter range Base class Key method
Ch24+ passage exercises PassageExercise (Template Method) _render_passages(show_answers: bool)
Ch1–Ch23 Plain ExercisePDF subclass add_generic_table()

Each exercise has a build_ch<N>_<exercise>() function that saves the PDF to the correct output path. All builders are called from the if __name__ == '__main__': block.

Key data classes: - VerbEntry — 6 fields: num, verb, conj, pgn, root, func - PassageBlock — fields: ref, hebrew, english, watchout

To regenerate all PDFs:

python3 src/bible_grammar/exercise_pdf.py


Input Field Types

Fields with a constrained set of valid answers must use <select> dropdowns, not free-text <input> elements. This prevents ambiguous answers and makes grading unambiguous.

Field type Required control
Stem (Qal, Niphal, Hiphil …) <select>
Conjugation (Perfect, Imperfect, Imperative …) <select>
PGN (3ms, 2fp …) <select>
Yes / No answers <select>
Function / Role labels <select>
Any field with fewer than 10 possible answers <select>
Free-text (root, translation, gloss) <input class="parse-field">

Rule: Any column whose complete answer set is fewer than 10 values must use <select>. Do not use placeholder="A or B" to hint at a constrained set — that is a signal the field should be a dropdown. When a placeholder lists options separated by "or" (e.g. placeholder="QH or —"), replace the <input> with a <select>.

Enforced by: validate_exercises.py → check_ans_row_no_inline_display (detects placeholder="… or …" patterns).

For paradigm fill-in drills (<select>-based): present four options, all from the same root. Answer rows must include morphological notes (stem + conjugation + PGN label).


Answer Row Layout

Every .answer-row <td> must align cell-for-cell with the corresponding columns in the table header. Do not collapse multiple cells into td[0] or use colspan on answer rows.

An answer row must never be left empty. <tr class="answer-row"><td></td></tr> is a hard content failure, not a formatting nit — clicking ▶ Answer or Show All Answers reveals nothing, and the exercise is unusable. This reached production on ch34-function-sort.html (and, independently, on ch24-function-sort.html and ch26-function-sort.html) before it was caught. scripts/validate_exercises.py's answer-row-empty check treats this as a blocking error, not a warning, unlike the general alignment check above — an exercise scaffolded with placeholder answer rows must have its answers filled in before the PR that adds it is merged, not left for a later pass.

<!-- Header row -->
<tr>
  <th>#</th><th>Verb</th><th>Stem</th><th>Conj</th><th>PGN</th><th>Root</th><th>Function</th>
</tr>

<!-- Answer row — same column count -->
<tr class="answer-row">
  <td>1</td><td>יִשְׁמֹר</td><td>Qal</td><td>Imperfect</td><td>3ms</td><td>שׁמר</td><td>Prefix conjugation</td>
</tr>

Distractor Policy (Ch24+ Passage Exercises)

"Spot the [Stem]" passage exercises must include distractor verbs from every stem learned before the target chapter's stem, ordered by chapter introduction. Students label every numbered verb with "[Stem]? Yes / No."

  • Distractors must be drawn from real verbs appearing in the same passage quotations where possible.
  • The coverage table in the exercise README.md must list every stem tested and its distractor status.

Stem introduction order follows the BBH chapter map in CLAUDE.md.