# ADR 0015: Slides & Transcript As Per-Element Markdown Content Surfaces ## Status Accepted — and to be implemented. Adds two new **markdown** content surfaces (`slides`, `transcript`) as per-element optional content fields, plus a typed `Step.assembleMarkdown` that concatenates them in parts order into a single-file markdown deliverable. Builds on **ADR-0005** ((a)-class fields like 逐字稿 live inside a kind's `ElementData`; (b)-class medium lives in the target build), **ADR-0006** (`content` leaves in a kind schema), **ADR-0011** (a target is an artifact + ordered typed steps; presentation lives in the template/content not the manifest), **ADR-0013** (non-typst targets ride a build step; failures are build-process errors outside the 6-class taxonomy), and **ADR-0014** (the markdown/HTML export direction — R1 shared typst-content vs. R2 target-specific content; math must survive as markup not images). ## Context The KenKen lesson (and curriculum generally) needs a **slides** deliverable — an **outline-grade** projection (titles + bullet points; detail belongs in the 讲义/教案 or the 逐字稿), aimed at a third-party platform whose interchange format is a **specific markdown dialect**. It also needs a **逐字稿** (transcript) — the spoken text, read aloud, no typography needed. Two prior facts made the design: 1. **slides content ≠ 讲义 content.** ADR-0014 already recorded that a markdown/HTML target's content does not fully overlap the PDF 讲义/教案. Forcing slides to be a projection of the same typst source (ADR-0014's R1) drags in the hard **typst→markdown math conversion** (typlite renders math as images; 0.15 MathML is accessibility-oriented and hard to turn into KaTeX; `mitex` is LaTeX→typst, the reverse direction — ADR-0014 §Context). 2. **slides/逐字稿 can be authored directly in markdown.** If `slides` and `transcript` are authored as **markdown + KaTeX from the start**, there is *no* typst→markdown conversion at all — the math is KaTeX source (`$…$`) on the day it is written. The math principle ("markup, not images", ADR-0014) is satisfied for free, not by building a fragile converter. So for the slides/transcript surfaces this ADR picks **ADR-0014's R2** (target-specific content) deliberately: it **sidesteps** the typst→md converter entirely. The converter (R1) remains an open problem only for the *讲义* surface, which is independent and out of scope here. The spec already pins where (a)-class fields live: `Element.lean` says "逐字稿、重点圈划 落在具体 kind 的 `ElementData` 内". So `slides`/`transcript` are modeled as **per-kind optional `content` leaves** (declared in each kind's JSON schema), consistent with the existing `textbook`/`problem`/`solution` content fields — except their **format is markdown, not typst**. This is a real extension to the `content` concept: a content leaf now carries a *format* (typst | markdown). `RichContent.lean` gains a `ContentFormat` and the content ref gains a `format` field. ## Decision ### Two new markdown content fields on every kind Each stdlib kind schema (`segment`, `example`, `lemma`, `sop`) gains two **optional** `content` fields — `slides` and `transcript` — whose format is **markdown** (`x-cph-content: "md"`, the new extension-bearing form of the marker; the old `x-cph-content: true` stays `.typ` for backward compat). Optional ⇒ a kind instance without them is legal (like a lemma's `proof`). Convention: `slides.md` is **outline-grade** (title + bullets; no worked detail — that lives in 讲义/逐字稿); `transcript.md` is the **spoken** text. ### A new typed step `Step.assembleMarkdown field` A target like `[targets.slides]` carries `[[…steps]] type = "assemble-markdown" field = "slides"` over a `single-file` artifact. The framework **owns** the assembly (not a `cat`): it walks the manifest `[[parts]]` **in order**, reads each element's `.md` if present (skips if absent), joins them with a blank-line separator, and writes the single file to the artifact's `filepath` (relative to the engineering root). It does **not** inject headings — each `slides.md`/`transcript.md` authors its own `# …` (presentation lives in the content, ADR-0011). This is **typed**, not `shell`, because ordered-read + skip-missing + write-to-artifact-path is cleaner owned by the framework than expressed as a shell pipeline, and it stays deterministic/testable. ### Self-contained build dir: the assembler collects referenced images The assembled `slides.md` is written under the build root (the artifact `filepath`'s parent, e.g. `build/`). For that directory to be a **standalone deliverable** — uploadable to the platform on its own — its `![](rel)` image references must resolve *within* it, not back into the engineering root. So the assembler, after writing the markdown, scans it for local `![](rel)` references and copies each referenced image from the engineering root (`/`) into the build root (`/`), preserving the relative path. This is part of the `assembleMarkdown` step's owned responsibility (one step, not a separate copy step), keeping a slides target a single typed step. Conventions that fall out of this: - **Image references are authored relative to the engineering root** (`assets/images/…`), which is *also* their form relative to the build root after the copy — so the same path string is correct both at authoring time (the source image exists at `/assets/images/…`) and in the assembled output. - **Widget references are build-relative** (`interactives/ex1.html`): widgets are *build artifacts* produced by a separate target (the `interactives` shell target writes `build/interactives/`), not engineering-root sources, so they are referenced but **not** copied by the assembler. A fully self-contained `build/` therefore requires running the `interactives` target first — a documented prerequisite, like `kendoku` reachability (ADR-0013). - **External URLs** (`http://`/`https://`/`data:`) are left untouched. - A referenced image **missing** at the engineering root is a build-process error (the build dir would be broken), reported by the build layer — it does **not** enter the 6-class taxonomy. ### Course-level h1 is injected The assembler prepends the course title (from manifest `[info] title`) as the document's `#` h1. This is course metadata, not per-element content; each element's `.md` then contributes its `##` part and `###` 小节. This is the one piece of presentation the assembler owns at the document level (ADR-0011's "presentation lives in the content" governs *per-element* presentation). ### Same three boundaries as the shell step (ADR-0013) `assembleMarkdown` is a **non-typst target**, identical in boundary to the shell step: 1. **opt-in by construction** — runs only on an explicit `cph build --target `, never in `check`. `check` validates lesson *structure*, not the outcome of assembling markdown. 2. **failure is a build-process error, not a lesson diagnostic** — an assembly failure (e.g. write error) does **not** enter the 6-class diagnostic taxonomy; it is reported by the build layer. We do **not** add an `AssembleMarkdown` diagnostic code (taxonomy stays 6, ADR-0010/0012/0013). 3. **non-typst targets skip typst compile** — `check`'s compile phase already filters to `TypstCompile`-bearing targets, so a pure `assembleMarkdown` target is naturally excluded. ### Single-file artifact now; structured FileTree deferred The artifact is a **single-file** markdown. The user reasoned that until parts are organized as an **ordered tree** (rather than the current flat ordered list), single-file is unavoidable — a loose FileTree of per-element `.md` files would lose the ordering/structure the manifest encodes. So: single-file now; a structured multi-file FileTree artifact is deferred until the parts tree-restructure lands (it is out of scope here and recorded as OPEN). ## Consequences - The framework gains two orthogonal content surfaces (outline slides, spoken transcript) authored directly in markdown+KaTeX, with **no** typst→md math conversion in their path. The math problem (ADR-0014) is *not* solved generally — it is *avoided* for these surfaces by choosing R2. - A third typed `Step` variant (`assembleMarkdown`) joins `typstCompile` and `shell`; the step universe is extensible by design (ADR-0011). - The `content` leaf concept broadens: it now carries a *format*. The `x-cph-content` marker accepts an extension (`"md"`), backward-compatible with the boolean form (`.typ`). `RichContent.lean` pins `ContentFormat`. - The 6-class diagnostic taxonomy is unchanged. - The 恒一小奥 KenKen lesson gains `slides`/`transcript` targets alongside its existing `student`/`teacher`/`interactives` targets — all from one engineering file through one CLI. ## Open Questions / Deferred - **Third-party markdown dialect.** Baseline CommonMark + KaTeX (`$…$`/`$$…$$`) ships now; the platform's specific dialect (page-break markers, callout / fenced-div conventions, heading-depth rules, math-delimiter quirks) is a transform layer to be added once the lesson's output is fed to the platform and the real requirements are observed. - **Structured FileTree artifact.** A multi-file markdown deliverable organized by the parts tree — deferred until the parts tree-restructure (itself a separate OPEN / future ADR; touches the manifest schema, render assembly, and all examples). - **讲义 (typst) → markdown.** Still ADR-0014's open R1/converter problem, *unaffected* by this ADR. Whether the 讲义 surface ever needs a markdown export is an independent decision. - **Universal vs. per-kind markdown surfaces.** `slides`/`transcript` are declared per-kind (4 schemas, duplicated) to match ADR-0005's "(a)-class lives in kind `ElementData`". Whether they should become a cross-cutting universal content surface is reconsidered alongside the parts tree-restructure.