@chengzhao-dev/dsh-quarto-book 0.6.2 → 0.6.4

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.
Files changed (53) hide show
  1. package/README.en.md +60 -31
  2. package/README.md +53 -24
  3. package/lib/index.js +49 -7
  4. package/lib/tools/pass-report.js +12 -12
  5. package/lib/tools/render-check.js +231 -62
  6. package/lib/workspace.js +1 -0
  7. package/locale/en.json +1 -1
  8. package/locale/zh.json +1 -1
  9. package/package.json +13 -7
  10. package/skills/designing-theme/assets/theme/assets/fonts/lxgw-bright-code-common.woff2 +0 -0
  11. package/skills/designing-theme/assets/theme/assets/fonts/lxgw-bright-medium-common.woff2 +0 -0
  12. package/skills/designing-theme/assets/theme/css/content.css +15 -0
  13. package/skills/designing-theme/assets/theme/css/mermaid.css +5 -6
  14. package/skills/designing-theme/knowledge/reading-surface.md +1 -1
  15. package/skills/designing-theme/references/theme-system.md +5 -5
  16. package/skills/designing-theme/scripts/checking-layout.py +60 -12
  17. package/skills/designing-theme/scripts/measuring-pages.mjs +2 -2
  18. package/skills/writing-quarto/SKILL.md +7 -0
  19. package/skills/writing-quarto/examples/02-structure-shape.md +26 -12
  20. package/skills/writing-quarto/examples/03-block-after.md +14 -0
  21. package/skills/writing-quarto/examples/03-qmd-elements.md +45 -2
  22. package/skills/writing-quarto/examples/04-paragraph-cases.md +14 -0
  23. package/skills/writing-quarto/examples/04-paragraph-opening.md +2 -2
  24. package/skills/writing-quarto/examples/05-sentence-frame.md +4 -4
  25. package/skills/writing-quarto/examples/06-wording-cases.md +23 -0
  26. package/skills/writing-quarto/examples/07-layout-list.md +3 -3
  27. package/skills/writing-quarto/examples/08-appendix.md +1 -1
  28. package/skills/writing-quarto/quarto/authoring.md +7 -6
  29. package/skills/writing-quarto/quarto/basics.md +17 -16
  30. package/skills/writing-quarto/quarto/coords.md +2 -1
  31. package/skills/writing-quarto/quarto/figures.md +49 -0
  32. package/skills/writing-quarto/quarto/mermaid.md +41 -5
  33. package/skills/writing-quarto/quarto/prose.md +4 -4
  34. package/skills/writing-quarto/quarto/refactor.md +1 -1
  35. package/skills/writing-quarto/quarto/terminal.md +1 -1
  36. package/skills/writing-quarto/references/book-journey.md +38 -0
  37. package/skills/writing-quarto/references/token-budget.md +26 -0
  38. package/skills/writing-quarto/scripts/checking-callouts.py +2 -2
  39. package/skills/writing-quarto/scripts/checking-dom.py +12 -6
  40. package/skills/writing-quarto/scripts/checking-links.py +29 -13
  41. package/skills/writing-quarto/scripts/checking-names.py +3 -1
  42. package/skills/writing-quarto/scripts/checking-output.py +3 -3
  43. package/skills/writing-quarto/scripts/checking-prose.py +11 -7
  44. package/skills/writing-quarto/scripts/testing-content.py +264 -1
  45. package/skills/writing-quarto/scripts/verifying-content.py +442 -36
  46. package/skills/writing-quarto/steps/03-blocks.md +20 -8
  47. package/skills/writing-quarto/steps/04-paragraphs.md +19 -2
  48. package/skills/writing-quarto/steps/06-wording.md +53 -0
  49. package/skills/writing-quarto/steps/07-layout.md +1 -1
  50. package/skills/writing-quarto/steps/08-reread.md +1 -0
  51. package/skills/writing-quarto/steps/09-check.md +3 -3
  52. package/templates/quarto-book-project/SKILL.md +1 -1
  53. package/templates/quarto-book-project/checks.json +5 -0
@@ -40,11 +40,11 @@
40
40
  正文与标题各一面。**正文**用 `LXGW Bright Medium`(Klee One 中笔画 + Ysabeau Office 西文,登记 `font-weight: 400`,`font-synthesis: none` 下不合成粗体);**代码**用 `LXGW Bright Code`(Klee + Monaspace Argon);**标题**(h1–h6、导航书名、首页卡片小标题)用 `--heading-font` 的系统黑体栈(`Noto Sans SC` → 思源黑体 → `PingFang SC` → `Microsoft YaHei` → system-ui),请求 600/700 时由系统字面真正加粗。理由:单字面的楷体在 24px 以上笔画偏细、600 又不合成,标题与正文层级只靠字号,层级弱;对照实测 h2 换黑体后层级立刻清楚,且不新增字体文件、不影响正文观感。中文正文保持霞鹜楷体路线;文楷 Screen 与 Fixel 已移除。候选对比、授权与字面实测(数字等宽、标点宽度、`+` 墨迹 0.23em)见 `docs/research/font-selection.md`。西文来源依据:文楷仓库点名 Ysabeau;Bright 用齐线数字的 Ysabeau Office;文楷 Screen 的 Medium 笔画标 Regular、度量对齐 Roboto,不适合中西文都自托管的站点。
41
41
 
42
42
  - 文件与预算:`assets/theme/assets/fonts/` 每字体一个 `*-common.woff2`(共 2 个),单页合计 ≤ 1.6 MB、≤ 5 个文件。
43
- - 子集:沿用常用字表并覆盖全书正文与终端输出;生成方式 `pyftsubset --flavor=woff2 --no-hinting`。改结果文案或新增字符后,用 fontTools 读 woff2 cmap 复核(起因:子集共用字符表漏「胜」,结果行末字落到宋体);`checking-layout.py` 的覆盖断言在未声明 unicode-range 时直接读 cmap。
43
+ - 子集:沿用常用字表并覆盖全书正文与终端输出;生成方式 `pyftsubset --flavor=woff2 --no-hinting`。改结果文案或新增字符后,用 fontTools 读 woff2 cmap 复核(起因:子集共用字符表漏「胜」,结果行末字落到宋体);`checking-layout.py` 的覆盖断言在未声明 unicode-range 时直接读 cmap。新增字符时,按同版本上游 TTF(Bright 5.528、Bright Code 2.922)保留现 cmap 并入新码位后重建,输出到插件与书内两份;跑页面矩阵时用 `NODE_PATH` 指向插件的 `node_modules`。
44
44
  - 覆盖范围不只汉字:西文标点、数学/箭头、制表符、几何图形与全角形式(U+FF01–FF5E)同在断言内(`GLYPH_RANGES`)。漏一个字不会被发现,却会静默回落到系统 CJK 字体,按 0.5em 的中线画出——正文 `+` 墨迹在 0.23em,「++」会显得浮在汉字上方。表情符号(≥U+1F000、U+2600–U+27BF)按设计走系统 emoji,不在断言内。
