8.7 KiB
Contributing to Open Free Books
Thank you for helping grow free, open textbooks.
Readable guide (with examples): openfreebooks.org/contributing — or locally after zola serve → http://127.0.0.1:1111/contributing/
Prerequisites: Zola, Bun. See README.md for dev setup.
Quick reference
| I want to… | Start here |
|---|---|
| Add a planned chapter (roadmap only) | § Planned chapter |
| Publish a live chapter (readable page) | § Publish a live chapter |
| Add a branch in the catalog map | § Chapter graph |
| Add a new subject | § New subject |
| Add a catalog subject banner | § Catalog subject banner |
| Add a curriculum filter (e.g. GCSE) | § New curriculum label |
Browse the catalog locally: bun run build:js && zola serve → http://127.0.0.1:1111/catalog/
Search locally: After bun run build:js, run bun run index:search once (or again after changing chapter HTML), then zola serve. Try http://127.0.0.1:1111/search/ or press ⌘K / Ctrl+K.
How the catalog fits together
data/{subject}-curriculum.json → catalog list + map (status, curriculumCoverage, graph.edges)
content/{subject}/{slug}/ → everything for the live page (one folder)
_index.md, core.html, supplement.html, assets/, widgets/, chapter.scss
- Catalog (
/catalog/) — list, map (?view=tree), and for Mathematics a Compare doc (?view=compare, HTML partialthemes/openfreebooks/templates/partials/math-curricula-compare.html). Curriculum names (DSE, IB, …) appear only in the catalog — not inside chapter prose (spec.md). - Every chapter needs a
descriptionin curriculum JSON (map clamps to three lines). bun run sync:chapters(also run automatically bybun run build:js) mergescore.html+supplement.htmlinto_index.mdfor Zola. Edit the HTML files, not the merged body in_index.md.
Map graph rules (summary)
| Rule | Detail |
|---|---|
| Edges | Only graph.edges — strand chapters[] order does not create arrows |
| Meaning | Each edge is a required prerequisite (from → to) |
| Scope | Cross-strand edges allowed |
| Layout | Longest-path levels; siblings stack vertically |
| DAG | No cycles — build throws if the graph cycles |
tier |
Optional foundation (default) or non-foundation (Extension badge) |
Add a planned chapter (roadmap)
- Open
data/{subject}-curriculum.json(e.g.data/math-curriculum.json). - Add a chapter object under the right strand:
{
"slug": "your-chapter-slug",
"title": "Human-readable title",
"description": "One or two sentences for the catalog list and map card.",
"status": "planned",
"curriculumCoverage": {
"DSE": "core",
"IGCSE": "core",
"IB": "related"
},
"tier": "non-foundation"
}
Each framework maps to core, extension, or related (Compare matrix). Use extension for elective / M2-style topics; related when the topic supports but is not a named unit.
- Use kebab-case slugs. No
content/folder needed for planned chapters.
Publish a live chapter
1. Catalog entry
In data/{subject}-curriculum.json, set "status": "live" and include description, curriculumCoverage, and optional tier.
2. Chapter folder
Create content/{subject}/{slug}/:
content/math/ # subject folder (mathematics today)
├── subject.scss # shared CSS for all math chapters → /css/subjects/math.css
├── subject.ts # shared JS init for all math chapters
└── your-chapter-slug/
├── _index.md # page metadata (see below)
├── core.html # required — main lesson HTML
├── supplement.html # optional — history, checkpoints, extra demos
├── chapter.scss # optional — styles for this chapter only
├── assets/ # optional — images (synced to static/chapters/…)
└── widgets/ # optional — Solid widgets (auto-mounted)
└── my-widget.tsx # default export; data-widget="my-widget" in HTML
_index.md front matter (page fields only — catalog fields stay in JSON):
+++
title = "Your chapter title"
description = "One sentence for search and previews — no curriculum names here."
template = "chapter.html"
weight = 10
[extra]
subject = "math"
chapter_id = "your-chapter-slug"
strand = "Number & Algebra"
+++
3. HTML and widgets
- Use shared classes:
book-prose,book-prose__heading,book-callout, etc. Copy fromcontent/math/quadratic-equations/. - Checkpoints:
<details class="book-question">withbook-question__promptandbook-question__solution(not__answer). See.agents/skills/ofb/math-chapter-patterns.md. - Put styles reused across several chapters in
content/{subject}/subject.scss, not in eachchapter.scss. - Do not mention DSE, IB, or other curriculum names in chapter HTML.
- Mount widgets with
<div class="math-widget-mount" data-widget="widget-name" data-pagefind-ignore></div>(filename without.tsx=data-widgetvalue). - Math (KaTeX): write
\( … \)and\[ … \]in HTML; prose is auto-rendered on chapter load. In widgets useimport { renderLatex } from "@ofb/katex"— do not add a per-widgetrenderLatexhelper. - Reference synced assets with root-relative paths:
/chapters/math/your-chapter-slug/image.png(chapter HTML is not processed as Tera)
4. Build and check
bun run build:js # sync:chapters + Vite bundle
zola serve
/catalog/?subject=math— chapter appears and links correctly./math/your-chapter-slug/— page renders.bun run build— full production build.
New subjects with shared CSS/JS: add content/{subject}/subject.scss and/or subject.ts; sync:chapters wires them automatically. No per-chapter template branches.
Chapter graph (branches)
Add edges in data/{subject}-curriculum.json:
"graph": {
"edges": [
{ "from": "quadratic-equations", "to": "sequences-series" }
]
}
from / to are chapter slugs. Graph must be acyclic. Verify at /catalog/?subject=math&view=tree.
Add a new subject
data/catalog.json— add tosubjects.- Create
data/{id}-curriculum.json(copy structure frommath-curriculum.json). - Wire the subject in
themes/openfreebooks/templates/catalog.html(until auto-wire lands). - Add
content/{subject}/when you have chapters.
Catalog subject banner
Optional header image when browsing a subject in the catalog (List and Map). Subjects without a banner field keep the plain header.
- Prepare a 2:1 image (e.g. 2000×1000). Keep the large source file out of the repo if possible.
- Optimize to WebP under
static/catalog/banners/:
mkdir -p static/catalog/banners
# ffmpeg (requires libwebp in your build):
ffmpeg -y -i your-banner.jpg -vf "scale=1400:-2" -c:v libwebp -quality 82 \
static/catalog/banners/math.webp
# or cwebp (Homebrew: brew install webp):
cwebp -resize 1400 0 -q 82 your-banner.jpg -o static/catalog/banners/math.webp
- In
data/catalog.json, add a root-relative path on the subject:
{ "id": "math", "name": "Mathematics", "banner": "/catalog/banners/math.webp" }
- Check locally:
bun run build:js && zola serve→/catalog/?subject=math
Omit banner for subjects that should stay unchanged (e.g. roadmap-only placeholders).
Add a curriculum label
- Add the label to
curriculumsindata/catalog.json. - Add roles per chapter in
curriculumCoverage(and update Compare if needed). - Add badge style in
themes/openfreebooks/sass/_catalog.scssand mapping infrontend/src/lib/catalog-badge.ts.
Development & tests
bun install
bun run sync:chapters # after content/ chapter changes
bun run build:js # sync + Vite
zola serve
bun test
bun run build
- Do not commit
themes/openfreebooks/static/(Vite output). - Do not commit
public/.
Pull requests
- Keep changes focused.
- Run
bun run buildbefore opening the PR. - Describe subject, chapters, live vs planned, and curricula.
Questions? Open a GitHub discussion or issue.
For maintainers & agents
Machine-oriented detail: .agents/skills/ofb/SKILL.md.