Learning Block Specification¶
Course content is data. Every block uses this versioned envelope:
{
id: string; // stable within its topic or collection
type: string; // registered semantic block type
version: number; // positive schema version
props: JsonObject; // type-specific, serializable, no raw HTML
meta?: {
title?: string;
tags?: string[];
learningObjectives?: string[];
estimatedMinutes?: number;
masteryWeight?: number;
sourceRefs?: SourceReference[];
};
}
The registry entry is the executable contract. It must define parseProps, a renderer,
capabilities, search-text extraction, and—where relevant—mastery weighting, references, and
version migrations. Unregistered block types and invalid props render an explicit fallback.
Feeding the review queue¶
A block with the reviewable capability should also implement reviewItems, which lifts each
thing the student can be asked about out of the block's props:
reviewItems: (props) => props.cards.map((card) => ({
itemId: card.id, // stable; it keys the item's scheduling history
prompt: card.front,
answer: card.back, // optional — omit when the content states no answer
group: card.group, // optional
})),
/review drills these directly, so a student can rate an item without opening the block it came
from. A reviewable block that does not implement reviewItems still records progress, but its
items cannot be rated from the queue. itemId must match the itemId the block writes into its
exercise.completed or flashcard.rated events, since that is how a rating is matched back to
its scheduling history.
Registered version 1 blocks¶
| Type | Required prop shape | Rendering rule | Capabilities |
|---|---|---|---|
core.theory |
paragraphs: string[] |
Reading prose | searchable, printable |
core.key-points |
title?, points[] |
Semantic key-point list | searchable, printable |
core.definition-list |
title?, entries[{term, definition, context?}] |
Definition list | searchable, printable |
core.table |
title?, columns[], rows[][] |
Accessible table | searchable, printable |
core.callout |
tone, title?, body |
Typed note/warning/success | searchable, printable |
media.gallery |
title?, images[{src, alt, caption?}] |
Alt-text-required gallery | searchable, printable |
math.formula-reference |
title?, formulas[] |
KaTeX formula reference | searchable, printable |
math.exercise |
Exercise Specification | Math response and evaluator | interactive, gradable, reviewable, printable |
code.example |
languageId, code, caption? |
Read-only code example | searchable, printable |
code.exercise |
Exercise Specification + runtime |
Editable files and executor protocol | interactive, executable, gradable, reviewable, stateful |
diagram.mermaid |
source, caption? |
Mermaid diagram with failure fallback | diagram, searchable, printable |
resource.link |
title, href?, available, description?, provenance? |
External or internal resource link | searchable, printable |
resource.document |
title, href?, available, description?, provenance? |
Available/missing document record | searchable, printable |
assessment.question |
Exercise Specification | One semantic assessment item | interactive, gradable, reviewable, printable |
assessment.flashcard-deck |
title, groups?, cards[{id, front, back, group?}] |
Filterable rated flashcards | interactive, reviewable, stateful, searchable, printable |
assessment.quiz |
id, title, description?, durationMinutes?, questions[] |
Auto/self-reviewed quiz | interactive, gradable, reviewable, stateful, searchable, printable |
assessment.practice-session |
id, title, description?, durationMinutes?, questions[] |
Filtered practice and review scheduling | interactive, gradable, reviewable, stateful, searchable, printable, exportable |
assessment.cloze |
title, intro?, items[{id, text, blanks[{key, accept[], hint?}], group?}] |
Typed fill-in-the-blank over authored sentences | interactive, gradable, reviewable, stateful, searchable, printable |
assessment.matching |
title, intro?, pairs[{id, term, definition, group?}], distractors? |
Select-a-term-then-a-definition pairing | interactive, gradable, reviewable, stateful, searchable, printable |
assessment.ordering |
title, intro?, sets[{id, prompt, steps[{id, label}], group?}] |
Reorder shuffled steps into the authored sequence | interactive, gradable, reviewable, stateful, searchable, printable |
tool.math-plotter |
title?, intro?, expressions[], xMin?, xMax? |
Mathematical function plotter using mathjs and recharts | interactive, searchable |
tool.number-drill |
title?, intro? |
8-bit adder with carry trace, base converter, generated conversion drills | interactive, searchable |
tool.kmap-solver |
title?, intro? |
Four-variable Karnaugh map minimised to DNF or CNF | interactive, searchable |
tool.rtl-stepper |
learnIntro, practiceIntro, instructions[{id, label, cycles[]}] |
Cycle-by-cycle micro-code stepper plus a rebuild-the-sequence drill | interactive, searchable |
tool.staged-reveal |
method[], scenarios[{id, title, brief[], stages[]}] |
Scenario practice where each model-answer stage stays hidden until asked for | interactive, searchable |
reference.tabs |
tabs[{id, label, sections[], snippets[]}] |
Tabbed reference tables and code snippets | interactive, searchable, printable |
practice.exam-trainer |
questions[{number, theme, points, topicIds[], pitfalls[], links[]}] |
Per-exam-question cards with topic progress and drill links | interactive, searchable |
The last six — tool.number-drill, tool.kmap-solver, tool.rtl-stepper, tool.staged-reveal,
reference.tabs, and practice.exam-trainer — take sections built from the same core.theory,
core.key-points and core.table prop shapes listed above, so ported prose renders identically
inside a tool or on its own.
The table is a quick authoring index. The executable source of truth is
src/course-os/registry/blockRegistry.ts and the parseProps function in each registered
definition. See Adding a course for a complete workflow.
Exercise and runtime contract¶
Exercises separate prompt, response, evaluation, hints, solution, and runtime. Responses can be
single/multiple choice, text, numeric (with tolerance), symbolic, or editable files. A code runtime
names a registered languageId, language version, and task mode (run, compile, test, or
simulate). Execution always uses the normalized request/result protocol with denied network,
resource limits, structured diagnostics, artifacts, and status values including compile error,
runtime error, timeout, output limit, unavailable, and success.
Choice responses may include an options array of { value, label } entries. The shared
assessment.question renderer presents those labels as selectable answers, evaluates supported
answer keys and numeric tolerances in the browser, and records completed attempts in the review
schedule. Written or symbolic responses without an automatic evaluator use an explicit self-review
step against the authored model answer.
assessment.quiz and assessment.practice-session questions may add an evaluator with the same
versioned { kind, version, config } boundary. Automatic written checking currently supports
numeric-tolerance and symbolic-equivalence. A symbolic config states an expected result,
answerKind, declared variables and constants, optional assumptions, tolerances, and an
equivalence mode. The expected result is executable assessment data; modelAnswer remains
explanatory learning content and is never parsed to discover the answer.
Supported final-answer kinds are scalar values, expressions, antiderivatives, linear equations,
vectors, and finite complex sets. Students enter compact expression syntax such as sqrt(2),
2*x/(1+x^2), y+2=5/4*(x-1), or [1,-1,1]. Full LaTeX, assignments, property access, units,
and undeclared symbols are rejected. Proofs, theorem explanations, domains, and geometric
descriptions continue to use model-answer self-review.
Symbolic evaluation runs entirely in a restricted, timeout-bounded browser worker. It returns one of four states: correct, incorrect, invalid notation, or unverified. Invalid and unverified answers must not be recorded as incorrect. The UI keeps an incorrect answer editable, separates Check answer from Reveal model solution, and falls back to self-review whenever equivalence cannot be established safely. No answer text is sent to an API or external service.
tue-rtl and pp2-assembly run in a terminating browser worker: the block dispatches to the worker
whenever the language's executor reports kind: "browser-worker", and posts to
/api/runtime/execute otherwise. C, Java, Python, and future custom university languages plug into
the same registry and sandbox boundary; course renderers never know which executor is used.
Exercises with a simulator-assertions evaluator forward address, value, description,
initialStackPointer, and initialMemory from the evaluator config to the executor as
publicTestConfig. The executor seeds machine state from those fields and returns the assertion as
a normal tests entry, which the renderer shows as passed or failed.
Authoring workflow¶
- Add a data-only course manifest, topics, and collections under
src/courses/<course-id>. - Use registered block envelopes; never import a course-specific React renderer.
- Add the validated input to
src/courses/index.ts. - Run
npm run validate:content, thennpm run check.
The fixture course under src/courses/fixture is the smallest complete example. Adding it to the
catalog would immediately provide overview, topics, resources, progress, review, exam preparation,
search, graph, and shell navigation without adding a route file.