Skip to content

curriculum: runtime JSON loading, retire CurriculumRegistry.swift

jedmund requested to merge jedmund/munich-v3 into main

Created by: jedmund

Summary

Initial migration: move curriculum from a 1339-line Swift literal to runtime JSON loading so authoring doesn't require Swift edits. Then, per feedback that the architecture needed to scale to ~600 lessons, add three follow-ups in the same PR:

Migration (commit 1):

  • lessons/**/*.json is now the runtime source of truth. New lessons/index.json + per-track lessons/{track}/track.json carry the track/module metadata that used to live in Swift. The 30 per-lesson files are unchanged.
  • ContentLoader.loadCurriculum() walks the tree and fatalErrors on any missing/malformed file.
  • CurriculumRegistry.swift deleted. PreviewFixtures.swift backs the three #Preview blocks.

Lazy lesson loading (commit 2):

  • New LessonSummary (id, track, module, position, title, tagline, ref path). Module.lessons: [LessonSummary].
  • loadCurriculum() now reads only lesson metadata at launch (not full payloads); the full Lesson loads on demand via ContentLoader.loadLesson(_:) when a lesson page opens. Cuts in-memory curriculum size ~10x at full scope.
  • track.json drops its lessons[] arrays; lesson files are auto-discovered by {module}-{position}.json filename pattern. Removes the "forgot to add to track.json" class of bug entirely.
  • LessonScreen is a thin wrapper that loads and hands the full Lesson to an inner LessonScreenContent.

Content fix (commit 3):

  • Six lessons (music-theory/2-1..2-6) were using device_categories: ["linnstrument"] where the established pattern is devices: ["linnstrument-200"], device_categories: []. Caught by the validator on its first run, fixed in a standalone commit.

Validator + pre-commit hook + CI (commit 4):

  • scripts/validate_curriculum.py walks the full content graph. Checks every path ref resolves; every tool value matches a ToolType raw value; every check.type matches a CheckType raw value; every device_categories value is in the known vocab; track/module/position match directory + filename; no orphan lesson files exist.
  • .githooks/pre-commit runs the validator before commit (enable with git config core.hooksPath .githooks); skips gracefully if uv isn't installed.
  • .github/workflows/validate-curriculum.yml runs the validator on every PR and main push as the hard gate.

Test plan

  • Swift build passes (iPad Pro sim, Metal Toolchain installed).
  • uv run --project scripts scripts/validate_curriculum.py passes locally.
  • Walk through the home screen and into a lesson in each track. Confirm all content renders identically to pre-change.
  • Edit a lesson title in JSON, rebuild, confirm the change appears with no Swift changes.
  • Break a config_ref temporarily; confirm the validator reports it with file:field location and exits nonzero; restore.
  • Install the pre-commit hook (git config core.hooksPath .githooks), attempt a bad commit, confirm it's blocked.
  • Open a draft PR with a broken lesson file; confirm the GitHub Action fails.

Merge request reports

Loading