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>
6.8 KiB
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: segment ⇒ textbook.typ; example ⇒ problem.typ,
solution.typ; lemma ⇒ stmt.typ, proof.typ; sop ⇒ sop.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+ eachelement.tomlwithout evaluating any typst — order and membership are plain data, diffable and greppable. - A per-target export template (a real, editable
.typfile, 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). pathbeing a real relative folder makes "the lesson references element X" inspectable on disk; a danglingpathis 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 inmanifest.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 outsideparts. 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.