# ADR 0036: Engineering-File Structure Is A Nested Outline Manifest ## Status Accepted. **Supersedes ADR-0008** on the concrete layout of a lesson's structure (the ordered `[[parts]]` arrangement), and discharges the "grouping/sectioning" and "manifest richness" gaps ADR-0008 left Open. ADR-0007 (the engineering file is a real directory tree) stands unchanged. ADR-0005's "a lesson is an ordered sequence of element instances" stands unchanged — order and membership are preserved; the *shape* that encodes them is now a tree. ## Context ADR-0008 encodes a lesson's order and membership as a **single flat `[[parts]]` array** in a root `manifest.toml`, where every `[[parts]]` entry is a `kind` + `path` to an element folder. Two forces now push against that flat shape: 1. **Real lessons are internally structured.** TH-144 has a `题目/` tree of problem/answer pairs and A/B/C sections that exist only in folder names today. Teachers think of a lesson as an **outline** — sections, sub-sections, groups of worked examples — not an unbroken flat list of ~40 parts. The admin/teacher surface (the 老师端 being built against the Hub) is supposed to show "the project structure, expanding each structural element to the files inside" — and today that structure is a giant flat scroll. 2. **Outline and file structure should correspond 1:1, not via a separate index.** The 7.31 design discussion landed on a shape where each level of the lesson is a folder whose manifest states that level's children — so the on-disk tree *is* the outline, self-descriptive, with no secondary artifact to drift out of sync. A flat root `[[parts]]` list, by contrast, names the whole lesson in one file and forces the folder tree to be a projection of it (or vice versa) with two sources of truth. ADR-0008 itself anticipated this: its Open Questions list "Per-part metadata, grouping/sectioning (TH-144's A/B/C structure is only in folder names today)" as explicitly not modeled. This ADR closes that gap. ## Decision ### A lesson is a tree of folders; each folder is a self-describing node whose manifest names its children The engineering file remains a real directory tree (ADR-0007). The ordering and membership encoding of ADR-0008 changes from **one flat root `[[parts]]`** to a **nested, per-folder outline**: - The root's `manifest.toml` keeps `[project]`, `[info]`, and the `[targets.*]` build configuration exactly as ADR-0008/0011 define them. - The lesson's **structure is expressed as a folder tree**, where every folder that groups children carries its own small **outline manifest** (per-folder `manifest.toml`, see *Name and discriminator* below) stating that level's ordered children. - A **leaf** is an element folder exactly as ADR-0008 defines it: an `element.toml` declaring `kind` + scalar fields, plus convention-named content `.typ` siblings. A leaf has no outline manifest — its own `element.toml` is its descriptor. - An **internal folder** (a grouping node) has an outline manifest but no `element.toml`: it is not an element, it is a container of elements/containers. It carries only structure and, optionally, group-level scalar metadata. ### The engineering-file root is itself the implicit top container Children live directly in the root `manifest.toml`'s `children` array — there is no mandated single top-level section folder. Rationale: migration is a pure flatten of the existing root `[[parts]]` into root `children` (same order, zero forcing); a mandated wrapper folder would be pure indirection for most lessons. A lesson that wants a top-level section simply creates one as a child (consistent with the ADR-0021 folder-tree precedent, which holds direct children at the root). ### Name and discriminator: every grouping folder uses `manifest.toml` Every folder that groups children uses the same filename, `manifest.toml`, at every level including the root: - Root `manifest.toml`: `[project]`, `[info]`, `[targets.*]`, plus a `children` array. - Internal folder `manifest.toml`: `children` (+ optional `[group]` scalars); never `[project]`/`[info]`/`[targets.*]`. The leaf/container discriminator is disjoint and structural: a folder with `element.toml` is a **leaf** (ADR-0008 descriptor); a folder with `manifest.toml` and no `element.toml` is a **container**; a folder with neither is a structural error. `OUTLINE.toml` was rejected (a new reserved name, no benefit over the uniform name); `info.toml` was rejected because it collides with the model's `Info` (title/author, folded into root `manifest.toml`'s `[info]` by ADR-0008) and would blur "metadata vs structure". The 7.31 sketch's intent — each level self-describes its children — is preserved; the name aligns with current ADR-0008. ### Order is encoded per-folder, and the lesson order is the depth-first traversal ADR-0005 requires the lesson to be an ordered sequence. In the tree, **order is declared locally at each folder** by the order of children in that folder's `manifest.toml`. The canonical lesson order is the **depth-first pre-order traversal** of the tree: an internal folder contributes no element of its own (its label is a heading, not a part), and leaves contribute in the order they appear. The checker materializes this traversal; no part of the lesson order lives in a typst script (ADR-0008's core rejection of typst-as-order-manifest stands). Concretely, a `segment` that in TH-141 was one flat `[[parts]]` entry can now be a folder whose outline lists its sub-segments and examples in order — and any grouping (TH-144's A/B/C, a "导言 cluster", a "例题组") is a folder, transparent in the element sequence but a real node in the outline. ### The outline manifest shape A folder's `manifest.toml` `children` array holds its ordered children. Each child entry is either: ```toml # a leaf element (ADR-0008 descriptor), by relative path { kind = "example", path = "examples/41届复赛三-1-混注石油" } # or an internal grouping folder, by relative path (recursed) { kind = "section", path = "导言簇" } ``` The `kind` of a **leaf** is still read from that folder's `element.toml` (ADR-0008: the folder is self-describing; the outline entry may restate it for readability but the `element.toml` is authoritative). The `kind` of an **internal** child is a container kind — recognized from a small, open set of container kinds — and selects how that subtree is rendered/grouped. Leaves and containers are disjoint by construction: a folder is a container iff it has a `manifest.toml`; a leaf iff it has an `element.toml`. A folder must have exactly one of the two. ### Container kinds: MVP ships exactly one — `section`, rendered as a heading The demonstrated needs (TH-144's A/B/C, a "导言簇") are all `section`, so the MVP ships exactly one container kind: - A `section` opens a **heading** at its depth in the DFS, then renders its children in order; it never appears in the element part sequence (the already-decided DFS semantics). - The heading uses `[group].title` when present, else the folder name. - `group` (a heading-less visual grouping) and any other container kind are **deferred**: added only when a real need appears, honoring ADR-0005's open universe / "add when needed". ### Group-level scalars An internal folder MAY carry a `[group]` table (e.g. a title distinct from the folder name, a description) in its `manifest.toml`. Kept minimal — no other container metadata until a real need appears. ### The flat `[[parts]]` array at the root is retired for structure The root `manifest.toml` no longer needs a root-level `[[parts]]` that names the whole lesson. Lesson structure lives in the folder tree, rooted at the engineering-file root's `manifest.toml` `children`. `[targets.*]` and `[project]`/`[info]` stay at the root `manifest.toml`. ## Consequences - The teacher/admin surface shows the outline: the folder tree *is* the lesson structure, self-descriptive and 1:1 with files. Opening an element reveals its files (as the 老师端 requirement asked). This discharges the 7.31 driver ("项目内部有一套 cph schema 定义的结构,由 manifest 组织,给老师看的应该是这个, 展开每个结构元素内部才是文件"). - The checker can still recover the full ordered lesson **without evaluating typst**: it reads the root `manifest.toml` (project/info/targets) and walks the folder tree, honoring each folder's `manifest.toml` children order and each leaf's `element.toml`. Order and membership remain declarative data, greppable and diffable. - Grouping (sections) is now a real, checkable structure rather than a folder naming convention — TH-144's A/B/C can be first-class. - Every folder is self-describing, so a subtree can be understood/moved on its own; nothing about a subtree's structure lives only in the root file. - Migration is mechanical: flatten the existing root `[[parts]]` into root `children` with the same leaf order (root is the implicit top container, so no wrapper folder is needed). The element sequence is unchanged, so `cph check`/`cph build` semantics for leaves carry over. ## Open Questions / Deferred - **Target-scoped container options** (e.g. hide a section in the student build): deferred, stay out of structure. ADR-0009/0011 field-visibility / per-target map already handles this in rendered output, not structure; keep it there unless a concrete need forces it back into the manifest. - **Additional container kinds** beyond `section` (e.g. a heading-less `group`): deferred until a real need appears (open universe, ADR-0005). - **Container metadata beyond `[group]`** (title/description): deferred — only the minimal `[group]` table ships; richer container scalars await a concrete authoring need. ## Supersedes ADR-0008's "the ordering manifest is declarative" decision stands; this ADR replaces its **flat `[[parts]]` encoding of order/membership** with the nested per-folder outline. ADR-0008's other decisions (declarative `manifest.toml`/ `element.toml`, folder self-description, content-file naming convention, schema as source of truth for which `.typ` files exist) are unchanged and carry into this tree form.