ADR-0015: Documentation site — MkDocs Material on GitHub Pages, with pre-executed notebooks¶
Date: 2026-07-04 Status: Accepted
Context¶
The repo is feature-complete (phases 0–8) and heavily documented — a rich README, a
RUNBOOK, RESULTS/TRADEOFFS, 14 ADRs, and ten teaching notebooks — but all of it is
readable only as raw Markdown on GitHub or as .py scripts that must be converted before
they render. As a portfolio piece it needs a published, navigable site with the same
shape as the sibling repos (scribe-iq-lakehouse, campus-rag-assistant), both of which
publish MkDocs Material to GitHub Pages.
Two questions shaped the decision:
- Toolchain and hosting. What builds the site, and where does it live, given the portfolio already has a convention?
- How do the teaching notebooks appear? They are the centerpiece of a learning
lab, but ADR-0014 deliberately keeps
.pypercent scripts as the versioned source and gitignores the.ipynb. A notebook rendered without outputs undersells it; executing notebooks in CI is infeasible here — the catalog CSV is gitignored (absent in CI), and notebook 08 needs a local Ollama daemon.
Decision¶
- MkDocs Material → GitHub Pages, matching the portfolio.
mkdocs.yml(Material theme,strict: true, indigo palette, mermaid via SuperFences,edit_uri) plus a.github/workflows/docs.ymlthat builds--strictand deploys via the Pages-artifact action (nogh-pagesbranch). A[docs]extra inpyproject.tomlmirrorsrequirements-docs.txt(CI installs from the latter for a fast, cache-keyed build). - Reuse existing docs; add a thin reviewer-facing layer. RESULTS, TRADEOFFS,
RUNBOOK, the ADRs, and the roadmap wire straight into the nav. Five new pages serve
the two audiences —
index,reviewer-guide(hiring skim),ARCHITECTURE,case-study(learner narrative),about.changelog.mdincludes the repo-rootCHANGELOG.mdverbatim viapymdownx.snippets(single source of truth). - Render notebooks with
mkdocs-jupyter, from pre-executed.ipynbcommitted underdocs/notebooks/.scripts/render_notebooks.py(amake docs-notebookstarget) executes eachnotebooks/*.pylocally — where the catalog and Ollama exist — and writes the executed notebook todocs/notebooks/NN_*.ipynb. The plugin renders it withexecute: false, so CI never runs a notebook and needs no data, models, or Ollama. This is a scoped, deliberate exception to ADR-0014: the.pypercent script remains the source of truth; thedocs/notebooks/*.ipynbare a generated publish artifact, never hand-edited. The.gitignoreban on committed notebooks stays, but is narrowed from all*.ipynbtonotebooks/*.ipynb.
Alternatives considered¶
- Render the
.pysource with no outputs (execute: falseovernotebooks/*.py). Fully CI-reproducible and no ADR-0014 exception, but a notebook page with no leaderboard, no plots, and no live-LLM responses is exactly the weak artifact the teaching notebooks exist to avoid. Rejected — outputs are the lesson here. - Execute notebooks in the CI docs build. Rejected: the catalog CSV is gitignored (not in a CI checkout) and notebook 08 needs a local Ollama daemon; CI would be slow, flaky, and would still fail the data-dependent cells.
- Link out to the
.pyscripts on GitHub, no in-site rendering. Simplest, but the weakest experience for the learner audience — rejected. - A different SSG (Docusaurus, Sphinx, plain README). Rejected for portfolio consistency: the two published sibling repos already standardize on MkDocs Material, so matching them is lower-friction and visually coherent.
mkdocs gh-deploy(gh-pages branch). Rejected in favor of the Pages-artifact action — no long-lived generated branch, same as the sibling repos.
Consequences¶
Positive. A published, navigable site at
https://sandeep-jay.github.io/course-recommender-lab/ matching the portfolio, with the
teaching notebooks shown as real executed notebooks (SBERT rankings, the clustering map,
the live qwen3:8b responses in 08). strict: true fails the build on a broken internal
link, so the published site can't rot silently. Existing docs are reused, not rewritten.
Honest caveats. (1) The published notebook outputs are a manual re-render:
make docs-notebooks must be run when a notebook's code or the underlying results change,
or docs/notebooks/*.ipynb will lag the source (a .py edit alone won't update the
rendered page). This is the price of showing outputs without executing in CI. (2) The
[docs] and [notebooks] extras are not co-installable — mkdocs-jupyter pins
ipykernel<7 while [notebooks] pins 7.3.0; they are separate steps (render vs build)
that never share an env, and CI installs only [docs]. (3) The committed .ipynb
reintroduce some JSON diff noise under docs/notebooks/ — bounded to the publish artifact,
not the source, and the metadata filter in [tool.jupytext] keeps it minimal.
Neutral. The distribution/display name was aligned to course-recommender-lab (repo
name); the Python import package stays courserec (hyphens are illegal in module names).
Builds on ADR-0014 (notebooks) and ADR-0012/0013 (the UI/Docker surfaces the site documents).
Implementation notes¶
mkdocs.yml, requirements-docs.txt, .github/workflows/docs.yml, a [docs] extra in
pyproject.toml, and a Makefile (docs-notebooks / docs-build / docs-serve).
scripts/render_notebooks.py executes the notebooks with the kernel cwd set to
notebooks/ (so import nbtools resolves) and writes to docs/notebooks/. New pages:
docs/index.md, reviewer-guide.md, ARCHITECTURE.md, case-study.md, about.md,
changelog.md, and docs/notebooks/index.md. .gitignore adds site/ and narrows the
notebook ban to notebooks/*.ipynb. One-time maintainer step: Settings → Pages →
Source = "GitHub Actions".