@chengzhao-dev/dsh-quarto-book 0.6.11 → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/skills/writing-quarto/quarto/mermaid.md +6 -0
- package/skills/writing-quarto/references/book-journey.md +1 -0
- package/skills/writing-quarto/scripts/verifying-content.py +83 -0
- package/skills/writing-quarto/steps/02-structure.md +1 -1
- package/skills/writing-quarto/steps/09-check.md +2 -0
package/README.en.md
CHANGED
|
@@ -29,7 +29,7 @@ Both skills are discovered from the package's `skills/` directory and loaded on
|
|
|
29
29
|
2. Open a session in the book repository. `_quarto.yml` needs a standalone `type: book` line, and the book needs at least one `.qmd` or `.md` chapter source; only then do both skill names appear.
|
|
30
30
|
3. Call a skill: `writing-quarto` for prose, `designing-theme` for the theme.
|
|
31
31
|
|
|
32
|
-
Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin only if the app refuses that write. To pin a version, write `npm:@chengzhao-dev/dsh-quarto-book@0.6.
|
|
32
|
+
Replace `--profile` with the profile you actually run. The desktop app always runs the `desktop` profile, and that profile is owned by the app, so `dsh --profile desktop` is rejected; install or upgrade with `dsh plugin --profile desktop add <package>`, and use the app's Settings → Plugins → Add plugin only if the app refuses that write. To pin a version, write `npm:@chengzhao-dev/dsh-quarto-book@0.6.12`; to follow a GitHub tag, write `github:chengzhao-dev/dsh-quarto-book#v0.6.12`.
|
|
33
33
|
|
|
34
34
|
Upgrading from the old unscoped package `dsh-quarto-book` (removed from npm with 0.3.4, so this only affects profiles that installed it earlier): remove it from the same profile first with `dsh plugin --profile <profile> remove dsh-quarto-book`, then add the scoped package. Keeping both installed registers `writing-quarto` and `designing-theme` twice at the same rank 550.
|
|
35
35
|
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
2. 在书仓库里打开会话。`_quarto.yml` 要有独立一行的 `type: book`,书根至少有一个 `.qmd` 或 `.md` 章节源文件;两个技能名这时才会出现。
|
|
30
30
|
3. 调用技能:写正文用 `writing-quarto`,改主题用 `designing-theme`。
|
|
31
31
|
|
|
32
|
-
`--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.
|
|
32
|
+
`--profile` 换成你实际运行的那个 profile。桌面应用固定跑 `desktop` profile,且该 profile 由应用独占管理,`dsh --profile desktop` 直接启动会被拒绝;安装或升级用 `dsh plugin --profile desktop add <包>`,应用拒绝写入时再走「设置 → 插件 → 添加插件」。需要固定版本时写 `npm:@chengzhao-dev/dsh-quarto-book@0.6.12`,跟 GitHub 标签时写 `github:chengzhao-dev/dsh-quarto-book#v0.6.12`。
|
|
33
33
|
|
|
34
34
|
从旧的无作用域包 `dsh-quarto-book` 升级(该包已随 0.3.4 从 npm 删除,只影响之前装过它的 profile):先在同一个 profile 里 `dsh plugin --profile <profile> remove dsh-quarto-book`,再装新包。两个包同时留着会以相同的 rank 550 各注册一遍 `writing-quarto` 和 `designing-theme`。
|
|
35
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chengzhao-dev/dsh-quarto-book",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "把一份 Quarto Book 写成可读、可查、可发布的中文技术书:九步章节写作、结构/链接/图表检查、统一主题与渲染验收。 · Write, check, and publish a Chinese Quarto Book: nine-step chapter writing, structure/link/figure checks, a consistent theme, and render acceptance.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -10,6 +10,12 @@ Quarto 只执行 ` ```{mermaid} ` 属性围栏;plain 的 ` ```mermaid ` 围栏
|
|
|
10
10
|
|
|
11
11
|
渲染核对方法:调用 `quarto_render_check`,在返回的 `html_report` 对应 HTML 中应出现 mermaid 图表容器(`mermaid-js` 或 `quarto-diagram` 类名),不应出现 `sourceCode mermaid` 高亮代码块。
|
|
12
12
|
|
|
13
|
+
## 无障碍名称
|
|
14
|
+
|
|
15
|
+
每个 `{mermaid}` 围栏都写角色与名称:`{.mermaid role="img" aria-label="调用链:Server 接收请求,交给 Controller 处理。"}`;`fig-cap` 给视觉读者,`aria-label` 给读屏软件,两处各写一句短名称。
|
|
16
|
+
|
|
17
|
+
名称说清谁和谁、按什么顺序、结果落到哪,不写“流程图”这类对象名。缺名称时 axe 报 `svg-img-alt`;渲染核对在 HTML 里搜 `<pre class="mermaid" role="img"`。
|
|
18
|
+
|
|
13
19
|
## 块首固定 init 指令
|
|
14
20
|
|
|
15
21
|
每个 `{mermaid}` 块的第一行写:
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- **对象章**只写本章对象:数据成员、构造后的状态、公开接口与实现选择。后章才展开的类型名、个数标题、块后归属段不写;「留在某类型」「由某类得出」「不放进本章」这类归属句整句删除。
|
|
25
25
|
- 对象章内的互斥结果写成无序列表,每项「情形:结果」,列表后停止;标题点名这一节的实体,不数条目(`八条线` 拆成 `线坐标表`、`成线判断`)。
|
|
26
26
|
- 总览章与对象章的分界见项目章节表;本书 `01-rules.qmd`、`02-mapping-layout.qmd`、`viewing-program.qmd` 与两份 index 是总览,`03` 起各章是对象章。
|
|
27
|
+
- 整书遍同时统一陈述范围:部分页使用项目名与「这一部分」,不把单个样例抬成全书;只有书首页概括全书。页首导语不复述目录与章节卡片,卡片入口只写章节标题与一句交付结果。
|
|
27
28
|
|
|
28
29
|
## 以井字棋书为例
|
|
29
30
|
|
|
@@ -54,6 +54,8 @@ CODE_META_RE = re.compile(r"对应的代码")
|
|
|
54
54
|
COINED_RELATION_RE = re.compile(r"(?:组成|依赖|继承|调用)边")
|
|
55
55
|
CARRIER_RE = re.compile("承载")
|
|
56
56
|
NEXT_SECTION_RE = re.compile(r"(?:下一节|上一节)")
|
|
57
|
+
CALLOUT_OPEN_RE = re.compile(r"^\s*:::\s*\{?\s*\.callout-")
|
|
58
|
+
OPENING_CLICHE_RE = re.compile(r"^(?:本章|本节|这一章|这一节)(?:将|会|要)|^下面(?:介绍|讲)")
|
|
57
59
|
LADDER_RE = re.compile(r"(?:分别交给|各说一层)")
|
|
58
60
|
SKILL_TONE_RE = re.compile(r"这里不定义|测试也不必|不必为[^。;\n]{0,20}再记|不写双层循环")
|
|
59
61
|
INLINE_NAME_RE = re.compile(r"`([A-Za-z_][A-Za-z0-9_]*)`")
|
|
@@ -95,6 +97,10 @@ MAX_HEADING_LEVEL = 3
|
|
|
95
97
|
MAX_PARAGRAPH_CHARS = 320
|
|
96
98
|
MAX_SENTENCE_CHARS = 95
|
|
97
99
|
|
|
100
|
+
# 导语长度由项目在 checks.json 的 lead_chars 里声明,例如 [60, 140];没有声明
|
|
101
|
+
# 就不检查,避免把一个项目的版面预算带进另一本书。
|
|
102
|
+
LEAD_SKIP_RE = re.compile(r"^\s*(?:::|\||```|~~~|[-*+]\s|\d+\.\s|>|#)")
|
|
103
|
+
|
|
98
104
|
|
|
99
105
|
@dataclass
|
|
100
106
|
class Issue:
|
|
@@ -495,6 +501,51 @@ def check_html_figure_block(path: Path, block: list[tuple[int, str]]) -> list[Is
|
|
|
495
501
|
return issues
|
|
496
502
|
|
|
497
503
|
|
|
504
|
+
def visible_chars(text: str) -> int:
|
|
505
|
+
label = LINK_LABEL_RE.sub(r"\1", text)
|
|
506
|
+
return len(re.sub(r"\s+", "", label))
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def check_lead(
|
|
510
|
+
path: Path, front: str, lines: list[str], issues: list[Issue], limits: list[int]
|
|
511
|
+
) -> None:
|
|
512
|
+
"""检查 title 与第一个标题之间的首段导语长度。
|
|
513
|
+
|
|
514
|
+
着陆页(body-classes 里有 index-page 或 homepage)按卡片组织,不受限。
|
|
515
|
+
"""
|
|
516
|
+
if "body-classes:" in front and ("index-page" in front or "homepage" in front):
|
|
517
|
+
return
|
|
518
|
+
first = len(lines)
|
|
519
|
+
for index, raw in enumerate(lines):
|
|
520
|
+
if HEADING_RE.match(raw):
|
|
521
|
+
first = index
|
|
522
|
+
break
|
|
523
|
+
paragraph: list[str] = []
|
|
524
|
+
start = 1
|
|
525
|
+
for index, raw in enumerate(lines[:first], start=1):
|
|
526
|
+
if not raw.strip() or LEAD_SKIP_RE.match(raw):
|
|
527
|
+
if paragraph:
|
|
528
|
+
break
|
|
529
|
+
continue
|
|
530
|
+
if not paragraph:
|
|
531
|
+
start = index
|
|
532
|
+
paragraph.append(raw.strip())
|
|
533
|
+
if not paragraph:
|
|
534
|
+
return
|
|
535
|
+
length = visible_chars(" ".join(paragraph))
|
|
536
|
+
low, high = limits[0], limits[1]
|
|
537
|
+
if length < low:
|
|
538
|
+
issues.append(Issue(
|
|
539
|
+
path, start,
|
|
540
|
+
f"导语 {length} 字符,短于 {low};补一句本章要解决什么",
|
|
541
|
+
))
|
|
542
|
+
elif length > high:
|
|
543
|
+
issues.append(Issue(
|
|
544
|
+
path, start,
|
|
545
|
+
f"导语 {length} 字符,超过 {high};先删下一块已经摆出来的内容",
|
|
546
|
+
))
|
|
547
|
+
|
|
548
|
+
|
|
498
549
|
def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> list[Issue]:
|
|
499
550
|
issues: list[Issue] = []
|
|
500
551
|
text = read_text(path)
|
|
@@ -511,6 +562,9 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
|
|
|
511
562
|
if leading:
|
|
512
563
|
body = "\n" * leading.count("\n") + body
|
|
513
564
|
lines = body.splitlines()
|
|
565
|
+
limits = project.get("lead_chars")
|
|
566
|
+
if isinstance(limits, list) and len(limits) == 2 and all(isinstance(v, int) for v in limits):
|
|
567
|
+
check_lead(path, front, lines, issues, limits)
|
|
514
568
|
in_fence = False
|
|
515
569
|
fence_lang = ""
|
|
516
570
|
fence_start = 0
|
|
@@ -561,6 +615,11 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
|
|
|
561
615
|
fence_blocks.append(fence_lines)
|
|
562
616
|
if "mermaid" in fence_lang:
|
|
563
617
|
mermaid_blocks.append(fence_lines)
|
|
618
|
+
if "aria-label=" not in fence_lang or "role=" not in fence_lang:
|
|
619
|
+
issues.append(Issue(
|
|
620
|
+
path, number,
|
|
621
|
+
'mermaid 图缺少无障碍名称:围栏写 role="img" 与 aria-label="…"',
|
|
622
|
+
))
|
|
564
623
|
if fence_is_html:
|
|
565
624
|
html_blocks.append(fence_lines)
|
|
566
625
|
fence_lines = []
|
|
@@ -637,6 +696,30 @@ def check_file(path: Path, root: Path, project: dict, chapters: list[str]) -> li
|
|
|
637
696
|
(n for n, raw in enumerate(lines, start=1) if HEADING_RE.match(raw)),
|
|
638
697
|
len(lines) + 1,
|
|
639
698
|
)
|
|
699
|
+
# 章首(第一个标题之前)只放自然段与列表:Callout 会把导语压成旁注,开篇套话则先讲
|
|
700
|
+
# 章节安排、不讲读者要做的事。着陆页是目录页,不受这条限制。
|
|
701
|
+
if path.name != "index.qmd":
|
|
702
|
+
opening_line = 0
|
|
703
|
+
opening_text = ""
|
|
704
|
+
for index, raw in enumerate(lines, start=1):
|
|
705
|
+
if index >= first_heading:
|
|
706
|
+
break
|
|
707
|
+
if index in fenced_lines:
|
|
708
|
+
continue
|
|
709
|
+
if CALLOUT_OPEN_RE.match(raw):
|
|
710
|
+
issues.append(Issue(
|
|
711
|
+
path, index,
|
|
712
|
+
"章首出现 Callout;第一个标题之前的导语只用自然段与列表(steps/02-structure.md)",
|
|
713
|
+
))
|
|
714
|
+
continue
|
|
715
|
+
if not opening_text and raw.strip() and not raw.lstrip().startswith(("|", ">", "-", "*", "+")):
|
|
716
|
+
opening_line = index
|
|
717
|
+
opening_text = raw.strip()
|
|
718
|
+
if opening_text and OPENING_CLICHE_RE.match(opening_text):
|
|
719
|
+
issues.append(Issue(
|
|
720
|
+
path, opening_line,
|
|
721
|
+
"引言以「本章将…/下面介绍…」开场;第一句写读者要做成的事(steps/02-structure.md)",
|
|
722
|
+
))
|
|
640
723
|
intro_blob_targets: set[str] = set()
|
|
641
724
|
for index, raw in enumerate(lines, start=1):
|
|
642
725
|
if index >= first_heading or index in fenced_lines:
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
**章际衔接(定结构检查项)**:第 N 章的引言、任务节与自测题只用第 1..N-1 章已建立的名字与概念;本章新术语在首次真正使用处就近落术语卡或行内括号;术语卡的升级判据见 `writing-quarto` 技能 `quarto/authoring.md`「Callout 升级判据」,写法见 `steps/03-blocks.md`。某概念接不上前章已建立的名字时,把它下沉回首次使用它的章建立,不在本章临时补课。
|
|
19
19
|
|
|
20
|
-
- **无标题引言**:第一个 `##` 之前,写成**连贯读者文**回答读者三问——从哪来(承接上一章已成立的结果)、这一章做什么(本章要解决的问题与读者将获得的能力,正面陈述;范围能并则并,不写否定式后章预告)、代码在哪(可选:若本章有主源文件,用**同段末句**自然语言点名并挂全章 blob 链接,不另起「代码写在…里」独立段)。第一句写出谁在什么情况下做本章这件事,类章同时写出类名;类型怎么实现放到后面的句子。不另开背景节。**形态硬限(用户决定)**:首选**恰好 1 个自然段**(通常 2–4 句);必要时允许 **1 个自然段 + 1 个列表**(列表前引导句写在同一段末,或段落后紧跟列表;列表不算第二散文段);**禁止**出现两个散文自然段并立,更禁止 ≥3 段、单句孤儿段、结尾导航段。列表仅用于并列范围/对象,任务细节下沉正文 `##`,不用顿号串交付物冒充散文。**摘录免责句永不写进读者正文**:「本章摘录源码服务于讲解」「片段不保证与源文件逐字一致」「不等于文件内顺序」类作者旁白一律删除(旧「示例位置句开口子」已废止),同源保证交给 `verifying-content.py`;摘录免责只保留在 技能的权威文件与检查脚本里。不放 Callout,不写后续章节内容,不用「后面的 X」式前向指称引入后续章节才建立的结构件(如「实现放在后面的编号目录里」)。引言容纳多个独立概念时**并段或改列表**,禁止拆成两个散文段;拆分判据见 `steps/04-paragraphs.md
|
|
20
|
+
- **无标题引言**:第一个 `##` 之前,写成**连贯读者文**回答读者三问——从哪来(承接上一章已成立的结果)、这一章做什么(本章要解决的问题与读者将获得的能力,正面陈述;范围能并则并,不写否定式后章预告)、代码在哪(可选:若本章有主源文件,用**同段末句**自然语言点名并挂全章 blob 链接,不另起「代码写在…里」独立段)。第一句写出谁在什么情况下做本章这件事,类章同时写出类名;类型怎么实现放到后面的句子。不另开背景节。**形态硬限(用户决定)**:首选**恰好 1 个自然段**(通常 2–4 句);必要时允许 **1 个自然段 + 1 个列表**(列表前引导句写在同一段末,或段落后紧跟列表;列表不算第二散文段);**禁止**出现两个散文自然段并立,更禁止 ≥3 段、单句孤儿段、结尾导航段。列表仅用于并列范围/对象,任务细节下沉正文 `##`,不用顿号串交付物冒充散文。**摘录免责句永不写进读者正文**:「本章摘录源码服务于讲解」「片段不保证与源文件逐字一致」「不等于文件内顺序」类作者旁白一律删除(旧「示例位置句开口子」已废止),同源保证交给 `verifying-content.py`;摘录免责只保留在 技能的权威文件与检查脚本里。不放 Callout,不写后续章节内容,不用「后面的 X」式前向指称引入后续章节才建立的结构件(如「实现放在后面的编号目录里」)。引言容纳多个独立概念时**并段或改列表**,禁止拆成两个散文段;拆分判据见 `steps/04-paragraphs.md`「拆分判据」。陈述范围跟页面一样大:部分页只说这一部分的对局与代码,只有书首页可以写「这本书」;第一句写读者要做成的事,不用「本章将…」「下面介绍…」开场。内容量硬限:导语 60–140 个可见字符(链接只算标签文字,空白不计;超过先删下一块已经摆出来的内容,不足补本章要解决什么);项目可在 `.agents/skills/quarto-book-project/checks.json` 用 `"lead_chars": [60, 140]` 声明,`verifying-content.py` 按此检查,着陆页不受限;token 预算见本文件「分层上限」;范围边界能并入承接段时不成列表。承接中引用其他章节一律用章节标题链接,不用「第 N 章」;**链接预算**:引言只挂承接上一章的那 1 个章节链接和本章主源文件的 blob 链接,其余章节链接下沉到正文真正用到它的句子,每处只挂当下需要的那个目标,同一段不堆多个章节链接(Google 技术写作指南把每个链接视为一次读者可能离开当前页的机会,链接越多,认知负担越重);本章回顾的下一章预告不写链接,直述预告一句(页面导航已有前进按钮),两者均见 `steps/06-wording.md`。任务概览默认并入承接句,正文小节就是任务的展开;预览列表仅当任务没有对应正文小节时使用(示例章四项工作列表已按此删除)。原 front matter `description` 的定位信息并入引言承接句,信息只保留引言一处、不同义两存;章节 qmd 不写 `description`(字段出处见 basics.md)。着陆页 `index` 按 landing 规则组织,不硬套本契约。
|
|
21
21
|
- **任务序列**:每节一个可验证结果。判定标准是任务结束时存在一个读者能自己复述的检查动作;只有「读完了解释」的内容并入相邻任务。概念 → 最小示例 → 操作 → 验证 → 边界,先让读者看到行为再解释规则;单次操作按「做什么 → 执行什么 → 看到什么 → 简短说明」推进。各章的任务节以该章标题里的对象为准,不把每一章都写成「目录、设计、构建、测试」四节。设计范围以这一章能在 `src/` 里核对的源文件收束。内容量硬限:任务级 `##` 通常不超过 4 个;`##` 与 `###` 开头的承接句数见本文件「分层上限」,写完直接进入代码、命令或列表;超出即为凑字数信号,禁令见 `steps/04-paragraphs.md`「凑字数禁令」。有 `###` 子节时,二级标题(`##`)的引言须承接上文;恰好 2 个子节时一句点一个,3 个或 4 个时不再逐个点名,点名交给三级标题的句式见本文件「标题分层」,点名用读者已认识的词,不点后文才定义的类型名;一句之内禁止把两个子节对象配成「由 X 定位,并保存 Y」「分别交给」式的两组对应(对照见 `examples/05-sentence-frame.md`「拉链句」)。交付清单或纯边界说明不单独设 `##`,并入引言或相邻工程总览节;同一头文件中连续的值类型优先按一个可验证结果归并到同一任务节。任务节内的讲述顺序按「类型 → 简单函数 → 更难的函数」排列,最难的组装放在最后,并满足先定义后调用:读者要跟着读懂的行内代码标识符,其行为必须已经出现过——上一章、上文,或同一小节里紧挨着的声明和一句行为——上一章已建立的名字视为已知;未定义名字的调用链、节首概览与编号步骤整段删除。`##` 导语只用白话,不点后文才定义的函数名。章内顺序的依据见 `SKILL.md`「九步」的定结构一条,本文件不复述。
|
|
22
22
|
- **常见错误**:服务失败后回来的读者。每个症状一个 `###`,标题后第一句写触发条件和可观察现象。误用型条目一段成文写原因与正确做法;原因与做法各需一句完整交代时(如原因牵涉私有段的设计动机),用症状句导语加「出错的原因/正确的做法」引导词列表,列表可作为节导语的合法收尾,导语预算只约束症状句;排查型故障展开定位 → 修复 → 验证三步。形态分野与细则是 `writing-quarto` 技能 `quarto/terminal.md`「常见错误条目形态与三步诊断流」。入门章节只保留最可能遇到的 2-3 项,不是完整 FAQ。
|
|
23
23
|
- **自测问题**:服务刚跑通主线、需要确认理解的读者。每章 0-7 道题,题目问本章已经写明的一件可观察结果(会怎样、是哪一个、从几开始),没有这种结果就不设题;只有本章写了原因、而且情景已经说全时,才用「为什么」。题面用本章已经用过的词说全,不用本章未定义的概念,不问「为什么不做成另一种」;题干是合上书的一问,只看本题题干、不看答案与上一题;题干代词的判法见 `steps/05-sentences.md`「代词不能代替未写出的对象」,允许「什么」——它在要一个名字或一句文字,不是指上文没出现的东西;题面里每个细节都要改变答案,答案不受细节影响时不单点某一种情形。题面只描述一个自洽局面,每个事实都要能同时成立,禁止把互相矛盾的局面写成题干(同一句里先说轮到甲方、又说这一步的用户是 O,读者听到的是程序自相矛盾,不是一次非法调用)——「轮到谁」和「这一步的用户」若是同一个对象,只写一次。要考拒绝,用正文已经用过的那条规则问可观察结果(正例:「空状态面板开局,轮到甲方。没轮到的一方来操作时,状态面板会改变吗?」,答案首句「不会。操作的用户不是当前用户,`Place` 返回 `kRejected`,状态面板和轮次都保持原样。」);不为了出题制造正文没走过的矛盾局面。一道题只问一件可观察结果,不把两个观察捆进同一题(如「状态面板是空的吗」与「轮到谁」并列),拆开或删去一个。固定导语「不要展开答案,试着自己把回答完整说出来,写完之后展开对照;说不出来的部分,回到对应小节对照正文。」;答案写在 `::: {.answer}` 围栏 div 内,由书内 `filters/answering-disclosure.lua` 渲染为默认收起的原生 `<details>`;答案只使用本章已讲过的概念。题目同样必须能由本章正文回答:答案依据须是正文已写明的内容,正文没讲的概念不设题;若问题值得问,先在正文补上依据再设题。答案先答所问:首句直接回答题面,一到两句,不补题面之外的对比、延伸或重复题面(对照与归属由正文承载)。问答中的文件、路径与标识符指称统一用行内代码,不挂链接(`verifying-content.py` 对自测节链接记 WARN);所属目录在答案前文已确立时,路径可省去重复的顶层前缀(如先写 `example/`,后文写 `include/state.h`)。
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
|
|
46
46
|
不手跑 `quarto render`,也不手跑 `checking-output.py`、`checking-callouts.py`、`checking-dom.py`。工具已经按当前书配置调用它们。
|
|
47
47
|
|
|
48
|
+
渲染之外还有一层可访问性核对:书配置的 debug 项里可打开 `axe: true`,Quarto 会在浏览器里加载 axe-core 报告 `heading-order`、`svg-img-alt` 这类问题。报告只作提示,改法是给图补 `aria-label`、补齐跳过的标题层级,不把行为项当作新步骤。
|
|
49
|
+
|
|
48
50
|
### 执行回执
|
|
49
51
|
|
|
50
52
|
检查结论确定后,调用一次 `quarto_pass_report`,把这次执行的结论压成一张卡片:
|