@chengzhao-dev/dsh-quarto-book 0.4.3 → 0.6.0
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 +3 -3
- package/README.md +3 -3
- package/lib/tools/render-check.js +5 -2
- package/package.json +5 -2
- package/skills/designing-theme/SKILL.md +3 -2
- package/skills/designing-theme/assets/theme/css/base.css +216 -55
- package/skills/designing-theme/assets/theme/css/callouts.css +32 -23
- package/skills/designing-theme/assets/theme/css/code.css +84 -2
- package/skills/designing-theme/assets/theme/css/content.css +152 -36
- package/skills/designing-theme/assets/theme/css/landing.css +175 -54
- package/skills/designing-theme/assets/theme/css/nav.css +462 -35
- package/skills/designing-theme/assets/theme/css/sidebar.css +76 -31
- package/skills/designing-theme/assets/theme/css/tokens.css +34 -16
- package/skills/designing-theme/assets/theme/includes/fonts.html +1 -1
- package/skills/designing-theme/assets/theme/includes/footer.html +16 -7
- package/skills/designing-theme/assets/theme/includes/palette.html +420 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/brand-mark.svg +3 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/favicon-dark.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/favicon-light.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/highlight-dark.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/highlight-light.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/meta.md +53 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/theme-dark.scss +43 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/theme-light.scss +40 -0
- package/skills/designing-theme/assets/theme/palettes/flexoki/tokens.css +156 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/brand-mark.svg +3 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/favicon-dark.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/favicon-light.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/meta.md +78 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/syntax.css +150 -0
- package/skills/designing-theme/assets/theme/palettes/hill-light/tokens.css +322 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/brand-mark.svg +3 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/favicon-dark.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/favicon-light.svg +5 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/highlight-dark.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/highlight-light.theme +214 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/meta.md +66 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/syntax.css +67 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/theme-dark.scss +43 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/theme-light.scss +41 -0
- package/skills/designing-theme/assets/theme/palettes/solarized/tokens.css +154 -0
- package/skills/designing-theme/knowledge/reading-surface.md +27 -15
- package/skills/designing-theme/references/theme-spine.md +2 -2
- package/skills/designing-theme/references/theme-system.md +82 -36
- package/skills/designing-theme/scripts/checking-layout.py +306 -48
- package/skills/designing-theme/scripts/measuring-pages.mjs +40 -3
- package/skills/writing-quarto/examples/01-language-scope.md +3 -3
- package/skills/writing-quarto/examples/02-structure-shape.md +5 -5
- package/skills/writing-quarto/examples/03-block-after.md +2 -2
- package/skills/writing-quarto/examples/03-excerpt-lead.md +6 -6
- package/skills/writing-quarto/examples/03-layout-list.md +15 -15
- package/skills/writing-quarto/examples/03-qmd-elements.md +4 -4
- package/skills/writing-quarto/examples/04-paragraph-cases.md +5 -5
- package/skills/writing-quarto/examples/04-paragraph-opening.md +3 -3
- package/skills/writing-quarto/examples/05-sentence-brevity.md +5 -5
- package/skills/writing-quarto/examples/05-sentence-frame.md +6 -6
- package/skills/writing-quarto/examples/05-sentence-reference.md +5 -5
- package/skills/writing-quarto/examples/06-wording-cases.md +8 -8
- package/skills/writing-quarto/examples/08-appendix.md +4 -4
- package/skills/writing-quarto/quarto/basics.md +24 -2
- package/skills/writing-quarto/quarto/comments.md +43 -0
- package/skills/writing-quarto/quarto/coords.md +4 -4
- package/skills/writing-quarto/quarto/encoding.md +35 -0
- package/skills/writing-quarto/quarto/mermaid.md +1 -1
- package/skills/writing-quarto/quarto/prose.md +86 -0
- package/skills/writing-quarto/quarto/render.md +17 -16
- package/skills/writing-quarto/quarto/terminal.md +3 -3
- package/skills/writing-quarto/scripts/checking-dom.py +20 -8
- package/skills/writing-quarto/scripts/verifying-content.py +12 -3
- package/skills/writing-quarto/steps/01-task.md +6 -6
- package/skills/writing-quarto/steps/02-structure.md +19 -19
- package/skills/writing-quarto/steps/03-blocks.md +40 -40
- package/skills/writing-quarto/steps/04-paragraphs.md +55 -55
- package/skills/writing-quarto/steps/05-sentences.md +42 -43
- package/skills/writing-quarto/steps/06-wording.md +9 -11
- package/skills/writing-quarto/steps/07-layout.md +21 -17
- package/skills/writing-quarto/steps/08-reread.md +9 -13
- package/templates/quarto-book-project/SKILL.md +1 -1
- package/skills/designing-theme/assets/theme/assets/brand-mark.svg +0 -4
- package/skills/designing-theme/assets/theme/assets/favicon.svg +0 -9
- package/skills/designing-theme/assets/theme/palettes/github/highlight-dark.theme +0 -213
- package/skills/designing-theme/assets/theme/palettes/github/highlight-light.theme +0 -213
- package/skills/designing-theme/assets/theme/palettes/github/meta.md +0 -38
- package/skills/designing-theme/assets/theme/palettes/github/theme-dark.scss +0 -35
- package/skills/designing-theme/assets/theme/palettes/github/theme-light.scss +0 -34
- package/skills/designing-theme/assets/theme/palettes/github/tokens.css +0 -160
|
@@ -63,14 +63,14 @@ def active_palette_name(quarto_text=""):
|
|
|
63
63
|
SHARED_CHECKS = [
|
|
64
64
|
("body line-height 1.75", "line-height: 1.75"),
|
|
65
65
|
("content width", "--content-width: 840px"),
|
|
66
|
-
("code font
|
|
67
|
-
("code line-height 1.
|
|
66
|
+
("code font 15px", "--code-font-size: 0.9375rem"),
|
|
67
|
+
("code line-height 1.7", "--code-line-height: 1.7"),
|
|
68
68
|
("code title padding token", "--code-title-padding"),
|
|
69
69
|
("ui font stack", '--ui-font: "LXGW Bright Medium"'),
|
|
70
70
|
("cjk font fallback", '"LXGW Bright Medium"'),
|
|
71
71
|
("code font stack", '--mono-font: "LXGW Bright Code"'),
|
|
72
72
|
("bright code family", '"LXGW Bright Code"'),
|
|
73
|
-
("code followup gap", "--code-followup-gap:
|
|
73
|
+
("code followup gap", "--code-followup-gap: 0.9rem"),
|
|
74
74
|
("list code followup spacing", "margin-top: var(--code-followup-gap)"),
|
|
75
75
|
("list code with filename selector", "li > :is(.code-copy-outer-scaffold, .code-with-filename"),
|
|
76
76
|
("readable body font", "font-size: 1rem"),
|
|
@@ -79,7 +79,8 @@ SHARED_CHECKS = [
|
|
|
79
79
|
("answer disclosure container", "details.answer-disclosure"),
|
|
80
80
|
("list marker token color", "li::marker"),
|
|
81
81
|
("answer disclosure summary", "details.answer-disclosure > summary"),
|
|
82
|
-
("feature-grid max two columns", "
|
|
82
|
+
("feature-grid max two columns", "repeat(2, minmax(0, 1fr))"),
|
|
83
|
+
("feature-grid single card full row", ":has(> .landing-card:only-child)"),
|
|
83
84
|
]
|
|
84
85
|
|
|
85
86
|
# 语义令牌名必须存在(色值由活跃 palette 提供)
|
|
@@ -93,42 +94,117 @@ TOKEN_NAME_CHECKS = [
|
|
|
93
94
|
("callout important (deep-dive semantics)", "--callout-important-border"),
|
|
94
95
|
]
|
|
95
96
|
|
|
96
|
-
# palettes/
|
|
97
|
+
# palettes/flexoki 色值期望(编译默认)。换 pack 时改对应表。
|
|
97
98
|
PALETTE_VALUE_CHECKS = {
|
|
98
|
-
"
|
|
99
|
-
("light body token #
|
|
100
|
-
("light
|
|
101
|
-
("light
|
|
102
|
-
("
|
|
103
|
-
("light
|
|
104
|
-
("dark
|
|
105
|
-
("
|
|
106
|
-
("dark
|
|
107
|
-
("dark
|
|
108
|
-
("dark
|
|
109
|
-
("
|
|
110
|
-
("
|
|
111
|
-
("
|
|
112
|
-
("
|
|
113
|
-
("
|
|
99
|
+
"flexoki": [
|
|
100
|
+
("light body token #100F0F", "--body-color: #100F0F"),
|
|
101
|
+
("light emphasis #100F0F", "--emphasis-color: #100F0F"),
|
|
102
|
+
("light line numbers #575653", "--line-num-color: #575653"),
|
|
103
|
+
("light Flexoki link ink #100F0F", "--link-color: #100F0F"),
|
|
104
|
+
("light navbar page-bg token", "--navbar-bg: #FFFCF0"),
|
|
105
|
+
("dark navbar page-bg token", "--navbar-bg: #100F0F"),
|
|
106
|
+
("light code background", "--code-bg: #F2F0E5"),
|
|
107
|
+
("dark code background", "--code-bg: #1C1B1A"),
|
|
108
|
+
("dark page #100F0F", "#100F0F"),
|
|
109
|
+
("dark body #CECDC3", "#CECDC3"),
|
|
110
|
+
("dark secondary #9F9D96", "#9F9D96"),
|
|
111
|
+
("dark link ink #CECDC3", "--link-color: #CECDC3"),
|
|
112
|
+
("callout note border light", "--callout-note-border: #6F6E69"),
|
|
113
|
+
("callout warning border light", "--callout-warning-border: #BC5215"),
|
|
114
|
+
("light highlight comment #575653", "#575653"),
|
|
115
|
+
("light highlight keyword #66800b", "#66800b"),
|
|
116
|
+
("dark highlight comment #878580", "#878580"),
|
|
117
|
+
("dark highlight keyword #879a39", "#879a39"),
|
|
114
118
|
],
|
|
115
119
|
}
|
|
116
120
|
|
|
121
|
+
# Solarized 是运行时覆盖层:令牌必须挂在 html[data-palette="solarized"] 下。
|
|
122
|
+
SOLARIZED_TOKEN_CHECKS = [
|
|
123
|
+
("solarized token selector", 'html[data-palette="solarized"] body'),
|
|
124
|
+
("solarized light page #FDF6E3", "--page-bg: #FDF6E3"),
|
|
125
|
+
("solarized light body #526C74", "--body-color: #526C74"),
|
|
126
|
+
("solarized light link rebase #586E75", "--link-color: #586E75"),
|
|
127
|
+
("solarized light code text #526C74", "--code-fg: #526C74"),
|
|
128
|
+
("solarized light code #EEE8D5", "--code-bg: #EEE8D5"),
|
|
129
|
+
("solarized dark page #002B36", "--page-bg: #002B36"),
|
|
130
|
+
("solarized dark body #839496", "--body-color: #839496"),
|
|
131
|
+
("solarized dark link rebase #93A1A1", "--link-color: #93A1A1"),
|
|
132
|
+
("solarized link hover action", "--link-hover-color: var(--action-color)"),
|
|
133
|
+
("solarized dark code #073642", "--code-bg: #073642"),
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
SOLARIZED_SYNTAX_CHECKS = [
|
|
137
|
+
("solarized syntax selector", 'html[data-palette="solarized"] code span.kw'),
|
|
138
|
+
("solarized keyword #859900", "#859900"),
|
|
139
|
+
("solarized light comment #556C73", "#556C73"),
|
|
140
|
+
("solarized light syntax blue #1B6EA3", "#1B6EA3"),
|
|
141
|
+
("solarized dark comment #93A1A1", "#93A1A1"),
|
|
142
|
+
("solarized dark syntax blue #51A0D5", "#51A0D5"),
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
# 山光是运行时覆盖层,四时辰都挂在 html[data-palette="hill-light"][data-mode] 下。
|
|
146
|
+
HILL_LIGHT_TOKEN_CHECKS = [
|
|
147
|
+
("hill-light dawn selector", 'html[data-palette="hill-light"][data-mode="dawn"] body'),
|
|
148
|
+
("hill-light noon selector", 'html[data-palette="hill-light"][data-mode="noon"] body'),
|
|
149
|
+
("hill-light dusk selector", 'html[data-palette="hill-light"][data-mode="dusk"] body'),
|
|
150
|
+
("hill-light night selector", 'html[data-palette="hill-light"][data-mode="night"] body'),
|
|
151
|
+
("hill-light night native dark controls", "color-scheme: dark;"),
|
|
152
|
+
("hill-light night print fallback", "@media print"),
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
HILL_LIGHT_SYNTAX_CHECKS = [
|
|
156
|
+
("hill-light syntax base rule", '[data-mode="dawn"] code span:not([class])'),
|
|
157
|
+
("hill-light dawn keyword", 'html[data-palette="hill-light"][data-mode="dawn"] code span.kw'),
|
|
158
|
+
("hill-light noon keyword", 'html[data-palette="hill-light"][data-mode="noon"] code span.kw'),
|
|
159
|
+
("hill-light dusk keyword", 'html[data-palette="hill-light"][data-mode="dusk"] code span.kw'),
|
|
160
|
+
("hill-light night keyword", 'html[data-palette="hill-light"][data-mode="night"] code span.kw'),
|
|
161
|
+
("hill-light night comment", 'html[data-palette="hill-light"][data-mode="night"] code span.co'),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def lu(hex_color: str) -> float:
|
|
166
|
+
"""WCAG 相对亮度。"""
|
|
167
|
+
h = hex_color.lstrip("#")
|
|
168
|
+
r, g, b = (int(h[i:i + 2], 16) / 255 for i in (0, 2, 4))
|
|
169
|
+
lin = [c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4 for c in (r, g, b)]
|
|
170
|
+
return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def check_hill_light_modes(tokens_text: str) -> int:
|
|
174
|
+
"""山光四时辰:前三个时辰浅纸、夜月暗纸;打印时夜月回落浅纸。"""
|
|
175
|
+
fails = 0
|
|
176
|
+
values = re.findall(r"--page-bg: (#[0-9A-Fa-f]{6})", tokens_text)
|
|
177
|
+
if len(values) < 5:
|
|
178
|
+
print(f" MISS hill-light page-bg count (found {len(values)}, want >= 5)")
|
|
179
|
+
return 1
|
|
180
|
+
names = ("dawn", "noon", "dusk")
|
|
181
|
+
for name, v in zip(names, values[:3]):
|
|
182
|
+
ok = lu(v) > 0.55
|
|
183
|
+
print(f" {'OK ' if ok else 'MISS'} hill-light {name} paper is light ({v} L={lu(v):.2f})")
|
|
184
|
+
fails += 0 if ok else 1
|
|
185
|
+
night_ok = lu(values[3]) < 0.15
|
|
186
|
+
print(f" {'OK ' if night_ok else 'MISS'} hill-light night is dark ({values[3]} L={lu(values[3]):.2f})")
|
|
187
|
+
fails += 0 if night_ok else 1
|
|
188
|
+
print_ok = abs(lu(values[4]) - lu(values[0])) < 0.02
|
|
189
|
+
print(f" {'OK ' if print_ok else 'MISS'} hill-light night print falls back to dawn paper ({values[4]})")
|
|
190
|
+
fails += 0 if print_ok else 1
|
|
191
|
+
return fails
|
|
192
|
+
|
|
193
|
+
|
|
117
194
|
# 兼容旧名:测试与外部引用仍可能导入 CHECKS。
|
|
118
|
-
CHECKS = SHARED_CHECKS + TOKEN_NAME_CHECKS + PALETTE_VALUE_CHECKS["
|
|
195
|
+
CHECKS = SHARED_CHECKS + TOKEN_NAME_CHECKS + PALETTE_VALUE_CHECKS["flexoki"]
|
|
119
196
|
|
|
120
197
|
CALLOUT_CHECKS = [
|
|
121
198
|
("callout body font 16px", "font-size: 1rem;"),
|
|
122
|
-
("callout title font
|
|
123
|
-
("callout padding
|
|
124
|
-
("callout header gap
|
|
199
|
+
("callout title font 13px", "font-size: 0.8125rem;"),
|
|
200
|
+
("callout padding", "padding: 0.1rem 0 0.1rem 1.1rem;"),
|
|
201
|
+
("callout header gap", "margin: 0 0 0.35rem;"),
|
|
125
202
|
]
|
|
126
203
|
|
|
127
204
|
FORBIDDEN_FONT_REFERENCES = [
|
|
128
205
|
("Fixel reference", "Fixel"),
|
|
129
206
|
("LXGW WenKai Screen reference", "LXGW WenKai Screen"),
|
|
130
207
|
("Inter reference", "Inter"),
|
|
131
|
-
("Noto Sans SC reference", "Noto Sans SC"),
|
|
132
208
|
("Noto Sans Mono CJK SC reference", "Noto Sans Mono CJK SC"),
|
|
133
209
|
("JetBrains Mono reference", "JetBrains Mono"),
|
|
134
210
|
("local font fallback", "local("),
|
|
@@ -317,6 +393,9 @@ def browser_layout(book_dir, verbose):
|
|
|
317
393
|
f" (node={node or '无'}, NODE_PATH={playwright_root or '无'})"
|
|
318
394
|
), {}
|
|
319
395
|
|
|
396
|
+
# 测量目标是渲染产物:书目录里还可能有主题模板(theme/includes/*.html),
|
|
397
|
+
# 它们没有 #quarto-document-content,混进采样会误报。优先用 _book。
|
|
398
|
+
render_dir = book_dir / "_book" if (book_dir / "_book").is_dir() else book_dir
|
|
320
399
|
shots_dir = ROOT / "temp" / "theme-regression"
|
|
321
400
|
out_file = shots_dir / "measure.json"
|
|
322
401
|
shots_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -325,7 +404,7 @@ def browser_layout(book_dir, verbose):
|
|
|
325
404
|
node,
|
|
326
405
|
str(MEASURE_SCRIPT),
|
|
327
406
|
"--book-dir",
|
|
328
|
-
str(
|
|
407
|
+
str(render_dir),
|
|
329
408
|
"--out",
|
|
330
409
|
str(out_file),
|
|
331
410
|
"--shots-dir",
|
|
@@ -361,7 +440,7 @@ def browser_layout(book_dir, verbose):
|
|
|
361
440
|
for _family, url in font_face_entries(FONTS_CSS.read_text(encoding="utf-8")):
|
|
362
441
|
target = (FONTS_CSS.parent / url).resolve()
|
|
363
442
|
font_assets_by_name[target.name] = target.stat().st_size if target.is_file() else 0
|
|
364
|
-
width_floor = {1280: 640, 768: 500, 390: 320}
|
|
443
|
+
width_floor = {1280: 640, 768: 500, 390: 320, 360: 300}
|
|
365
444
|
for width_text, pages in metrics["viewports"].items():
|
|
366
445
|
width = int(width_text)
|
|
367
446
|
for key, data in pages.items():
|
|
@@ -371,6 +450,9 @@ def browser_layout(book_dir, verbose):
|
|
|
371
450
|
problems.append(f"{label}: {data['error']}")
|
|
372
451
|
continue
|
|
373
452
|
requested_fonts = data.get("requestedFonts", [])
|
|
453
|
+
# MathJax 自带 CHTML 字体(mjx-*):数学公式必须用它才有 TeX 字形,
|
|
454
|
+
# 不算「主题外字体漂移」,也不计入本站字体预算。
|
|
455
|
+
requested_fonts = [name for name in requested_fonts if not name.startswith("mjx-")]
|
|
374
456
|
unknown_fonts = sorted(set(requested_fonts) - set(font_assets_by_name))
|
|
375
457
|
if unknown_fonts:
|
|
376
458
|
problems.append(f"{label}: unknown font requests {unknown_fonts}")
|
|
@@ -411,6 +493,33 @@ def browser_layout(book_dir, verbose):
|
|
|
411
493
|
problems.append(
|
|
412
494
|
f"{label}: 窄屏左栏浮层底色 alpha={alpha},正文会透上来,需不透明"
|
|
413
495
|
)
|
|
496
|
+
# 栏间分隔只留顶栏下那一条通栏横线:桌面左栏不得有右边框,
|
|
497
|
+
# 顶栏底边恰好 1px 且次级导航不再自己画一条,正文 h2 不带伪元素短线。
|
|
498
|
+
# 依据见 references/theme-system.md「定尺度」与「组件约定·章节线」。
|
|
499
|
+
if overlay and width > 991.98 and overlay.get("borderRightWidth", 0) != 0:
|
|
500
|
+
problems.append(
|
|
501
|
+
f"{label}: 桌面左栏 border-right="
|
|
502
|
+
f"{overlay['borderRightWidth']}px,应为 0(栏间不画竖线)"
|
|
503
|
+
)
|
|
504
|
+
header_rule = data.get("headerRule") or {}
|
|
505
|
+
if header_rule:
|
|
506
|
+
if abs(header_rule.get("borderBottomWidth", 0) - 1) > 0.01:
|
|
507
|
+
problems.append(
|
|
508
|
+
f"{label}: 顶栏底边 "
|
|
509
|
+
f"{header_rule.get('borderBottomWidth')}px,应为 1px 通栏发丝线"
|
|
510
|
+
)
|
|
511
|
+
if header_rule.get("secondaryBorderBottomWidth", 0):
|
|
512
|
+
problems.append(
|
|
513
|
+
f"{label}: 次级导航底边 "
|
|
514
|
+
f"{header_rule['secondaryBorderBottomWidth']}px,会和顶栏底边叠成双线"
|
|
515
|
+
)
|
|
516
|
+
h2_after = data.get("h2Rules") or []
|
|
517
|
+
if h2_after:
|
|
518
|
+
first = h2_after[0]
|
|
519
|
+
problems.append(
|
|
520
|
+
f"{label}: h2「{first['text']}」仍有装饰短线 "
|
|
521
|
+
f"{first['afterWidth']}×{first['afterHeight']},章节不画线"
|
|
522
|
+
)
|
|
414
523
|
# 侧栏折叠层级:桌面视口下只有当前页所在的分组默认展开。
|
|
415
524
|
# 其余分组折叠。依据见本仓 designing-theme 技能的主题说明。
|
|
416
525
|
sections = data.get("sidebarSections")
|
|
@@ -427,24 +536,25 @@ def browser_layout(book_dir, verbose):
|
|
|
427
536
|
f"{label}: 侧栏展开的不是当前页所在分组 "
|
|
428
537
|
f"(展开={expanded[0]['text']})"
|
|
429
538
|
)
|
|
430
|
-
|
|
539
|
+
# Callout 按 theme-system.md「组件约定」是薄条:2px 左色条、无填充、无圆角,
|
|
540
|
+
# 标题 13px、正文 16px,上下内边距各 0.1rem(根字号 16px 时 1.6px)。
|
|
541
|
+
# 不跟正文 17px 比——Callout 是附注层,比正文低一档是有意为之。
|
|
431
542
|
for index, callout in enumerate(data.get("callouts", []), 1):
|
|
432
|
-
if abs(callout["bodyFontSize"] -
|
|
543
|
+
if abs(callout["bodyFontSize"] - 16) > 0.01:
|
|
433
544
|
problems.append(
|
|
434
|
-
f"{label}: Callout#{index} 正文 {callout['bodyFontSize']}px "
|
|
435
|
-
f"!= 正文 {paragraph_font_size}px"
|
|
545
|
+
f"{label}: Callout#{index} 正文 {callout['bodyFontSize']}px != 16px"
|
|
436
546
|
)
|
|
437
|
-
if abs(callout["titleFontSize"] -
|
|
547
|
+
if abs(callout["titleFontSize"] - 13) > 0.01:
|
|
438
548
|
problems.append(
|
|
439
|
-
f"{label}: Callout#{index} 标题 {callout['titleFontSize']}px !=
|
|
549
|
+
f"{label}: Callout#{index} 标题 {callout['titleFontSize']}px != 13px"
|
|
440
550
|
)
|
|
441
551
|
if (
|
|
442
|
-
abs(callout["paddingTop"] -
|
|
443
|
-
or abs(callout["paddingBottom"] -
|
|
552
|
+
abs(callout["paddingTop"] - 1.6) > 0.05
|
|
553
|
+
or abs(callout["paddingBottom"] - 1.6) > 0.05
|
|
444
554
|
):
|
|
445
555
|
problems.append(
|
|
446
556
|
f"{label}: Callout#{index} 内边距 "
|
|
447
|
-
f"{callout['paddingTop']}/{callout['paddingBottom']}px !=
|
|
557
|
+
f"{callout['paddingTop']}/{callout['paddingBottom']}px != 1.6/1.6px"
|
|
448
558
|
)
|
|
449
559
|
if callout["width"] + 1 < data["paragraphWidth"]:
|
|
450
560
|
problems.append(
|
|
@@ -493,14 +603,31 @@ def browser_layout(book_dir, verbose):
|
|
|
493
603
|
return problems, None, info
|
|
494
604
|
|
|
495
605
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
)
|
|
606
|
+
# 自托管字面必须覆盖的码位:汉字与中文标点之外,还包括西文标点、数学/箭头的
|
|
607
|
+
# 「书中符号」。漏了它们不会被发现,却会静默回落到系统字体——同一个「+」会按
|
|
608
|
+
# 系统 CJK 字体的中线画在 0.5em 高度,紧挨 0.23em 的拉丁基线,肉眼就是「++ 偏上」。
|
|
609
|
+
# 表情符号(≥U+1F000 与 U+2600–U+27BF 的装饰符)按设计就该由系统 emoji 字体绘制,不在断言内。
|
|
610
|
+
GLYPH_RANGES = (
|
|
611
|
+
(0x00A0, 0x024F), # Latin-1 补充 + Latin Extended-A
|
|
612
|
+
(0x2000, 0x206F), # 通用标点(引号、破折号、省略号、间隔号)
|
|
613
|
+
(0x20A0, 0x20BF), # 货币符号
|
|
614
|
+
(0x2100, 0x214F), # 字母式符号(℃ ™ №)
|
|
615
|
+
(0x2190, 0x21FF), # 箭头
|
|
616
|
+
(0x2200, 0x22FF), # 数学运算符
|
|
617
|
+
(0x2500, 0x257F), # 制表符(终端输出里的 ├ ─ └ │)
|
|
618
|
+
(0x25A0, 0x25FF), # 几何图形(■ □ ▲)
|
|
619
|
+
(0x2E80, 0x2EFF), # CJK 部首补充
|
|
620
|
+
(0x3000, 0x303F), # CJK 标点
|
|
621
|
+
(0x4E00, 0x9FFF), # 汉字
|
|
622
|
+
(0xF900, 0xFAFF), # CJK 兼容汉字
|
|
623
|
+
(0xFE30, 0xFE4F), # CJK 兼容形式
|
|
624
|
+
(0xFF00, 0xFFEF), # 全角形式(+ - = ())
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def needs_glyph_coverage(codepoint):
|
|
629
|
+
"""Assert self-hosted coverage for CJK text, CJK punctuation and book symbols."""
|
|
630
|
+
return any(start <= codepoint <= end for start, end in GLYPH_RANGES)
|
|
504
631
|
|
|
505
632
|
|
|
506
633
|
def read_font_cmap(path):
|
|
@@ -563,13 +690,90 @@ def check_font_coverage(book_dir):
|
|
|
563
690
|
for char in chunk:
|
|
564
691
|
codepoint = ord(char)
|
|
565
692
|
if (
|
|
566
|
-
|
|
693
|
+
needs_glyph_coverage(codepoint)
|
|
567
694
|
and codepoint not in coverage[family]
|
|
568
695
|
):
|
|
569
696
|
missing[family].add(char)
|
|
570
697
|
return coverage, missing
|
|
571
698
|
|
|
572
699
|
|
|
700
|
+
def _parse_hex_color(value):
|
|
701
|
+
match = re.fullmatch(r"#([0-9a-fA-F]{6})", value.strip())
|
|
702
|
+
if not match:
|
|
703
|
+
return None
|
|
704
|
+
digits = match.group(1)
|
|
705
|
+
return tuple(int(digits[i : i + 2], 16) for i in (0, 2, 4))
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _relative_luminance(rgb):
|
|
709
|
+
channels = []
|
|
710
|
+
for value in rgb:
|
|
711
|
+
v = value / 255
|
|
712
|
+
channels.append(v / 12.92 if v <= 0.03928 else ((v + 0.055) / 1.055) ** 2.4)
|
|
713
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _contrast_ratio(fg, bg):
|
|
717
|
+
hi, lo = sorted((_relative_luminance(fg), _relative_luminance(bg)), reverse=True)
|
|
718
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def check_palette_contrast():
|
|
722
|
+
"""色板正文/次要/链接/代码前景对各自底色都需 >= 4.5:1。
|
|
723
|
+
|
|
724
|
+
只读 tokens.css 的顶层定义块,跳过 prefers-contrast 的增强段:
|
|
725
|
+
那里本来就该更高。Solarized 的语法彩色 token 由官方配色决定,
|
|
726
|
+
不在本断言范围内(见 theme-system.md 的对比度说明)。
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
palette_dir = THEME_DIR / "assets" / "theme" / "palettes"
|
|
730
|
+
pairs = (
|
|
731
|
+
("body", "--body-color", "--page-bg"),
|
|
732
|
+
("secondary", "--text-secondary", "--page-bg"),
|
|
733
|
+
("link", "--link-color", "--page-bg"),
|
|
734
|
+
("code", "--code-fg", "--code-bg"),
|
|
735
|
+
("line numbers", "--line-num-color", "--code-bg"),
|
|
736
|
+
("code title", "--code-title-fg", "--code-title-bg"),
|
|
737
|
+
)
|
|
738
|
+
failures = []
|
|
739
|
+
for palette in ("flexoki", "solarized", "hill-light"):
|
|
740
|
+
tokens_path = palette_dir / palette / "tokens.css"
|
|
741
|
+
if not tokens_path.is_file():
|
|
742
|
+
continue
|
|
743
|
+
css = tokens_path.read_text(encoding="utf-8")
|
|
744
|
+
css = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
|
|
745
|
+
for match in re.finditer(r"([^{}]+)\{([^{}]*)\}", css):
|
|
746
|
+
selector = " ".join(match.group(1).split())
|
|
747
|
+
if "@media" in selector or "prefers-contrast" in selector:
|
|
748
|
+
continue
|
|
749
|
+
body = match.group(2)
|
|
750
|
+
if "--page-bg" not in body:
|
|
751
|
+
continue
|
|
752
|
+
mode_match = re.search(r'data-mode="(\w+)"', selector)
|
|
753
|
+
if palette == "hill-light":
|
|
754
|
+
if " body" not in selector:
|
|
755
|
+
continue
|
|
756
|
+
mode = mode_match.group(1) if mode_match else "dawn"
|
|
757
|
+
else:
|
|
758
|
+
if " body" not in selector:
|
|
759
|
+
continue
|
|
760
|
+
mode = "dark" if "quarto-dark" in selector else "light"
|
|
761
|
+
values = dict(re.findall(r"(--[a-z0-9-]+)\s*:\s*([^;]+);", body))
|
|
762
|
+
for name, fg_token, bg_token in pairs:
|
|
763
|
+
fg = _parse_hex_color(values.get(fg_token, ""))
|
|
764
|
+
bg = _parse_hex_color(values.get(bg_token, ""))
|
|
765
|
+
if not fg or not bg:
|
|
766
|
+
continue
|
|
767
|
+
ratio = _contrast_ratio(fg, bg)
|
|
768
|
+
if ratio < 4.5:
|
|
769
|
+
failures.append(
|
|
770
|
+
f"{palette}/{mode}: {name} {ratio:.2f}:1 "
|
|
771
|
+
f"({values.get(fg_token).strip()} on {values.get(bg_token).strip()})"
|
|
772
|
+
)
|
|
773
|
+
return failures
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
|
|
573
777
|
def main():
|
|
574
778
|
try:
|
|
575
779
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
@@ -638,6 +842,45 @@ def main():
|
|
|
638
842
|
if not found:
|
|
639
843
|
fail += 1
|
|
640
844
|
|
|
845
|
+
# Solarized 不参与编译,但两份覆盖层必须始终可用;按文件分别核 selector 与色值。
|
|
846
|
+
solarized_tokens = PALETTE_ROOT / "solarized" / "tokens.css"
|
|
847
|
+
solarized_syntax = PALETTE_ROOT / "solarized" / "syntax.css"
|
|
848
|
+
solarized_texts = {}
|
|
849
|
+
for path in (solarized_tokens, solarized_syntax):
|
|
850
|
+
if path.is_file():
|
|
851
|
+
solarized_texts[path.name] = path.read_text(
|
|
852
|
+
encoding="utf-8", errors="ignore"
|
|
853
|
+
)
|
|
854
|
+
else:
|
|
855
|
+
print(f" MISS solarized layer file ({path.as_posix()})")
|
|
856
|
+
fail += 1
|
|
857
|
+
hill_tokens = PALETTE_ROOT / "hill-light" / "tokens.css"
|
|
858
|
+
hill_syntax = PALETTE_ROOT / "hill-light" / "syntax.css"
|
|
859
|
+
hill_texts = {}
|
|
860
|
+
for path in (hill_tokens, hill_syntax):
|
|
861
|
+
if path.is_file():
|
|
862
|
+
hill_texts[path.name] = path.read_text(encoding="utf-8", errors="ignore")
|
|
863
|
+
else:
|
|
864
|
+
print(f" MISS hill-light layer file ({path.as_posix()})")
|
|
865
|
+
fail += 1
|
|
866
|
+
for texts, path, layer_checks in (
|
|
867
|
+
(solarized_texts, solarized_tokens, SOLARIZED_TOKEN_CHECKS),
|
|
868
|
+
(solarized_texts, solarized_syntax, SOLARIZED_SYNTAX_CHECKS),
|
|
869
|
+
(hill_texts, hill_tokens, HILL_LIGHT_TOKEN_CHECKS),
|
|
870
|
+
(hill_texts, hill_syntax, HILL_LIGHT_SYNTAX_CHECKS),
|
|
871
|
+
):
|
|
872
|
+
text = texts.get(path.name)
|
|
873
|
+
if text is None:
|
|
874
|
+
continue
|
|
875
|
+
for name, pattern in layer_checks:
|
|
876
|
+
found = pattern in text
|
|
877
|
+
mark = "OK " if found else "MISS"
|
|
878
|
+
print(f" {mark} {name} ({pattern})")
|
|
879
|
+
if not found:
|
|
880
|
+
fail += 1
|
|
881
|
+
|
|
882
|
+
fail += check_hill_light_modes(hill_texts.get("tokens.css", ""))
|
|
883
|
+
|
|
641
884
|
callouts_css = (
|
|
642
885
|
THEME_DIR / "assets" / "theme" / "css" / "callouts.css"
|
|
643
886
|
).read_text(encoding="utf-8")
|
|
@@ -661,6 +904,21 @@ def main():
|
|
|
661
904
|
if found:
|
|
662
905
|
fail += 1
|
|
663
906
|
|
|
907
|
+
contrast_failures = check_palette_contrast()
|
|
908
|
+
print(f" {'MISS' if contrast_failures else 'OK '} palette text contrast >= 4.5:1")
|
|
909
|
+
if contrast_failures:
|
|
910
|
+
for line in contrast_failures:
|
|
911
|
+
print(f" {line}")
|
|
912
|
+
fail += 1
|
|
913
|
+
|
|
914
|
+
css_defined = set(re.findall(r"(--[a-z0-9-]+)\s*:", theme_text))
|
|
915
|
+
css_used = set(re.findall(r"var\(\s*(--[a-z0-9-]+)", theme_text))
|
|
916
|
+
undefined_tokens = sorted(css_used - css_defined)
|
|
917
|
+
print(f" {'MISS' if undefined_tokens else 'OK '} css tokens all defined")
|
|
918
|
+
if undefined_tokens:
|
|
919
|
+
print(" " + ", ".join(undefined_tokens))
|
|
920
|
+
fail += 1
|
|
921
|
+
|
|
664
922
|
legacy_font_files = sorted(
|
|
665
923
|
p.name
|
|
666
924
|
for pattern in ("inter-*", "noto-sans-sc-*", "jetbrains-mono-*")
|
|
@@ -715,7 +973,7 @@ def main():
|
|
|
715
973
|
else:
|
|
716
974
|
print(
|
|
717
975
|
f" {'OK ' if not browser_problems else 'MISS'} browser-layout"
|
|
718
|
-
f" (viewports=1280,768,390; schemes=light,dark; "
|
|
976
|
+
f" (viewports=1280,768,390,360; schemes=light,dark; "
|
|
719
977
|
f"mode={browser_info.get('mode', 'n/a')}; "
|
|
720
978
|
f"pages={browser_info.get('pages', 0)})"
|
|
721
979
|
)
|
|
@@ -732,7 +990,7 @@ def main():
|
|
|
732
990
|
print(
|
|
733
991
|
"PASS theme-static;browser-layout "
|
|
734
992
|
f"{browser_info.get('mode', 'full')} "
|
|
735
|
-
f"{browser_info.get('pages', 0)} pages × 1280/768/390 × light/dark"
|
|
993
|
+
f"{browser_info.get('pages', 0)} pages × 1280/768/390/360 × light/dark"
|
|
736
994
|
)
|
|
737
995
|
return 0
|
|
738
996
|
print(f"{fail} theme check(s) failed; inspect the diagnostics above.")
|
|
@@ -20,7 +20,7 @@ const bookDir = resolve(flag("--book-dir", "_book"));
|
|
|
20
20
|
const outFile = flag("--out", "");
|
|
21
21
|
const shotsDir = flag("--shots-dir", "");
|
|
22
22
|
const allPages = argv.includes("--all-pages");
|
|
23
|
-
const viewports = [1280, 768, 390];
|
|
23
|
+
const viewports = [1280, 768, 390, 360];
|
|
24
24
|
const colorSchemes = ["light", "dark"];
|
|
25
25
|
|
|
26
26
|
async function collectHtml(directory) {
|
|
@@ -152,6 +152,9 @@ async function collect(page, pageId, scheme) {
|
|
|
152
152
|
for (const element of scope.querySelectorAll("*")) {
|
|
153
153
|
if (
|
|
154
154
|
element.closest(".screen-reader-only, .sr-only, [aria-hidden='true']")
|
|
155
|
+
// 内联 SVG(棋盘快照等)没有 CSS 盒模型,<text> 的 clientWidth 恒为 0、
|
|
156
|
+
// scrollWidth 按字形量出,天然「溢出」,不能当版式问题。
|
|
157
|
+
|| element.closest("svg")
|
|
155
158
|
|| style(element).display === "none"
|
|
156
159
|
|| style(element).visibility === "hidden"
|
|
157
160
|
) {
|
|
@@ -160,13 +163,22 @@ async function collect(page, pageId, scheme) {
|
|
|
160
163
|
if (!element.clientWidth || element.scrollWidth <= element.clientWidth + 1) {
|
|
161
164
|
continue;
|
|
162
165
|
}
|
|
163
|
-
|
|
166
|
+
// 宽于容器的元素只有整页横向滚动条才算问题:代码行本身宽于 <pre> 是常态,
|
|
167
|
+
// <pre> 自己 overflow-x: auto 就不该上报。所以沿祖先链找真正的滚动容器。
|
|
168
|
+
let scrolls = false;
|
|
169
|
+
for (let node = element; node && node !== document.body; node = node.parentElement) {
|
|
170
|
+
const overflowX = style(node).overflowX;
|
|
171
|
+
if (overflowX === "auto" || overflowX === "scroll") {
|
|
172
|
+
scrolls = true;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
164
176
|
overflow.push({
|
|
165
177
|
tag: element.tagName.toLowerCase(),
|
|
166
178
|
classes: typeof element.className === "string" ? element.className.slice(0, 80) : "",
|
|
167
179
|
scrollWidth: element.scrollWidth,
|
|
168
180
|
clientWidth: element.clientWidth,
|
|
169
|
-
scrolls
|
|
181
|
+
scrolls,
|
|
170
182
|
inPre: Boolean(element.closest("pre")),
|
|
171
183
|
});
|
|
172
184
|
}
|
|
@@ -183,6 +195,29 @@ async function collect(page, pageId, scheme) {
|
|
|
183
195
|
borderRightWidth: px(style(sidebar).borderRightWidth),
|
|
184
196
|
}
|
|
185
197
|
: null;
|
|
198
|
+
// 栏间分隔只该有顶栏下那一条:读 #quarto-header 的底边,和有没有 h2 短伪元素线。
|
|
199
|
+
// 三个数据都来自计算样式,不依赖主题里写了什么,改回旧规则会直接失败。
|
|
200
|
+
const header = document.querySelector("#quarto-header");
|
|
201
|
+
const headerRule = header
|
|
202
|
+
? {
|
|
203
|
+
borderBottomWidth: px(style(header).borderBottomWidth),
|
|
204
|
+
borderBottomColor: style(header).borderBottomColor,
|
|
205
|
+
secondaryBorderBottomWidth: px(
|
|
206
|
+
style(document.querySelector("nav.quarto-secondary-nav") || header).borderBottomWidth
|
|
207
|
+
),
|
|
208
|
+
}
|
|
209
|
+
: null;
|
|
210
|
+
const h2Rules = [...document.querySelectorAll("#quarto-document-content h2")]
|
|
211
|
+
.map((heading) => {
|
|
212
|
+
const after = window.getComputedStyle(heading, "::after");
|
|
213
|
+
return {
|
|
214
|
+
text: heading.textContent.trim().slice(0, 30),
|
|
215
|
+
afterContent: after.content,
|
|
216
|
+
afterWidth: px(after.width),
|
|
217
|
+
afterHeight: px(after.height),
|
|
218
|
+
};
|
|
219
|
+
})
|
|
220
|
+
.filter((item) => item.afterContent && item.afterContent !== "none" && item.afterHeight > 0);
|
|
186
221
|
// 折叠状态来自构建期写入的 expanded:顶层 part 以 depth=1 渲染,
|
|
187
222
|
// collapse-level 决定哪一层默认展开。断言只看 show 类,不看动画中间态。
|
|
188
223
|
const sidebarSections = sidebar
|
|
@@ -224,6 +259,8 @@ async function collect(page, pageId, scheme) {
|
|
|
224
259
|
copyButtonOpacities,
|
|
225
260
|
sidebarOverlay,
|
|
226
261
|
sidebarSections,
|
|
262
|
+
headerRule,
|
|
263
|
+
h2Rules,
|
|
227
264
|
contentWidth: px(rootStyle.width),
|
|
228
265
|
color: rootStyle.color,
|
|
229
266
|
backgroundColor: style(document.body).backgroundColor,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 语言课旁白与工程旁白
|
|
6
6
|
|
|
7
|
-
`content/**` 正文禁止用旁白讲解 C++ 语言机制或风格指南条目(硬边界见 `writing-quarto` 技能 `steps/01-task.md
|
|
7
|
+
`content/**` 正文禁止用旁白讲解 C++ 语言机制或风格指南条目(硬边界见 `writing-quarto` 技能 `steps/01-task.md`「叙事范围(硬边界)」;三个工程问题与反例句式见 `steps/01-task.md`,判定口诀同在该条文)。以下对照收工程旁白的定稿形态:
|
|
8
8
|
|
|
9
9
|
对照一:常量与类型定义(示例章 state.h 节)。
|
|
10
10
|
|
|
@@ -46,7 +46,7 @@ Position 携带从 0 起算的行列坐标,越界由 IsInRange 判为非法:
|
|
|
46
46
|
正确(先动作 → 再点名成员 → 后边界):
|
|
47
47
|
终端程序把「行 列」变成一次操作动作后,只能调用 Game 的三个成员去改状态面板或查结果,
|
|
48
48
|
不能直接改私有数组 cells_。测试也调用同一组成员。
|
|
49
|
-
Game::
|
|
49
|
+
Game::ApplyMove 负责写入数据项;Game::ReadCellState 负责读某个数据项的状态;
|
|
50
50
|
Game::GetResult 负责报告流程结果。
|
|
51
51
|
```
|
|
52
52
|
|
|
@@ -67,7 +67,7 @@ Game 不调用终端程序,也不读键盘。
|
|
|
67
67
|
|
|
68
68
|
## 流程交流不绑定媒介与行为刚性表述
|
|
69
69
|
|
|
70
|
-
以
|
|
70
|
+
以 首章观察一次流程节改稿前为对照。把交流绑定到具体媒介、把行为写成刚性属性,都会在后续编号目录更换界面或引入协议时与前面冲突(禁用与替换见 `steps/06-wording.md`):
|
|
71
71
|
|
|
72
72
|
```text
|
|
73
73
|
不佳(媒介绝对化,「只发生在」把流程交流写死在终端):
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
## 定结构:标题只留对象名
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
条文见 `steps/02-structure.md`「标题字数」「形式规则」。两个旧标题都在旧字数上限内,仍是一句步骤说明:指示、过程动词和关系词把真正的对象挤到末尾;目录、侧栏和页内 TOC 需要的是对象名,不是把导语缩进标题。改写在定结构:先圈出目录里要扫到的对象,删掉指示、过程和数量,再检查同级句式是否一致。
|
|
26
26
|
|
|
27
27
|
**定结构(示例章「记下流程数据」下两个三级标题):**
|
|
28
28
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
## 小节标题抽象与首句无中心
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
小节标题用了读者没学过的术语怎么办,与一段压缩多个论断怎么改,是同一类病。示例章依赖节的两次改稿对照:第一轮标题读不出动作对象,首句堆齐名称后才在句尾给出结论;第二轮标题动作化了,但「使用方」仍是读者未建立的术语,正文一句压缩三个论断,读者无法逐条核对(规则见 `steps/03-blocks.md`「小节标题与正文对齐」):
|
|
44
44
|
|
|
45
45
|
```text
|
|
46
46
|
不佳(标题抽象 + 首句无中心):
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
|
|
84
84
|
## 树注释点名文件与问答式读法句
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
示例章改稿前的两个反例:树注释点名下层实现文件,与同棵树的职责注释不同层级;树后读法句用问答式并列把前半句再讲一遍,像写给工具的格式规格,不像给读者的解说(规则见 `steps/03-blocks.md` 与 `steps/01-task.md`):
|
|
87
87
|
|
|
88
88
|
```text
|
|
89
89
|
不佳(树注释列举实现文件名):
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
|
|
110
110
|
## 树后换对象收束句
|
|
111
111
|
|
|
112
|
-
目录树后能补一段工具配置的分工吗:不能换对象。示例章改稿对照:树后两句把主语换成 `.clang-format`、`.clang-tidy`、`.clangd` 三份工具配置,而本节标题和清单的对象是四个目录;第二句「三份配置只描述工具怎么检查代码,CMake 构建规则不依赖它们」还与仓库事实不符——`.clangd` 的 `CompileFlags.Add` 是未构建时的解析兜底,不是检查规则,CMake 会为 clangd 导出 `build/compile_commands.json
|
|
112
|
+
目录树后能补一段工具配置的分工吗:不能换对象。示例章改稿对照:树后两句把主语换成 `.clang-format`、`.clang-tidy`、`.clangd` 三份工具配置,而本节标题和清单的对象是四个目录;第二句「三份配置只描述工具怎么检查代码,CMake 构建规则不依赖它们」还与仓库事实不符——`.clangd` 的 `CompileFlags.Add` 是未构建时的解析兜底,不是检查规则,CMake 会为 clangd 导出 `build/compile_commands.json`(规则见 `steps/04-paragraphs.md`「段间:过渡句只在话题切换处」与 `steps/03-blocks.md`「小节标题与正文对齐」):
|
|
113
113
|
|
|
114
114
|
```text
|
|
115
115
|
不佳(树后收束句换成本节清单没有的对象):
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
|
|
155
155
|
## 自测问可观察结果
|
|
156
156
|
|
|
157
|
-
以 示例章自测第 1 题改稿前为对照。「为什么后手不能在开局直接落在中心」把「中心」当成读者已认识的概念,本章正文从未定义中心格;拒绝的原因也与格子无关——空状态面板上轮到甲方,O
|
|
157
|
+
以 示例章自测第 1 题改稿前为对照。「为什么后手不能在开局直接落在中心」把「中心」当成读者已认识的概念,本章正文从未定义中心格;拒绝的原因也与格子无关——空状态面板上轮到甲方,O 落在哪一格都会被拒绝。题面用本章没说全的情景提问,读者会以为中心格另有规则(步骤:定结构;规则见 `steps/02-structure.md`「六段块序列」):
|
|
158
158
|
|
|
159
159
|
```text
|
|
160
160
|
不佳(题面用了本章未定义的概念,情景没说全):
|