forked from EduCraft/curriculum-project-hub
feat(spec+checker): slides/逐字稿 markdown 内容面 + assembleMarkdown step(ADR-0015)
给课程加两个 markdown 内容面:slides(大纲:h2 part → h3 小节 → ![]() 图 +
:::widget 教具)与逐字稿(口播)。直接 markdown+KaTeX 撰写,绕开 typst→md
公式转换(等于在 slides 面选 ADR-0014 的 R2)。
spec:
- RichContent.lean: content leaf 带 format(typst|markdown),新增 ContentFormat
归纳,RichContentRef 加 format 字段
- Export/Render.lean: Step 加 assembleMarkdown (field),钉执行语义
(同 shell 三边界 + 注入课程 h1 + 自包含收集引用图)
- Courseware.lean: ADR 区间 →0015
实现:
- cph-schema: x-cph-content 支持 true→.typ / 字符串→该扩展名(.md);
ContentField 带 ext;4 个 kind schema 各加可选 slides/transcript
- cph-model: Step::AssembleMarkdown{field} + manifest "assemble-markdown" 解析
- cph-check: run_markdown_assemble_target / target_is_markdown_assemble
(照 run_shell_target 形状:check 门控、注入 h1、按 [[parts]] 序拼、写盘后
收集  引用图进 build 根使产物自包含、引用图缺失属 build-过程错误
不入 6 类诊断);compile_targets 已 filter TypstCompile,纯 assemble 自然排除
- cph-cli: run_markdown_assemble_build 路由
- cph-typst: template_step 补 AssembleMarkdown => None 分支
测试:全 workspace 49 passed(新增 6 个 markdown 装配测试含图收集);
lake build 25 jobs 绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,7 @@ impl Artifact {
|
||||
/// inductive Step where
|
||||
/// | typstCompile (template : String)
|
||||
/// | shell (run : String)
|
||||
/// | assembleMarkdown (field : String)
|
||||
/// ```
|
||||
///
|
||||
/// A step is a *typed* operation (extensible): `TypstCompile` compiles a
|
||||
@@ -170,9 +171,13 @@ impl Artifact {
|
||||
/// the manifest into it (`--input manifest=…`), which a bare `typst compile`
|
||||
/// string cannot express. `Shell` is the escape hatch for steps that resist
|
||||
/// declaration (ADR-0005's medium-only category (b): HTML interactive / npm
|
||||
/// builds). The on-disk `[[steps]]` entry's `type` discriminator selects the
|
||||
/// variant: `"typst-compile"` → [`Step::TypstCompile`], `"shell"` →
|
||||
/// [`Step::Shell`].
|
||||
/// builds). `AssembleMarkdown` concatenates a per-element markdown content
|
||||
/// field into a single-file markdown deliverable (ADR-0015: the slides outline
|
||||
/// and 逐字稿 transcript surfaces, authored directly in markdown+KaTeX to
|
||||
/// sidestep the typst→md conversion). The on-disk `[[steps]]` entry's `type`
|
||||
/// discriminator selects the variant: `"typst-compile"` →
|
||||
/// [`Step::TypstCompile`], `"shell"` → [`Step::Shell`], `"assemble-markdown"`
|
||||
/// → [`Step::AssembleMarkdown`].
|
||||
///
|
||||
/// As with [`Artifact`], this alignment is maintained by review, not CI.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
@@ -189,6 +194,15 @@ pub enum Step {
|
||||
/// The command line to run.
|
||||
run: String,
|
||||
},
|
||||
/// Assemble a single-file markdown deliverable by concatenating each
|
||||
/// element's `field` markdown content file in `[[parts]]` order. Mirrors
|
||||
/// Lean `Step.assembleMarkdown` (ADR-0015). Not a typst build — the
|
||||
/// framework owns the read/concatenate/write itself.
|
||||
AssembleMarkdown {
|
||||
/// The per-element markdown content field to assemble (e.g. `slides`,
|
||||
/// `transcript`).
|
||||
field: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Step {
|
||||
@@ -901,7 +915,9 @@ fn parse_step(name: &str, value: toml::Value, diags: &mut Vec<Diagnostic>) -> Op
|
||||
DiagCode::SchemaViolation,
|
||||
format!("target '{name}' has a step with a non-string `type`"),
|
||||
)
|
||||
.with_hint("set the step `type` to \"typst-compile\" or \"shell\""),
|
||||
.with_hint(
|
||||
"set the step `type` to \"typst-compile\", \"shell\", or \"assemble-markdown\"",
|
||||
),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
@@ -911,7 +927,9 @@ fn parse_step(name: &str, value: toml::Value, diags: &mut Vec<Diagnostic>) -> Op
|
||||
DiagCode::SchemaViolation,
|
||||
format!("target '{name}' has a step with no `type` discriminator"),
|
||||
)
|
||||
.with_hint("add `type = \"typst-compile\"` (or `\"shell\"`) to the step"),
|
||||
.with_hint(
|
||||
"add `type = \"typst-compile\"` (or `\"shell\"`, `\"assemble-markdown\"`) to the step",
|
||||
),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
@@ -948,16 +966,33 @@ fn parse_step(name: &str, value: toml::Value, diags: &mut Vec<Diagnostic>) -> Op
|
||||
None
|
||||
}
|
||||
},
|
||||
"assemble-markdown" => match take_string_field(&mut table, "field") {
|
||||
Some(field) => Some(Step::AssembleMarkdown { field }),
|
||||
None => {
|
||||
diags.push(
|
||||
Diagnostic::error(
|
||||
DiagCode::SchemaViolation,
|
||||
format!(
|
||||
"target '{name}' assemble-markdown step is missing a string `field`"
|
||||
),
|
||||
)
|
||||
.with_hint("add `field = \"slides\"` (or `\"transcript\"`) to the step"),
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
other => {
|
||||
diags.push(
|
||||
Diagnostic::error(
|
||||
DiagCode::SchemaViolation,
|
||||
format!(
|
||||
"target '{name}' has a step with an invalid `type` '{other}'; \
|
||||
valid values are \"typst-compile\", \"shell\""
|
||||
valid values are \"typst-compile\", \"shell\", \"assemble-markdown\""
|
||||
),
|
||||
)
|
||||
.with_hint("set the step `type` to \"typst-compile\" or \"shell\""),
|
||||
.with_hint(
|
||||
"set the step `type` to \"typst-compile\", \"shell\", or \"assemble-markdown\"",
|
||||
),
|
||||
);
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user