Files
curriculum-project-hub/docs/adr/0008-engineering-file-layout-conventions.md
T
sjfhsjfh 9b37914bfb docs(adr): add ADR-0011 (export builds = typed steps over templates), correct ADR-0008
Refines ADR-0009 by fixing the shape of an artifact and a build; corrects a
false premise that propagated into the implementation.

ADR-0011:
- Artifact is an ADT WITH fields: SingleFile{filepath} / FileTree{root,
  outputs:glob} — the product semantics (one file where, vs a tree of what) are
  pinned, not erased behind a bare tag.
- A target = artifact + ordered typed steps: typstCompile{template} (typed
  because the framework wires the manifest in — not a raw shell line) | shell{run}
  (the hatch where ADR-0005 category-b medium-only builds land).
- Presentation (numbly numbering, styling) lives in the template file
  (exports/<target>.typ), NOT the manifest; manifest [targets.*.numbering] removed.
- Correction (verified against typst source: typst-eval/src/import.rs:22,174;
  tests/suite/scripting/include.typ): include/import paths MAY be runtime-computed
  (only a bare `import expr` without `as` is restricted). So the framework does
  NOT generate a static-include driver — a template toml()-reads the manifest and
  includes each part via a computed path. Removes a whole generated layer.
- RenderRule-as-payload retired; coverage downgraded to a declaration (which
  kinds a target renders) feeding the unchanged renderIgnored warning.

ADR-0008: corrected the two clauses (the "generated driver / static #import"
wording) that were residue of the false premise, pointing them at the
template-injection model and citing ADR-0011.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 09:44:32 +08:00

6.8 KiB
Raw Blame History

ADR 0008: Engineering-File Layout — Declarative Manifest + Element Descriptors

Status

Accepted — for the layout conventions below. Some sub-decisions are deferred; see Open Questions / Deferred.

Discharges the layout question ADR-0007 left open. ADR-0007 fixed that the engineering file is a real directory tree but explicitly deferred the concrete layout: the manifest (name, location, contents), how element instances are arranged and how their order (ADR-0005's Lesson is an ordered sequence) is encoded, and how rich-content .typ files are named. This ADR fixes those.

Context

Two real sample engineering files exist (TH-141, TH-144). They encode the lesson as a typst file main.typ containing #imports of each element's main.typ plus a #let parts = (("segment", 模块), ("lemma", 模块), …) ordered tuple list. Each element is a folder under a per-kind directory (segments/, examples/, lemmas/, sop/); its fields are typst source files (textbook.typ, problem.typ/solution.typ, stmt.typ/proof.typ, sop.typ) wired up by the element's own main.typ via #let f = include "f.typ".

That embryo has one property in tension with the contract: the ordering manifest is itself a typst script. ADR-0005 rejected typst as the definition/scripting language (its typing is too weak), and ADR-0006 moved the kind schema off typst onto declarative JSON Schema. A checker that had to parse typst to recover the lesson's element order would re-introduce exactly the dependency those ADRs removed: the order — a first-class part of the Lesson contract — would be locked inside an evaluated typst program.

Decision

The lesson manifest is declarative, not a typst script

The lesson's order and membership live in a declarative manifest.toml at the engineering-file root, read directly by the checker. TOML is chosen for consistency with the samples' existing project.toml / info.toml / meta.toml. It subsumes project.toml and info.toml. Shape:

[project]
id   = "…"            # stable project id
name = "…"            # folder/display name

[info]                # passed through to render targets verbatim
title  = "…"
author = "…"

[[parts]]             # ORDER OF THIS ARRAY IS THE LESSON ORDER (ADR-0005)
kind = "segment"      # one of the known kinds (ADR-0006 / stdlib set)
path = "segments/开场对照导言"   # element folder, relative to root
# … one [[parts]] block per element, in order …

[targets.student]     # which export targets exist for this lesson
[targets.teacher]

The checker reads parts for order and membership. It must not parse typst to recover ordering — the declarative array is the single source of truth. The sample's main.typ #let parts is replaced by this; any typst entrypoint that imports elements in order is a generated build artifact derived from the manifest (see ADR-0007's note that the on-disk encoding of Lesson is separate from "it's a tree"), never the canonical order.

An element folder is self-describing via element.toml

Each element folder carries an element.toml declaring its kind and its scalar fields (per the kind's JSON Schema, ADR-0006):

kind = "example"
source = "41 届物理竞赛复赛第三大题(1)"   # a scalar field of the example kind

kind is explicit in the descriptor (not inferred from the parent directory name) so a folder is self-describing and the segments/-vs-lemmas/ directory grouping is a human convenience, not load-bearing for the checker.

content fields are convention-named .typ siblings

A kind's schema (ADR-0006) marks some fields as the content extension type. For each such field F, its value is the typst source file F.typ in the element folder. The schema — not element.toml, not a per-element main.typ — is the source of truth for which .typ files must exist. Concretely for the MVP kind set: segmenttextbook.typ; exampleproblem.typ, solution.typ; lemmastmt.typ, proof.typ; sopsop.typ. This drops the samples' per-element main.typ (#let textbook = include …): that field-to-file wiring is now implied by the schema + naming convention and materialized at build time by the export template, not hand-authored. (Originally this said "the generated driver"; ADR-0011 supersedes that — there is no generated driver, an export template reads the manifest and includes each part's content.)

A content field's denotation remains ADR-0006's: the module body content of F.typ, not its #let exports. The file sits at a real relative path (ADR-0007), so its spans resolve and its relative imports anchor within the tree (ADR-0006 import boundary).

Consequences

  • The checker recovers the full ordered lesson from manifest.toml + each element.toml without evaluating any typst — order and membership are plain data, diffable and greppable.
  • A per-target export template (a real, editable .typ file, e.g. exports/student.typ) reads the manifest and renders the lesson; it is the build entrypoint, not the canonical lesson. (This clause originally described a generated driver with static #imports, on a since-corrected belief that typst forbids runtime-computed include paths — see ADR-0011's Correction. Include/import paths may be computed, so no driver is generated.)
  • Element folders lose their per-element main.typ; element.toml + the schema's content-file convention replace it. Migrating the samples is a mechanical rewrite (the migration is part of the MVP).
  • path being a real relative folder makes "the lesson references element X" inspectable on disk; a dangling path is a structural error the checker can point at.

Open Questions / Deferred

  • Per-file render override. ADR-0005 says the (kind × target) render matrix lives in the engineering file with framework defaults the file may override. This ADR's [targets.*] only declares which targets exist; the MVP keeps the render rules entirely in the framework render layer with no per-file override. Whether/how a lesson overrides a render rule (a block in manifest.toml? a per-target file?) is deferred until a real override need appears. This is a known, surfaced gap against ADR-0005, not an oversight.
  • Question bank (题库) on disk. TH-144 has a 题目/ tree of problem/answer pairs outside parts. Its layout and its element relationship (ADR-0005 Deferred: reference vs inline) stay open; out of MVP scope.
  • Manifest richness. Per-part metadata, grouping/sectioning (TH-144's A/B/C structure is only in folder names today), and target-specific options on a part are not modeled here; add when needed rather than guessing now.
  • stdlib kind set & schema dialect remain as ADR-0006 left them; this ADR only fixes the on-disk arrangement, not the schemas themselves.