# 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 `<field>.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 (`<eng-root>/<rel>`)
into the build root (`<build-root>/<rel>`), 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
  `<eng-root>/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 `<field>.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 <name>`, 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.
