explico
v0.8.0
Published
Build-time engine for prerequisite-aware, notation-rigorous course content: one validated content model, a semantic notation/citation/equation system with a completeness gate, and a deterministic JSON manifest.
Readme
explico
A build-time engine for prerequisite-aware, notation-rigorous course content. It turns a folder of MDX lessons and JSON collections into a validated static course whose every rendered symbol is explained and whose curriculum graph is checked.
What it gives a course:
- One validated content model — Zod schemas for lessons, competencies, assessments, tracks, sources, and notation; a single loader from files to typed data.
- A semantic notation system —
[[key]]binds a glyph to a define-once meaning; a completeness gate fails the build if any identifier in any rendered-math context (lesson body,notation.formula,checks.yml, assessment prompts, component slots) does not resolve to a key. One meaning per glyph per page, enforced corpus-wide. Named operators get the same treatment one tier softer: a lesson's\operatorname{Var}is a warning (operator-explained) until it carries an entry or the course lists it inoperators.allow; a shipped baseline (\exp,\int, trig, …) is exempt. - Inline citations —
[@source-id; locator]→ numbered marker + generated reference list, resolved against a schema-checked source collection. - Equation identity —
\label{eq:key}gives a display equation a stable key, an appearance-order number, an anchor, and cross-lesson[[eq-key]]references. - A KaTeX trust boundary — build-time HTML + MathML; the trust callback
accepts exactly one validated
data-notation-key. No runtime math, no CDN. - A deterministic JSON manifest — the MCP/LMS contract: lessons, notation, competencies, sources, cross-refs, diagnostics, content hashes. Two compiles of one content tree are byte-identical.
- Shared Astro/Starlight UI — a hover-panel primitive shared by the notation and citation layers, a glossary, a curriculum map, compact examples, layout overrides.
- Interactive figures, with room to grow — a
labMathScope/renderLabMathseam compiles a\slot{}-templated formula against the page's real notation at build time, so a client-side island only ever patches numbers into pre-rendered KaTeX. Hand-rolled SVG is the default chart (zero dependency, covers a single custom figure); reach for D3 once a figure needs real tick-labeled axes, log scales, or several series sharing a scale; Observable Plot pays off once a course is authoring many charts per lecture. Pick per figure — nothing in the engine assumes one plotting approach. - LMS / analytics / identity seams — framework-free interfaces shipped as no-ops; a host supplies real implementations without touching a component.
- A
contentCLI —status,diagnostics,warnings,context <lesson>,check <lesson>(compiles MDX → remark → KaTeX and fails loudly),new lesson|term.
The prerequisite graph itself is authored by the lecturer or an AI; the engine validates it (DAG, ordering, coverage, orphans) rather than inferring it.
Install
pnpm add -D explicoPeer dependencies (the course provides them, pinned): astro,
@astrojs/starlight, @astrojs/react, @astrojs/markdown-remark, react,
react-dom, katex, rehype-katex, remark-math.
Use
A course provides content/ (the six collections + content/domain/ +
content/course.config.ts) and wires the engine in three places:
// src/content.config.ts — Astro's content entry
export { collections } from 'explico/content-config';// astro.config.mjs
import { explicoContentRefresh } from 'explico/compiler/astro.js';
import { loadManifest } from 'explico/compiler/manifest.js';
import remarkMemo from 'explico/reference/remark-memo.mjs';
import remarkNotation, {
createRemarkNotationOptions,
} from 'explico/reference/remark-notation.mjs';
import remarkShortNote from 'explico/reference/remark-short-note.mjs';
import remarkCitation from 'explico/reference/remark-citation.mjs';
import { createNotationKatexOptions } from 'explico/reference/katex-options.mjs';
import rehypeFailKatexErrors from 'explico/reference/rehype-fail-katex-errors.mjs';
import { courseConfig } from './content/course.config.ts';
const manifest = await loadManifest(courseConfig);
// markdown.remarkPlugins: [
// remarkMath,
// remarkShortNote,
// remarkMemo,
// // `createRemarkNotationOptions` reads every numbering input off one
// // manifest — the equation / table / figure number tables *and* the part
// // each lesson sits in — so they can't drift apart. `strict: true` fails
// // the build when a catalogued lesson has equations/tables/figures but no
// // numbering or cross-page entry, instead of silently numbering it by
// // `##` section while references to it read the part scheme.
// [remarkNotation, createRemarkNotationOptions(manifest, { strict: true })],
// [remarkCitation, { sources: manifest.sources }],
// ]
// … integrations: [explicoContentRefresh(courseConfig, manifest),
// starlight({ customCss: ['explico/styles/global.css'],
// components: { Footer: 'explico/components/starlight/LessonFooter.astro',
// SocialIcons: 'explico/components/starlight/LayoutSocialIcons.astro', … } })
// … rehypePlugins: [[rehypeKatex, createNotationKatexOptions()], rehypeFailKatexErrors]loadManifest(courseConfig) always recompiles current content before Astro
starts. explicoContentRefresh(courseConfig, manifest) recompiles the manifest
and restarts the development server when any file under content/ is added,
changed, or removed, keeping Markdown transforms and UI components on the same
model without an editor-specific task.
A memo is a substantial optional derivation: it behaves like a collapsible,
numbered subsection without adding a heading to the page outline. Cite it from
earlier prose with [[memo-id]], then place the block wherever it best fits.
The citation, the block's own number, and its chain icon link to the same URL
fragment:
The omitted calculation is recorded in [[memo-full-check]].
Several paragraphs may intervene.
<Memo id="full-check" title="Full check" description="The omitted algebra and evidence.">
The longer derivation can contain paragraphs, equations, tables, and figures.
</Memo>For a reveal that really belongs inside one sentence, import
explico/components/memo/ShortNote.astro and use <ShortNote>. Its title is
the collapsed inline link; expanding replaces that link with the block-capable
body, then rejoins the sentence when collapsed:
The derivative agrees <ShortNote id="derivative" title="(show how)" description="Show the calculus calculation.">
Differentiate the polynomial, set the result to zero, and solve.
</ShortNote>. But symmetry is quicker.Both components require literal id, title, and description strings.
Memos start collapsed and persist reader state unless rememberState={false};
short notes always start collapsed.
Lesson exports
A course can opt into a self-contained HTML download and browser-native printing for each lesson:
export const courseConfig: CourseConfig = {
// ...the normal course fields
lessonExports: {
standaloneHtml: true,
print: true,
canonicalBaseUrl: 'https://example.edu/course/',
},
};The existing explicoContentRefresh(courseConfig, manifest) integration emits
downloads/<lesson-slug>.html after Astro's static build and adds a download
control to the top-right header cluster on each lesson. During astro dev, the
first download request runs an isolated static build and streams the resulting
file; later requests use that result until a watched source file changes. The
downloaded file contains its CSS,
KaTeX fonts, images, client scripts, Astro island components, and renderer; it
opens directly through file:// without a server or network access. Search,
sidebars, pagination, and other site chrome are omitted. Same-page references
stay local, while links that leave the lesson use canonicalBaseUrl and
cross-lesson hover previews degrade to ordinary links. A retained theme control
lets an offline reader choose Light, Dark, or Auto; Auto follows changes to the
viewer’s system color scheme.
The Print / save as PDF action calls the browser's native print dialog; it
does not download or build a .pdf file itself. The print layout removes site
chrome, expands disclosures and complete data surfaces without changing saved
reader state, hides controls that have no paper equivalent while retaining their
current visual result, and keeps linked text clickable with canonical destinations.
Paper size, margins, browser-added headers/footers, final filename, and the
choice to save as PDF remain browser and reader settings.
Set canonicalBaseUrl to the deployed course root whenever retained lesson
content links elsewhere in the course. If such a link exists without the base,
the build fails rather than shipping a dead local link. An unresolved or remote
stylesheet, script, font, image, iframe, media source, worker, WASM file, or CSS
URL likewise fails the standalone export audit.
// package.json — the read-side CLI
"scripts": { "validate:content": "tsx scripts/compile-manifest.ts" }Diagnostics have three distinct severities: an error blocks validation because
content is incorrect, ambiguous, unexplainable, or cannot render reliably; a
warning is non-blocking but identifies a rule or usability problem that should
be repaired; info is optional advice that may be intentionally left as-is.
Run pnpm --filter explico-playground content diagnostics info to print every
diagnostic. Replace info with warning to show warnings and errors, or error
to show blockers only. content warnings remains the warning-only view, and
--json returns normalized data for tooling. VS Code exposes matching
Explico: Show content diagnostics (info+), (warning+), and (errors)
tasks; locatable findings populate the Problems panel at the reported line and
column.
The reference course is credit-and-derivatives: discounting → bonds → credit/CDS → derivatives and options.
Layout
| Path | What |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| src/compiler/ | the one manifest compiler + the content CLI |
| src/reference/ | notation registry, page-glyph resolver, remark/rehype adapters, the completeness gate, equation identity, consistency checks, KaTeX options |
| src/curriculum/ | the curriculum-graph semantic validator |
| src/components/ | shared Astro/React UI (notation layer, citations, glossary, hover panel, examples, layout) |
| src/progress/, src/session/, src/analytics/ | the LMS / identity / analytics seams (no-op impls) |
| src/content-config.ts | the Zod collection schemas (Astro re-exports them) |
| src/styles/ | global.css (notation panel, keyed-equation flash, KaTeX) |
Authoring
Course content follows the rules in docs/authoring/ —
strong recommendations for human and AI authors covering notation spelling
(\mathcal{N} for the normal, \mathbb{P} for probability, upright \mathrm{d}
for the differential, …), which library draws a diagram or
chart (Diagram for graphs; SVG → D3 → Observable Plot for charts), how
randomised interactive figures pick their axes, and how the course's knowledge
version and content/CHANGELOG.md move with a content change (area C,
surfaced to readers in the About [?] panel).
docs/authoring/README.md is the governance: rule
IDs, enforcement levels, and how to amend.
docs/authoring/components.md is a syntax
catalog of what a lesson body can contain — native tooltips, tables, figures,
diagrams, memos, abbreviations, citations — for an author who wants "what's
available" rather than "why this rule."
Build
pnpm build # tsup → dist/ (ESM + .d.ts) for the .ts core; .astro / .tsx / .css
# and content-config.ts ship from src/; .mjs adapters are copied.Published exports resolve to dist/ for the typed core and to src/ for the
Astro components and styles (see publishConfig in package.json).
Versioning
The engine follows semver. The golden source for the
version is the installed package.json (explico's own version); the About
[?] panel reads it at build time and the docs never repeat a number.
CHANGELOG.md— what changed between engine versions.docs/VERSIONING.md— what a MAJOR / MINOR / PATCH bump means here, the public surface it is measured against, and the pre-1.0 clause.
A course built on explico carries an independent knowledge version over its
content — about.version in content/course.config.ts, tracked in the course's
content/CHANGELOG.md, surfaced to readers in the same [?] panel. See
docs/authoring/versioning.md.
For AI authors: AGENTS.md says which changelog and which version a
given change updates.
When upgrading an existing course, use the migration guide shipped with the
target package at node_modules/explico/docs/authoring/migrating.md. Every
published version has a section, including versions that require no course-side
changes, so an agent can update content, configuration, and scripts from the
installed artifact rather than guessing from the changelog.
Status: extracted from the equations project; API not yet stable (pre-1.0);
the license is undecided (UNLICENSED until chosen).
