Fixes the numbering bug (single pattern "一、" reused across levels → level-2
rendered "二、一、…"). display now takes a `config` dict; heading numbering uses
numbly per level — framework default ("{1:一}、","{1:1}.{2:1}","{1:1}.{2:1}.{3:1}")
→ 一、 / 1.1 / 1.1.1 — overridable via config.numbering.heading from the
engineering file (ADR-0009's file-resident override path, the layer whose absence
caused the bug). Verified: grep "二、一" = 0 across all smoke PDFs.
Adds @preview/numbly:0.1.0 (pure typst, zero transitive deps), VENDORED in-repo
at render/vendor/typst-packages/preview/numbly/0.1.0/ for network-free CI;
resolve via --package-cache-path render/vendor/typst-packages. parts contract +
student/teacher field visibility unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The heart of the pipeline. Embeds the typst 0.15 compiler as a library to
compile-check a lesson and export a PDF, with diagnostics mapped back to source
spans (the product's value).
- LessonWorld: a typst World over the engineering-file directory tree. vpath =
real relative path (ADR-0007); the per-target driver is mounted in-memory as
`main` (.cph/driver-<target>.typ, never written to disk); @local/cph-render is
resolved from render_dir (CPH_RENDER_DIR / with_render_dir), no package install
or network. Fonts via typst-kit FontStore (system CJK).
- Driver generation: from the manifest, per target — static root-relative
`include` of each content file (⇒ module body content, ADR-0006), assembled
into the frozen `display(info, target, parts)` call; optional lemma proof
emitted only if proof.typ exists; example `source` scalar threaded through.
- Diagnostics: typst SourceDiagnostic (errors + warnings) → cph_diag::Diagnostic.
0.15 DiagSpan handled via span.id() + WorldExt::range → file-relative
SourceSpan with 1-based line/col; driver-internal spans re-pointed with a
"generated driver" hint, never dropped. typst hints/trace carried through.
- Engine API: new / with_render_dir / compile_check / build_pdf.
- 6 tests incl. build_pdf_with_real_render — full World→driver→cph-render→PDF
producing real PDF-1.7 output (student 42KB / teacher 54KB). clippy/fmt clean.
Also fixes render/typst.toml `compiler = ">=0.15.0"` → `"0.15.0"`: typst 0.15's
manifest parser rejects a `>=` comparator on the compiler field (the 0.14 CLU
WU-2 tested against was lenient). This unblocks loading the real render package.
Pinned: typst/typst-pdf/typst-syntax/typst-library/typst-layout/typst-kit =0.15.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single entry `display(info, target, parts)` over an ordered parts array, the
frozen contract the generated typst driver calls. Per-kind dispatch for
segment/example/lemma/sop; student/teacher matrix derived internally from
`target` (student hides example solution + lemma proof; teacher shows all).
Content-field values are consumed as already-evaluated content (driver
`include`s them ⇒ module body, ADR-0006), never imported/stringified. Optional
fields (lemma proof, example source) handled gracefully; unknown kind/target
degrade without crashing. Zero @preview deps (CI-robust); CJK + math styled.
Smoke compiles to non-empty student/teacher PDFs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>