45
45
  - 不逐符号位移:Bright 的数字、括号、`× ÷ = +` 共用同一条数学轴(+ 中心 0.23em,汉字 0.357em)。只抬 `++` 会脱离同行的符号,所以 `.plus-align` 这类 `translateY` 已删除;对齐问题只在字形缺失时补码位。
46
46
  - 同步点:改字体须同步 SCSS、tokens、fonts、OFL、资产与 `checking-layout.py`;Mermaid 的 `%%{init}%%` 与坐标图脚本的 `FONT_FAMILY` 用同栈短版(`LXGW Bright Medium, system-ui, sans-serif`),保证测量与渲染一致。
47
- - 栈尾 CJK 系统字体兜底(PingFang SC、Microsoft YaHei、Noto Sans CJK SC、Source Han Sans SC)保留,缺字时落系统黑体而不是浏览器默认宋体。
47
+ - 栈尾 CJK 系统字体后备(PingFang SC、Microsoft YaHei、Noto Sans CJK SC、Source Han Sans SC)保留,缺字时落系统黑体而不是浏览器默认宋体。
48
48
  - 不采用 Inter、思源黑体、JetBrains Mono、Cascadia、Iosevka、Inconsolata;Roboto/Segoe UI 不放进 `assets/theme/assets/fonts/`(OFL 政策)。
49
49
 
50
50
  ## 字号、行长与页面尺度(定尺度)
@@ -79,7 +79,7 @@
79
79
  - **列表与折叠**:marker 用 `--text-secondary`,折叠三角用正文色;答案盒子只用上下 hairline,不做卡片。
80
80
  - **首页目录**:不是卡片;每行一条顶线、透明底、无圆角,一行最多两列;单张卡占满整行,序号 `01` 起、动作链接靠右下,窄屏动作移到提要下方;hover 只把顶线换成 `--accent`。
81
81
  - **章节翻页**:一条顶线 + 左右两个文字链接,不用边框卡片;箭头用 `<` / `>`,hover 才转 `--accent`,下一页右对齐。箭头用字符而不用 SVG 是量过的:Bright 的 `<` `>` 墨迹中心在 0.23em,与同行西文同一轴,没有面包屑 `>` 那种偏高,换 SVG 反而要另配基线。
82
- - **章节线**:标题不画线。全书只有一种横线——通栏 1px `--card-border` 发丝线,只出现在顶栏底边、引用块上下边、表格 booktabs、首页目录每行顶线、翻页顶线、页脚顶线、答案折叠线;`h2::after` 的 2.25rem 短发丝线已删除(连同首页 `.feature-grid` 例外),章节节奏只靠 H2 的 `margin-top: 3.25rem` 与字号台阶。契约改动时同步本节与 `css/base.css`。
82
+ - **章节线**:标题不画线。全书只有一种横线——通栏 1px `--card-border` 发丝线,只出现在顶栏底边、引用块上下边、表格 booktabs、首页目录每行顶线、翻页顶线、页脚顶线、答案折叠线;`h2::after` 的 2.25rem 短发丝线已删除(连同首页 `.feature-grid` 例外),章节节奏只靠 H2 的 `margin-top: 3.25rem` 与字号台阶。约定改动时同步本节与 `css/base.css`。
83
83
  - **引用块**:上下各一条 1px `--card-border` 横线、正文色、无底色、无左右竖条;与 Callout 的 2px 左色条区分开(后者是类型标记,不是分隔线)。
84
84
  - **表格**:`display: block` + 块内横向滚动承载宽表;Chrome 的表格修复会把同一块里的 `thead`/`tbody` 包进一个匿名表,所以列宽仍逐列对齐(1280 与 390 实测一致),不需要 JS 量列宽。表题走 `caption-side: top`、左对齐、14px `--text-secondary`;`table-striped` 压平成与普通行同底。
85
85
  - **脚注**:区块顶部一条 1px `--card-border`、`margin-top: 3rem`、14px / lh 1.7、`--text-secondary`;回跳箭头随正文链接色。
