上游 0029/0030 与本地 filelib 侧同号 ADR 相撞,cph 两份改到本地空闲号段, 代码锚点引用一并跟随。
10 KiB
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:
- 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. - 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.tomlkeeps[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.tomldeclaringkind+ scalar fields, plus convention-named content.typsiblings. A leaf has no outline manifest — its ownelement.tomlis 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 achildrenarray. - 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:
# 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
sectionopens 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].titlewhen 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'smanifest.tomlchildren order and each leaf'selement.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 rootchildrenwith the same leaf order (root is the implicit top container, so no wrapper folder is needed). The element sequence is unchanged, socph check/cph buildsemantics 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-lessgroup): 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.