@@ -112,7 +112,7 @@ Quarto Book 三栏;编译默认色板 `palettes/flexoki`(Flexoki Light / Dar
112
112
 
113
113
  ## 主题结构与装配
114
114
 
115
- 官方字段见 Quarto HTML format reference。下列路径与装配顺序是本项目契约。
115
+ 官方字段见 Quarto HTML format reference。下列路径与装配顺序是本项目约定。
116
116
 
117
117
  ### 文件职责
118
118
 
@@ -162,7 +162,7 @@ Solarized 是运行时层,不进 `_quarto.yml` 的 theme/highlight-style:读
162
162
  3. `callouts.css` 加 `.callout.callout-<name>`。
163
163
  4. 新语义写入本节令牌索引。
164
164
 
165
- ### 校验与高亮契约
165
+ ### 校验与高亮约定
166
166
 
167
167
  - `checking-layout.py` 校验令牌与布局;发布用 `1280/768/390/360` 明暗与触屏、窄屏浮层。
168
168
  - 高亮由 `_quarto.yml` 提供且与 palette `meta.md` 一致(默认 `palettes/flexoki/highlight-light.theme` / `highlight-dark.theme`,取色自 Flexoki 官方 VS Code 主题);Solarized 的语法色由 `palettes/solarized/syntax.css` 在 `html[data-palette="solarized"]` 下覆盖,山光的由 `palettes/hill-light/syntax.css` 在 `html[data-palette="hill-light"][data-mode]` 下覆盖,都不改编译配置。注释色必须在本书代码底(亮 `#F2F0E5`、暗 `#1C1B1A`)上达到 4.5:1:亮色注释用 `#575653`(6.42:1),暗色用 `#878580`(4.67:1)。
@@ -7,7 +7,7 @@ Node、Playwright 与 Edge 可用时,measuring-pages.mjs 验证真实几何。
7
7
  规范出处:本技能 `references/theme-system.md`。
8
8
 
9
9
  用法:python checking-layout.py [--root .] [--book-dir _book] [--browser]
10
- 退出码:0 = 关键令牌全部存在,1 = 有缺失。
10
+ 退出码:0 = 关键令牌全部存在,1 = 有缺失,2 = 渲染产物目录缺失,3 = 请求了 --browser 但运行时不可用。
11
11
  """
12
12
 
13
13
  import argparse
@@ -348,7 +348,12 @@ def resolve_node():
348
348
 
349
349
 
350
350
  def resolve_playwright_root():
351
- candidates = [os.environ.get("BOARD_GAMES_NODE_PATH"), os.environ.get("NODE_PATH")]
351
+ candidates = [
352
+ os.environ.get("BOARD_GAMES_NODE_PATH"),
353
+ os.environ.get("NODE_PATH"),
354
+ str(THEME_DIR.parents[1] / "node_modules"),
355
+ str(ROOT / "node_modules"),
356
+ ]
352
357
  cache = Path.home() / ".cache" / "codex-runtimes"
353
358
  if cache.is_dir():
354
359
  candidates.extend(
@@ -645,8 +650,52 @@ def read_font_cmap(path):
645
650
  return set(cmap)
646
651
 
647
652
 
653
+ def qmd_font_chunks(path):
654
+ """Split a qmd into prose text and code text for font coverage checks."""
655
+ text = path.read_text(encoding="utf-8", errors="ignore")
656
+ text = re.sub(r"\A---\s*\n.*?\n---\s*\n", "", text, count=1, flags=re.S)
657
+ text = re.sub(r"<!--.*?-->", "", text, flags=re.S)
658
+ prose_lines = []
659
+ prose_chunks = []
660
+ code_chunks = []
661
+ fence = None
662
+ fence_is_mermaid = False
663
+ buffer = []
664
+ for line in text.splitlines():
665
+ if fence is not None:
666
+ if line.strip().startswith(fence):
667
+ chunk = "\n".join(buffer)
668
+ if fence_is_mermaid:
669
+ prose_chunks.append(chunk)
670
+ else:
671
+ code_chunks.append(chunk)
672
+ fence = None
673
+ buffer = []
674
+ else:
675
+ buffer.append(line)
676
+ continue
677
+ match = re.match(r"^\s*(`{3,}|~{3,})(.*)$", line)
678
+ if match:
679
+ fence = match.group(1)
680
+ fence_is_mermaid = "mermaid" in match.group(2)
681
+ buffer = []
682
+ else:
683
+ prose_lines.append(line)
684
+ if fence is not None:
685
+ chunk = "\n".join(buffer)
686
+ (prose_chunks if fence_is_mermaid else code_chunks).append(chunk)
687
+ prose = "\n".join(prose_lines)
688
+ prose = re.sub(r"\]\([^)]*\)", "]", prose)
689
+ prose = re.sub(r"^\s*\[[^\]]+\]:\s*\S+.*$", "", prose, flags=re.M)
690
+ prose = re.sub(r"https?://\S+", "", prose)
691
+ prose = re.sub(r"<[^>]+>", "", prose)
692
+ inline_code = re.findall(r"`([^`\n]+)`", prose)
693
+ prose = re.sub(r"`[^`\n]+`", "", prose)
694
+ return [prose, *prose_chunks], [*inline_code, *code_chunks]
695
+
696
+
648
697
  def check_font_coverage(book_dir):
649
- """Check Chinese coverage for UI text and code text separately.
698
+ """Check Chinese coverage for prose and code in qmd sources separately.
650
699
 
651
700
  声明了 unicode-range 的 face 按声明区间判定。未声明时用 fontTools
652
701
  读取字体文件的真实 cmap 判定。起因:子集字符表漏字,正文落到系统宋体。
@@ -676,15 +725,11 @@ def check_font_coverage(book_dir):
676
725
  missing = {family: set() for family in FONT_FAMILIES}
677
726
  if not any(coverage.values()):
678
727
  return coverage, missing
679
- for html_path in sorted(book_dir.rglob("*.html")):
680
- html_text = html_path.read_text(encoding="utf-8", errors="ignore")
681
- page_parser = VisibleTextParser()
682
- page_parser.feed(html_text)
683
- code_parser = VisibleTextParser(code_only=True)
684
- code_parser.feed(html_text)
728
+ for qmd_path in sorted(book_dir.rglob("*.qmd")):
729
+ prose_chunks, code_chunks = qmd_font_chunks(qmd_path)
685
730
  for family, chunks in (
686
- ("LXGW Bright Medium", page_parser.chunks),
687
- ("LXGW Bright Code", code_parser.chunks),
731
+ ("LXGW Bright Medium", prose_chunks),
732
+ ("LXGW Bright Code", code_chunks),
688
733
  ):
689
734
  for chunk in chunks:
690
735
  for char in chunk:
@@ -798,7 +843,7 @@ def main():
798
843
  book_dir = ROOT / book_dir
799
844
  if not book_dir.is_dir():
800
845
  print(f"BookDir not found: {book_dir}. Run 'quarto render' first.")
801
- return 1
846
+ return 2
802
847
 
803
848
  css_texts = [
804
849
  p.read_text(encoding="utf-8", errors="ignore")
@@ -984,6 +1029,9 @@ def main():
984
1029
 
985
1030
  print()
986
1031
  if fail == 0:
1032
+ if args.browser and browser_note:
1033
+ print(f"SKIP browser-layout {browser_note}")
1034
+ return 3
987
1035
  if browser_note:
988
1036
  print(f"PASS theme-static;SKIP browser-layout {browser_note}")
989
1037
  else:
@@ -6,7 +6,7 @@
6
6
  import { mkdir, readdir, writeFile } from "node:fs/promises";
7
7
  import { createRequire } from "node:module";
8
8
  import { existsSync } from "node:fs";
9
- import { dirname, join, relative, resolve, sep } from "node:path";
9
+ import { delimiter, dirname, join, relative, resolve, sep } from "node:path";
10
10
  import { pathToFileURL } from "node:url";
11
11
 
12
12
  const argv = process.argv.slice(2);
@@ -79,7 +79,7 @@ function loadPlaywright() {
79
79
  try {
80
80
  return require("playwright");
81
81
  } catch (error) {
82
- for (const directory of (process.env.NODE_PATH || "").split(sep).filter(Boolean)) {
82
+ for (const directory of (process.env.NODE_PATH || "").split(delimiter).filter(Boolean)) {
83
83
  const candidate = join(directory, "playwright", "index.js");
84
84
  if (existsSync(candidate)) {
85
85
  return require(candidate);
@@ -16,6 +16,12 @@ metadata:
16
16
  - 项目带有 `.agents/skills/quarto-book-project/SKILL.md` 时,读完本技能再读它,并只按它指向的 `writing/` 文件读章节对照;没有就跳过,不要凭猜测补书名、章节名和术语。书内 `context/` 下的旧通用副本不再打开,写法以本技能为准。
17
17
  - 书仓库根目录的 `README.md` 只走第 5–8 步:它是仓库说明页,不是章节;不套第 1–4 步的任务与结构骨架,不写「常见错误」「自测问题」,不调 `quarto_render_check`。第 8 步发现问题只退回第 5、6、7 步。`README.en.md` 同样处理;`CHANGELOG.md`、`LICENSE`、`AGENTS.md` 不在写作范围。这条只在书仓库里适用(技能本身由 Book 门禁触发)。
18
18
 
19
+ ## 整书走法
20
+
21
+ 改多章或排目录时,先读 `references/book-journey.md`:先定全书主线与章节次序,再逐章走九步,最后做全书一致性回读。只润色一章时不读它。
22
+
23
+ 单章与整书共用同一套九步,不另造一套章节规则;项目有章节表时以项目表为准。技能文件体量受 `references/token-budget.md` 约束,新增整书规则只写进 `book-journey.md`。
24
+
19
25
  ## 两种走法
20
26
 
21
27
  增量润色沿九步走,只改判为不合格的地方,其余保持原样。全章重构仍是这九步,但第 1–2 步先定删留、不动句子,第 3–4 步只拆块和段,第 5–7 步才逐句改。
@@ -29,6 +35,7 @@ metadata:
29
35
  1. 定任务:写出读者做完能完成的那件事,再列这一章要覆盖的任务。规则在 `steps/01-task.md`。
30
36
  2. 定结构:排页面骨架、二级标题和篇幅预算,只定删留、标题与章题对齐。规则在 `steps/02-structure.md`。
31
37
  3. 排列内容块:导语、围栏、收束与 Callout 的次序,一个围栏只放正在讲的那一块。规则在 `steps/03-blocks.md`。
38
+ 图注写在图下、图前只写引出关系;三类图的统一写法见 `quarto/figures.md`。
32
39
  4. 定段落:一段一个对象,段间衔接落在话题切换处。规则在 `steps/04-paragraphs.md`。
33
40
  5. 定句子:主语选执行者,对象先行,一句一动作。规则在 `steps/05-sentences.md`。
34
41
  6. 定用词:统一术语、主动语态和语气。规则在 `steps/06-wording.md`。
@@ -81,22 +81,36 @@
81
81
 
82
82
  判定信号:引言一句话里用顿号或「并」串起三个以上交付物;树后段落逐项复述树内行尾注释。清单删掉后读者仍能说出成功判据,清单就该下沉到小节。
83
83
 
84
- ## 树注释点名文件与问答式读法句
84
+ ## 目录行替子文件说话
85
85
 
86
- 示例章改稿前的两个反例:树注释点名下层实现文件,与同棵树的职责注释不同层级;树后读法句用问答式并列把前半句再讲一遍,像写给工具的格式规格,不像给读者的解说(规则见 `steps/03-blocks.md` 与 `steps/01-task.md`):
86
+ 示例章改稿前,目录行把子文件的职责也写进来,读者会以为「打印与读入」是目录本身的任务;目录只说明这里装什么,文件才写职责(规则见 `steps/03-blocks.md`「情况:目录树」):
87
87
 
88
88
  ```text
89
- 不佳(树注释列举实现文件名):
90
- ├── apps/ #/ 终端程序
91
- │ ├── include/cli.h # 打印状态面板与读入坐标
92
- │ └── src/ #/ cli.cpp 与 main.cpp
93
-
94
- 改写(src/ 写“X 实现”,与规则库一侧的「规则库实现」平行;文件分工留给讲实现的章节):
95
- ├── apps/ #/ 终端程序
96
- │ ├── include/cli.h # 打印状态面板与读入坐标
97
- │ └── src/ #/ 终端程序实现
89
+ 不佳(目录替子文件说话,同棵树的层级也不一致):
90
+ ├── apps/ #/ 终端程序:打印与读入
91
+ │ ├── include/ #/ 终端程序的对外接口
92
+ │ │ └── cli.h # 终端交互
93
+ ├── cmake/ #/ 构建用的 CMake 脚本
94
+ └── tictactoe/ #/ 动态库:棋盘与对局
95
+ ├── include/ #/ 动态库对外接口
96
+ └── src/ #/ 动态库实现:随库一起编译
98
97
  ```
99
98
 
99
+ ```text
100
+ 改写(目录只写装什么,文件写职责):
101
+ ├── apps/ #/ 终端程序
102
+ │ ├── include/ #/ 终端程序接口
103
+ │ │ └── cli.h # 终端交互
104
+ ├── cmake/ #/ 构建脚本
105
+ └── tictactoe/ #/ 动态库
106
+ ├── include/ #/ 动态库接口
107
+ └── src/ #/ 动态库的实现
108
+ ```
109
+
110
+ 判定信号:目录注释出现冒号、顿号清单或替子文件写任务(「打印与读入」「棋盘与对局」),就是越界;目录只用名词短语(不超过 16 个显示列),文件注释才承担对象与动作(不超过 28 列、只列 1–2 项)。
111
+
112
+ ## 树后读法句问答式并列
113
+
100
114
  ```text
101
115
  不佳(树后读法句问答式并列):
102
116
  目录树在同一行里写出文件位置和职责,文件名回答“文件在哪里”,行尾注释回答“它负责什么”。
@@ -105,7 +119,7 @@
105
119
  目录树在同一行里写出文件位置和职责:文件名给出位置,行尾注释说明职责。
106
120
  ```
107
121
 
108
- 判定信号:同棵树里某行注释是文件名列举而非职责短语;树后句子前半句先概括,后半句再用「X 回答 A,Y 回答 B」拆开重讲。
122
+ 判定信号:树后句子前半句先概括,后半句再用「X 回答 A,Y 回答 B」拆开重讲。
109
123
 
110
124
  ## 树后换对象收束句
111
125
 
@@ -106,3 +106,17 @@ $ORIGIN 表示可执行文件所在目录,查找位置随文件移动。
106
106
  不佳:本章配置和运行的对象就是引言中的最小工程目录……代码块标题使用短文件名……避免标题栏被深层路径挤满:
107
107
  改写:最小工程目录如下,树内注释标出各文件职责。src/main.cpp 与 CMakeLists.txt 是配置与运行的两个关键文件。
108
108
  ```
109
+
110
+ ## 块后复述字面值
111
+
112
+ 围栏已经把常量和字段摆出来,块后再翻译一遍,读者得不到新信息:
113
+
114
+ ```text
115
+ 不佳(围栏已写 kBoardRows = 3、kBoardCols = 3、row 与 col;块后逐项复述):
116
+ 棋盘格子坐标由尺寸和一对行列位置组成。这块棋盘有 3 行 3 列,每个位置用一对行列数字定位。代码里,kBoardRows 和 kBoardCols 都取 3,CellPosition 的 row 与 col 保存这对数字。
117
+
118
+ 改写(块前只写坐标约定,块后不翻译围栏):
119
+ 棋盘坐标由行数和列数确定,行向下、列向右,左上角是 (0, 0)。一对行列写成 (row, col),先写行后写列;行、列的取值都在 0 到 2 之间。尺寸和坐标定义在 board.h 里:
120
+ ```
121
+
122
+ 判定信号:块后连续出现围栏内的常量名、字段名或数字,句子只是把 `X = 3` 读成「`X` 取 3」;删掉块后句,读者仍能从围栏和导语得到同一事实。
@@ -48,8 +48,8 @@ constexpr bool IsInRange(Position position) { … }
48
48
  不佳(图前图例句复述图注的方位信息,一段三句三个主题):
49
49
  `Position` 把坐标交给 `Board`,负数和行列为 `3` 的位置都会被拒绝。下面的图标出可用的 `(row, col)`。左上角是 `(0, 0)`,行向下增加,列向右增加。
50
50
 
51
- 改写(方位与图例由 fig-cap 独自承载,正文只留行为句与桥接句):
52
- 调用方把 `Position` 交给 `Board`,行列出现负数或 `3` 都会被拒绝。下面的图标出可用的 `(row, col)`。
51
+ 改写(方位与图例由 fig-cap 独自说明,正文只留行为句与桥接句):
52
+ 调用方把 `Position` 交给 `Board`,行列出现负数或 `3` 都会被拒绝。图列出可用的 `(row, col)`,桥接句不再交代图面方位。
53
53
  ```
54
54
 
55
55
  判定信号:两个围栏之间只有空行;图后直接进入与图无关的下一块;块前引语同时推进多个对象(如 `Position` 与 `IsInRange` 各讲一句);图注已写明的方位与图例信息,桥接段或图后正文再讲一遍。
@@ -78,6 +78,33 @@ constexpr bool IsInRange(Position position) { … }
78
78
 
79
79
  判定信号:图后句删掉任一分句中心仍成立;图注或桥接句已写明的信息在图后再讲一遍。图后正文只给图注讲不完的新结论,图后对照与句段侧的冒号分号堆叠见 `examples/04-paragraph-opening.md`「冒号后堆叠分号解释」。
80
80
 
81
+ ## 原生 SVG 图注对照
82
+
83
+ `{=html}` 围栏里的 SVG 不会被 Quarto 包成 figure;图注必须自己用 `<figure>` 和 `<figcaption>` 放在图下,不要在图前留一句“下图标出……”。
84
+
85
+ ````text
86
+ 不佳(图注占图前正文,SVG 裸放):
87
+ 下图标出这块棋盘上可用的 (row, col)。
88
+
89
+ ```{=html}
90
+ <svg class="coords-grid" role="img" aria-label="棋盘坐标。">…</svg>
91
+ ```
92
+ ````
93
+
94
+ ````text
95
+ 改写(图注在图下,正文只留引出关系的一句):
96
+ 代码给出行列上限和坐标字段,图把行列编号落到棋盘格子上。
97
+
98
+ ```{=html}
99
+ <figure class="book-figure">
100
+ <svg class="coords-grid" role="img" aria-label="棋盘坐标。">…</svg>
101
+ <figcaption>棋盘坐标图:3 行 3 列,每格标出 (row, col);行向下、列向右,左上角是 (0, 0)。</figcaption>
102
+ </figure>
103
+ ```
104
+ ````
105
+
106
+ 判定信号:`<svg>` 是否在 `<figure>` 内、`<figcaption>` 是否在 `svg` 之后;图前是否还有只交代图面的句子;图注与图后正文是否逐句重复。
107
+
81
108
  ## 块后解释先于图对照
82
109
 
83
110
  同一小节既有代码块项目解释又有图时,解释紧跟代码块,桥接句与图在后;图后不再回头讲代码项。
@@ -210,3 +237,19 @@ CTest(CMake 自带的测试运行工具)… 随后跟上术语卡又把同
210
237
  ```
211
238
 
212
239
  判定信号:术语卡的定义句与行内括号逐字同义(升级无收益);Callout 内出现删掉后读者无法走通主线的必经动作。
240
+
241
+ ## 节末旁注改为 Callout 收尾
242
+
243
+ 不佳(`##` 最后一块是裸句,读者分不清它是结论还是旁注):
244
+
245
+ 六个目录各管一件事(列表略)。`build/` 里没有源码,删掉或换一个目录后重新配置、编译就会按源码重新生成。
246
+
247
+ 改写(末句独立成 Callout,正文不再保留):
248
+
249
+ 列表略。
250
+
251
+ ::: {.callout-note title="构建目录不含源码"}
252
+ 构建目录 `build/` 里只有生成物,没有源码。整个目录可以删掉,也可以换一个路径;下一次配置和编译会按源码重新生成。
253
+ :::
254
+
255
+ 判定信号:`##` 最后一块是一句可跳过的补充,前面是列表、表格或围栏;Callout 能独立读出对象与条件,且不与正文任何一句逐字相同。末句是本节结论、下一章入口或读者必须照做的步骤时,不升级。
@@ -85,3 +85,17 @@
85
85
  ```
86
86
 
87
87
  判定信号:读者复述不出这段要记住什么;删掉整段后导语与围栏仍闭合;段内另一件内容在后文小节已有专讲。
88
+
89
+ ## 章节核心漂移
90
+
91
+ 标题是棋盘,章首却把格子的用途列成一段,主语从 `Board` 滑到「格子」:
92
+
93
+ ```text
94
+ 不佳(标题是棋盘,首句却在数格子能推出哪些判断):
95
+ 棋盘是 3 行 3 列,共九个格子。每次落子先检查坐标;满盘、三连和赢家都从这些格子判断。
96
+
97
+ 改写(首句主语回到 Board 的保存职责,判断留给后文小节):
98
+ 棋盘是 3 行 3 列,共九个格子,Board 保存这些格子,并提供写入与读出的接口。
99
+ ```
100
+
101
+ 判定信号:章首或 `##` 首句的主语是下游对象(格子、对局、终端),而不是标题对象;把主语换成标题对象后句子仍成立,说明原句已经漂移。
@@ -44,7 +44,7 @@ Game::GetResult 这三个成员。Game::ReadCellState 与 Game::GetResult 声明
44
44
 
45
45
  ```text
46
46
  不佳(一段压三问):
47
- 配置由 CMake 完成:读取 CMakeLists.txt,在 build/ 下生成构建计划。构建由 Ninja 完成:按计划把源码编译成可执行文件。所以改了 CMakeLists.txt 要重跑配置,而只改 main.cpp 时重新构建就够了。
47
+ 配置由 CMake 完成:读取 CMakeLists.txt,在 build/ 下生成构建计划。构建由 Ninja 完成:按计划把源码编译成可执行文件。所以改了 CMakeLists.txt 要重新运行配置,而只改 main.cpp 时重新构建就够了。
48
48
 
49
49
  改写(引言 → 引导词列表 → 收束):
50
50
  配置和构建是两件不同的事,由不同工具完成。
@@ -52,7 +52,7 @@ Game::GetResult 这三个成员。Game::ReadCellState 与 Game::GetResult 声明
52
52
  - **配置**:由 CMake 完成,读取 CMakeLists.txt,在 build/ 下生成构建计划。
53
53
  - **构建**:由 Ninja 完成,按计划把源码编译成可执行文件。
54
54
 
55
- 改了 CMakeLists.txt 要重跑配置;只改 main.cpp 时重新构建即可。
55
+ 改了 CMakeLists.txt 要重新运行配置;只改 main.cpp 时重新构建即可。
56
56
  ```
57
57
 
58
58
  ## 问答对象错位
@@ -41,7 +41,7 @@
41
41
 
42
42
  ```text
43
43
  不佳(「各说一层」把 PlaceResult 与 Result 的分工配进一句,删掉任一配对另一照常成立):
44
- 操作结果和流程结果各说一层:`PlaceResult` 回答这一次操作写进状态面板没有,`Result` 回答整局流程进行到了哪一步。
44
+ 操作结果和流程结果各说一层:`PlaceResult` 表示这一次操作有没有写进状态面板,`Result` 表示整局流程走到哪一步。
45
45
 
46
46
  改写(两个类型已在前文各配导语和围栏,分工不再复述,直接进入下一块):
47
47
  (整段删除。下一块改为一句桥接:这些类型由 `Game` 的四个数据成员使用,都声明在 `state.h` 的 `private:` 下。)
@@ -107,15 +107,15 @@
107
107
 
108
108
  ## 定句子:两次说话拆成一次对话
109
109
 
110
- 条文见 `steps/05-sentences.md`「一次对话」。总述叠了几件内容:有没有写进状态面板、整局走到哪、它们是两个问题、用两个枚举分开答,下一句又改口「接着看」「第一个枚举」。改写发生在定句子:每件内容一次对话,紧挨自己的围栏。
110
+ 条文见 `steps/05-sentences.md`「一次对话」。总述叠了几件内容:有没有写进状态面板、整局走到哪、它们是两个问题、用两个枚举分开表示,下一句又改口「接着看」「第一个枚举」。改写发生在定句子:每件内容一次对话,紧挨自己的围栏。
111
111
 
112
112
  **定句子(示例章两个枚举的总述):**
113
113
 
114
114
  ```text
115
115
  不佳(几件内容叠进一次说话,读者要拿开头的问题去猜「第一个」是哪个;「走向」又与开头的「进行到了哪一步」对不上):
116
- 至于操作有没有写进状态面板、整局进行到了哪一步,是两个不同的问题,`Game` 用两个枚举分开回答。接着看操作可能写入状态面板、也可能被拒绝的处理结果,用第一个枚举 `PlaceResult` 表示:
116
+ 至于操作有没有写进状态面板、整局进行到了哪一步,是两个不同的问题,`Game` 用两个枚举分别表示。接着看操作可能写入状态面板、也可能被拒绝的处理结果,用第一个枚举 `PlaceResult` 表示:
117
117
 
118
- 第二个枚举回答流程整体的走向,用 `Result` 表示:
118
+ 第二个枚举表示流程整体的走向,用 `Result` 表示:
119
119
 
120
120
  改写(每件内容一次对话,说法从开头保持到围栏,中间不编号):
121
121
  这次操作有没有写进状态面板,用 `PlaceResult` 表示:
@@ -86,6 +86,7 @@ state.h 里 `private:` 下的 ToFlatIndex 只写函数名与参数表,函数
86
86
  | 错误被返回 | API 返回错误 |
87
87
  | 刻意保持最小 / 刻意…… | 直接陈述程序做了什么、不包含什么 |
88
88
  | 只可能来自 A,不可能来自 B | 写成可核对的排查范围:工程不含 B → 失败时先查 A |
89
+ | 只保存 / 只把 / 只返回 / 只有(范围断言) | 直接写动作与对象;需要限定范围时写出尺寸、条件、文件名或入口。「只读」「只写」作固定术语保留 |
89
90
  | 由 N 段命令定义:A、B 和 C | 直接点名对象:这段规则定义可执行目标 app,规定产物目录、源文件和 C++20。 |
90
91
  | 规则库 | 项目动态库(读者正文与代码注释、CMake 注释统一此定名) |
91
92
  | 即胜着 / 更好的着 / 随机着法 | 马上能赢的一步 / 对当前用户更有利的一步 / 随机操作 |
@@ -177,3 +178,25 @@ CMakeLists.txt 把编译和测试串起来:先 cmake --build build,再跑 ct
177
178
  ```
178
179
 
179
180
  判定信号:删掉「下面我们来看」「至关重要」「无缝」「赋能」后,读者取走的信息不变;同一动作在一段里换了三个说法;正文出现对读者的招呼或「如有疑问请联系」。
181
+
182
+ ## 无信息提问与自造关系词
183
+
184
+ 正文不向读者发问,也不把关系另造成一个新名词:
185
+
186
+ ```text
187
+ 不佳(读者被问一个与当前任务无关的问题):
188
+ 总览图为什么不列成员?
189
+
190
+ 改写(删除;图注已经说明总览的范围):
191
+ (删除)
192
+ ```
193
+
194
+ ```text
195
+ 不佳(把组成关系说成「组成边」,再让读者解释它):
196
+ Game 与 Board 之间的组成边表示什么?
197
+
198
+ 改写(用通行关系词直接陈述):
199
+ Game 持有一个 Board 作为组成部分。
200
+ ```
201
+
202
+ 判定信号:正文句以「为什么 / 为何 / 表示什么」收尾并带问号;出现「×边」式自造关系词;把图的取舍或成员多少写成读者需要回答的问题。
@@ -114,10 +114,10 @@ $$
114
114
 
115
115
  ```text
116
116
  不佳(五句各用句号,顿号串在分句之间):
117
- `Board` 只保存九个棋盘格子。落子、满盘、成线与赢家都由它回答。写入前先检查坐标。声明在工程目录的 board.h。实现放在 board.cpp。
117
+ `Board` 只保存九个棋盘格子。落子、满盘、成线与赢家都从这些格子得出。写入前先检查坐标。声明在工程目录的 board.h。实现放在 board.cpp。
118
118
 
119
- 改写(共享主语并成总说,四类问题用冒号列举,声明与实现并成一句):
120
- `Board` 只保存九个棋盘格子。它回答四类问题:坐标是否越界、棋盘是否已满、是否成线以及赢家是谁。写入前先检查坐标。接口声明在工程目录的 board.h,实现放在 board.cpp。
119
+ 改写(共享主语并成总说,四类结果用冒号列举,声明与实现并成一句):
120
+ `Board` 从九个棋盘格子得出四类结果:坐标是否越界、棋盘是否已满、是否连成三个、赢家是谁。写入前先检查坐标;接口声明在工程目录的 board.h,实现放在 board.cpp。
121
121
  ```
122
122
 
123
123
  判定信号:同一段连续 4 个以上句号,且多数句子短于 15 字;顿号用在分句之间;「声明在…的…」把位置拆成两句。
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 操作节删除离题辩白
6
6
 
7
- 以 示例章运行一键脚本节为对照。脚本节标题是「怎么跑、看到什么」,嵌在块后的「幂等 / 跳过 / 配置漂移」长论与标题无关,新人缺语境;删除后主线(执行脚本 → 核对输出)仍闭合,「必须重跑配置」已由「修改 CMakeLists 后行为没变」排查节与脚本注释覆盖:
7
+ 以 示例章运行一键脚本节为对照。脚本节标题是「怎么跑、看到什么」,嵌在块后的「幂等 / 跳过 / 配置漂移」长论与标题无关,新人缺语境;删除后主线(执行脚本 → 核对输出)仍闭合,「必须重新运行配置」已由「修改 CMakeLists 后行为没变」排查节与脚本注释覆盖:
8
8
 
9
9
  ```text
10
10
  不佳(操作节内的工程取舍长论):
@@ -2,13 +2,13 @@
2
2
 
3
3
  ## 文件关系先于单页润色
4
4
 
5
- 多个页面一起修改时,先确定入口、索引和正文的职责。根首页只回答「这是什么、从哪一部分开始」,part 落地页只安排顺序,正文负责一个可完成的任务。前置页面不提前展开后续命令,正文不重复落地页给出的路线。
5
+ 多个页面一起修改时,先确定入口、索引和正文的职责。根首页只说明「这是什么、从哪一部分开始」,part 落地页只安排顺序,正文负责一个可完成的任务。前置页面不提前展开后续命令,正文不重复落地页给出的路线。
6
6
 
7
7
  包含实际源码和构建结构的章节,应在第一次展示源码前加入目录树,展示前核对真实磁盘。目录树细则见 `steps/03-blocks.md`。
8
8
 
9
9
  ## 页面增长与拆页
10
10
 
11
- 教学页达到 `steps/02-structure.md` 的 token 上限(整章 5000 token)后,按读者任务拆页或上调本书阈值,不做「上篇/中篇/下篇」式机械切分。每页只改变一个主要工程变量或回答一个可验证问题。每个新页面都登记到 `_quarto.yml` 与所属 part 的落地页,并拥有独立的代码路径。
11
+ 教学页达到 `steps/02-structure.md` 的 token 上限(整章 5000 token)后,按读者任务拆页或上调本书阈值,不做「上篇/中篇/下篇」式机械切分。每页只改变一个主要工程变量或解决一个可验证问题。每个新页面都登记到 `_quarto.yml` 与所属 part 的落地页,并拥有独立的代码路径。
12
12
 
13
13
  超限先判断内容是否必要:凑字数句删除,必要讲解保留并按读者任务拆页。长代码不得以内联围栏绕过预算;先下沉为项目源码(例如 `src/` 或 `src/`,以项目实际目录为准),再在页面中摘录关键片段。
14
14
 
@@ -76,8 +76,9 @@ README 遵循 GitHub Markdown。根目录 `README.md` 是在线阅读链接的
76
76
 
77
77
  项目解释不得插队到图之后:禁止「导语 → 代码 → 图 → 再解释代码项」,图后正文不再回头讲代码块内的对象。代码块与图之间只有空行、或图后不接结论直接进入与图无关的下一块,都视为堆叠;结论已并入桥接段的小节收尾图除外。反例与改写对照见 `steps/03-blocks.md` 与 `quarto/coords.md`。
78
78
 
79
- 图的放置另有三条边界:
79
+ 图的放置另有四条边界:
80
80
 
81
+ - **图注与正文分工**:图注写图面内容与读法,图前句写这张图核对哪条关系;图前不写“下图标出……”这类只交代图面的句子,图注落在图下。三类图的图注写法见 `quarto/figures.md`。
81
82
  - **一图一事**:一张图只核对一条已讲清的关系。图需要同时表达两条关系时,拆成多张图,或按关系拆成多个 `###`,每节一图。
82
83
  - **同主题相邻**:代码块与图必须是同一对象的两种呈现。下一块与图主题不同时,先用一句正文收束本对象,再另起 `###`;禁止「代码 A → 一句拐弯 → 图讲 B → 再贴代码 C」的同节混排。
83
84
  - **图不承载正文未写的新信息**:图中的每个框、箭头或标记都能在图前正文找到对应对象与关系;读者需要新概念才能读懂的图,先在正文补出该概念,再放图。
@@ -94,7 +95,7 @@ README 遵循 GitHub Markdown。根目录 `README.md` 是在线阅读链接的
94
95
  | 深入机制 | `important` | 读者可跳过但不影响成功 |
95
96
  | 严重且不可逆 | `caution` | 数据丢失、需要停手确认 |
96
97
 
97
- 带标题的 Callout 使用 `::: {.callout-note title="中文标题"}`,标题由块属性提供,块内不再写 `##` 标题。Callout 放在它所补充的段落或小节内容的最后面,前后各留一个空行;术语卡是唯一就近放置的例外,紧跟术语参与的那段解释之后。必经步骤不进 callout。结果型 Callout 的边界见 `steps/03-blocks.md`。
98
+ 带标题的 Callout 使用 `::: {.callout-note title="中文标题"}`,标题由块属性提供,块内不再写 `##` 标题。Callout 放在它所补充的段落或小节内容的最后面,前后各留一个空行;术语卡是唯一就近放置的例外,紧跟术语参与的那段解释之后。必经步骤不进 callout。结果型 Callout 的边界见 `steps/03-blocks.md`。节末旁注的升级与位置见 `steps/03-blocks.md`「节末旁注与 Callout 收尾」;正文与 Callout 不复述同一句。
98
99
 
99
100
  参数解释默认使用短无序列表。Callout 不承载必经命令或构建命令,也不把唯一成功判据写成脱离正文的脚注。
100
101
 
@@ -102,7 +103,7 @@ README 遵循 GitHub Markdown。根目录 `README.md` 是在线阅读链接的
102
103
 
103
104
  何时升级为 Callout、何时留行内括号,只会遇到两种情形:
104
105
 
105
- - **升级**:说明需要的文字超过一句同位短语,且确有读者会回看的价值——术语卡(业界叫法 + 通过条件,就近放置)或尾置补充(正文与围栏注释之外确有独立价值的一点,如风险、版本差异、可选替代)。
106
+ - **升级**:说明需要的文字超过一句同位短语,且确有读者会回看的价值——术语卡(业界叫法 + 通过条件,就近放置)或尾置补充(正文与围栏注释之外确有独立价值的一点,如风险、版本差异、可选替代)。节末旁注按尾置补充处理,位置与改写要求见 `steps/03-blocks.md`「节末旁注与 Callout 收尾」。
106
107
  - **不升级**:一句同位短语能说清的(如「CTest(CMake 自带的测试运行工具)」)一律留行内括号;升级后卡片里没有正文未写的新信息时,删卡片。
107
108
 
108
109
  Callout 个数见 `steps/02-structure.md`,本段不写数字;`warning` 只进「常见错误」节里对应的环境差异条目。必经步骤与成功判据永不进 Callout。位置与类型选择的细则见 `steps/03-blocks.md`「元素边界」。
@@ -128,4 +129,4 @@ Callout 个数见 `steps/02-structure.md`,本段不写数字;`warning` 只
128
129
 
129
130
  交叉引用使用真实标题生成的锚点,引用前确认目标页面存在。本项目不手写英文 `{#id}`,标题锚点由中文标题派生;跨章节引用用章节标题链接,不用「第 N 章」。
130
131
 
131
- 需要渲染与链接依据时,读 `quarto/render.md` 与 `quarto/mermaid.md`。
132
+ 需要渲染与链接依据时,读 `quarto/render.md` 与 `quarto/mermaid.md`;图注、原生 SVG 与图前句的分工读 `quarto/figures.md`。
@@ -36,9 +36,9 @@ format: html
36
36
  ## 常用命令
37
37
 
38
38
  ```bash
39
- quarto render 文档.qmd # 渲染单个文档
40
- quarto preview 文档.qmd # 本地预览(实时刷新)
41
- quarto render # 渲染当前项目全部内容
39
+ quarto render 文档.qmd # 渲染单个文档
40
+ quarto preview 文档.qmd # 本地预览(实时刷新)
41
+ quarto render # 渲染当前项目全部内容
42
42
  ```
43
43
 
44
44
  `quarto render` 必须在仓库根目录执行,在其他目录执行会静默不渲染。书里若有 `{python}` 可执行单元,先按项目约定设置 `QUARTO_PYTHON`,再运行 `quarto render`;不要依赖系统 PATH 里的 `python3`(Windows 上可能是 Store 占位符)。
@@ -49,17 +49,17 @@ quarto render # 渲染当前项目全部内容
49
49
 
50
50
  ```text
51
51
  book/
52
- ├── _quarto.yml # 书配置:chapters、appendices 与输出
53
- ├── index.qmd # 根首页:只导航到各 part 落地页
54
- ├── content #/ 正文与附录
55
- │ ├── <part> #/ 一个部分
56
- │ │ ├── index.qmd # part 落地页:本部分章节卡片
57
- │ │ ├── 01-intro.qmd
58
- │ │ └── 02-topic.qmd
59
- │ └── appendix #/ 附录
60
- │ └── references.qmd
61
- ├── theme #/ 书内主题副本
62
- └── filters #/ 书内过滤器
52
+ ├── _quarto.yml # 章序与输出配置
53
+ ├── index.qmd # 首页:导航到各 part
54
+ ├── content/ #/ 正文与附录
55
+ │ ├── <part>/ #/ 一个部分
56
+ │ │ ├── index.qmd # part 落地页:本部分章节卡片
57
+ │ │ ├── 01-intro.qmd # 第一章的正文
58
+ │ │ └── 02-topic.qmd # 第二章的正文
59
+ │ └── appendix/ #/ 附录
60
+ │ └── references.qmd # 参考文献与术语出处
61
+ ├── theme/ #/ 书内主题副本
62
+ └── filters/ #/ 书内过滤器
63
63
  ```
64
64
 
65
65
  Quarto Book 的核心配置在书根 `_quarto.yml`:
@@ -78,7 +78,7 @@ book:
78
78
  chapters:
79
79
  - content/<part>/01-intro.qmd
80
80
  - content/<part>/02-topic.qmd
81
- - content/<part>/99-bibliography.qmd # 书目页:#refs 留在 chapters
81
+ - content/<part>/99-bibliography.qmd # 书目页:#refs 留在 chapters
82
82
  appendices:
83
83
  - content/appendix/references.qmd
84
84
  ```
@@ -109,7 +109,7 @@ title: "章节标题"
109
109
 
110
110
  开篇说明写正文普通段落(1-3 句);章节与 part 落地页不写 `description`,定位信息只保留在开篇说明。
111
111
 
112
- ## 第一个小节
112
+ # # 第一个小节
113
113
  正文。
114
114
  ```
115
115
 
@@ -126,5 +126,6 @@ title: "章节标题"
126
126
  ## 延伸
127
127
 
128
128
  - 正文结构、代码块/终端约定与文档元素:`quarto/authoring.md`
129
+ - 图、图注与图前句的分工:`quarto/figures.md`
129
130
  - 外观配置与渲染排错:`quarto/render.md`
130
131
  - 页面宽度与视觉依据:`steps/02-structure.md`
@@ -12,12 +12,13 @@
12
12
 
13
13
  - 页面 frontmatter 开 `engine: jupyter`,只给含图页面开启,避免全书每次渲染都起 kernel。
14
14
  - 坐标图单元用 ```` ```{python} ```` 围栏,块首写 `#| echo: false`、`#| warning: false`,再写 `#| fig-cap: "图 N:…"` 与 `#| fig-align: center`;图注文字概括图意,不与图后正文逐句重复,也不用 `>` 引用块另起一套图注。
15
+ - 没有执行环境或页面保持静态时,允许直接写原生 HTML/SVG;图必须用 `<figure class="book-figure">` 包住,`<figcaption>` 在图后。三类图的统一图注约定见 `quarto/figures.md`,不要把图注留成图前的独立段落。
15
16
  - 单元内从当前目录向上定位含 `_quarto.yml` 的书根,按路径加载本技能 `scripts/diagrams/rendering-coords.py`,绑定名仍是 `coords_grid`,用 `IPython.display.HTML` 输出保持 SVG 内联。不要换成 `IPython.display.SVG`:image/svg+xml 输出会被 Quarto 外置成 `<img>` 文件引用,无法继承站点 `data-bs-theme` 明暗与文档字体(书内字体退回系统字体)。
16
17
  - 图注讲不完的读图要点写在图后正文(一两句),不复述图注已写明的信息。
17
18
 
18
19
  ## 上游代码与图的桥接
19
20
 
20
- 坐标图不孤立出现:引出图的代码块与图之间必须有桥接句,说明图核对代码的哪部分契约(如「下面的图标出每个数据项的 (row, col),与 `IsValid` 接受的范围一致」);图后先给从图带走的结论,再进入下一块;小节以图收尾时,从图带走的结论可并入桥接段(如 示例章先给 `row` 为 `3` 或 `col` 为 `-1` 这类反例再上图),不在图后孤悬一句。代码块与图之间只有空行视为堆叠,固定序列见 `writing-quarto` 技能 `quarto/authoring.md`「代码与图的桥接」与 `steps/03-blocks.md`。
21
+ 坐标图不孤立出现:引出图的代码块与图之间必须有桥接句,说明图核对代码的哪部分契约(如「图核对 `IsValid` 接受的范围」);图后先给从图带走的结论,再进入下一块;小节以图收尾时,从图带走的结论可并入桥接段(如 示例章先给 `row` 为 `3` 或 `col` 为 `-1` 这类反例再上图),不在图后孤悬一句。代码块与图之间只有空行视为堆叠,固定序列见 `writing-quarto` 技能 `quarto/authoring.md`「代码与图的桥接」与 `steps/03-blocks.md`;图注与图前句的分工见 `quarto/figures.md`。
21
22
 
22
23
  坐标图单元计入盒子密度(口径见 `steps/03-blocks.md`):同一 `###` 内坐标图与代码块相邻出现时,桥接句就是盒子之间的承接正文;`verifying-content.py` 对两围栏之间无正文给出 WARN 提示,人审与对照案例仍是主检查。
23
24