explico 0.7.0 → 0.11.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.
Files changed (236) hide show
  1. package/CHANGELOG.md +1409 -1
  2. package/README.md +60 -15
  3. package/dist/compiler/astro.d.ts +6 -1
  4. package/dist/compiler/cli.d.ts +54 -3
  5. package/dist/compiler/cli.js +80 -6
  6. package/dist/compiler/collections.d.ts +35 -2
  7. package/dist/compiler/collections.js +84 -8
  8. package/dist/compiler/course-config.d.ts +3 -194
  9. package/dist/compiler/course-config.js +78 -5
  10. package/dist/compiler/exports/standalone-html.d.ts +6 -1
  11. package/dist/compiler/knowledge-version.js +1 -1
  12. package/dist/compiler/lesson-derivation.d.ts +27 -10
  13. package/dist/compiler/lesson-derivation.js +23 -8
  14. package/dist/compiler/logging.d.ts +14 -1
  15. package/dist/compiler/logging.js +6 -2
  16. package/dist/compiler/manifest.d.ts +204 -31
  17. package/dist/compiler/manifest.js +270 -55
  18. package/dist/compiler/resolution-report.d.ts +6 -1
  19. package/dist/course-config-Dsnri9Tk.d.ts +464 -0
  20. package/dist/reference/base-accents.d.mts +26 -0
  21. package/dist/reference/base-accents.mjs +31 -0
  22. package/dist/reference/base-roman-labels.d.mts +2 -2
  23. package/dist/reference/base-roman-labels.mjs +2 -2
  24. package/dist/reference/consistency.d.ts +14 -2
  25. package/dist/reference/consistency.js +108 -4
  26. package/dist/reference/delimiter-size.d.ts +21 -11
  27. package/dist/reference/delimiter-size.js +15 -39
  28. package/dist/reference/diagrams-validate.d.ts +7 -2
  29. package/dist/reference/diagrams-validate.js +9 -1
  30. package/dist/reference/equations-validate.d.ts +8 -5
  31. package/dist/reference/equations-validate.js +1 -0
  32. package/dist/reference/equations.d.mts +69 -34
  33. package/dist/reference/equations.mjs +90 -44
  34. package/dist/reference/examples-validate.d.ts +31 -0
  35. package/dist/reference/examples-validate.js +48 -0
  36. package/dist/reference/figures-validate.d.ts +1 -1
  37. package/dist/reference/figures-validate.js +4 -1
  38. package/dist/reference/folds-validate.d.ts +33 -0
  39. package/dist/reference/folds-validate.js +32 -0
  40. package/dist/reference/gate-abbreviations.d.ts +3 -1
  41. package/dist/reference/gate-abbreviations.js +3 -2
  42. package/dist/reference/gate-course-rules.d.ts +32 -0
  43. package/dist/reference/gate-course-rules.js +85 -0
  44. package/dist/reference/gate-heading-length.d.ts +5 -2
  45. package/dist/reference/gate-heading-length.js +47 -23
  46. package/dist/reference/gate-heading-structure.d.ts +41 -0
  47. package/dist/reference/gate-heading-structure.js +113 -0
  48. package/dist/reference/gate-math-punctuation.d.ts +71 -0
  49. package/dist/reference/gate-math-punctuation.js +251 -0
  50. package/dist/reference/gate-math.d.ts +44 -6
  51. package/dist/reference/gate-math.js +63 -10
  52. package/dist/reference/gate-part-pages.d.ts +50 -0
  53. package/dist/reference/gate-part-pages.js +118 -0
  54. package/dist/reference/gate-prose-notation.d.ts +32 -11
  55. package/dist/reference/gate-prose-notation.js +30 -8
  56. package/dist/reference/katex-delimiters.d.mts +15 -0
  57. package/dist/reference/katex-delimiters.mjs +138 -0
  58. package/dist/reference/katex-options.mjs +4 -0
  59. package/dist/reference/keyed-float-validate.d.ts +27 -10
  60. package/dist/reference/keyed-float-validate.js +15 -1
  61. package/dist/reference/keyed-jsx-floats.d.mts +11 -5
  62. package/dist/reference/keyed-jsx-floats.mjs +73 -20
  63. package/dist/reference/knowledge-checks-validate.d.ts +1 -1
  64. package/dist/reference/knowledge-checks.d.mts +3 -1
  65. package/dist/reference/knowledge-checks.mjs +9 -4
  66. package/dist/reference/lab-math-scope.d.ts +1 -0
  67. package/dist/reference/lab-math-slot.d.ts +34 -0
  68. package/dist/reference/lab-math-slot.js +6 -0
  69. package/dist/reference/math-glyphs.d.mts +80 -4
  70. package/dist/reference/math-glyphs.mjs +588 -39
  71. package/dist/reference/page-notation.d.ts +6 -1
  72. package/dist/reference/raw-floats-validate.js +1 -1
  73. package/dist/reference/raw-floats.mjs +1 -1
  74. package/dist/reference/rehype-heading-anchors.d.mts +14 -0
  75. package/dist/reference/rehype-heading-anchors.mjs +116 -17
  76. package/dist/reference/rehype-table-header-labels.d.mts +6 -1
  77. package/dist/reference/rehype-table-header-labels.mjs +21 -3
  78. package/dist/reference/remark-asides.d.mts +3 -0
  79. package/dist/reference/remark-asides.mjs +180 -0
  80. package/dist/reference/remark-memo.d.mts +3 -1
  81. package/dist/reference/remark-memo.mjs +42 -11
  82. package/dist/reference/remark-notation.d.mts +60 -10
  83. package/dist/reference/remark-notation.mjs +387 -36
  84. package/dist/reference/render-lab-math.d.ts +4 -26
  85. package/dist/reference/render-lab-math.js +5 -5
  86. package/dist/reference/sections-validate.d.ts +27 -0
  87. package/dist/reference/sections-validate.js +41 -0
  88. package/dist/reference/sections.d.mts +97 -0
  89. package/dist/reference/sections.mjs +306 -0
  90. package/dist/reference/tables-validate.d.ts +1 -1
  91. package/dist/reference/types.d.ts +24 -1
  92. package/dist/reference/unit-words.d.ts +37 -0
  93. package/dist/reference/unit-words.js +170 -0
  94. package/dist/rules/catalog.d.ts +176 -0
  95. package/dist/rules/catalog.js +983 -0
  96. package/dist/rules/course-rules.d.ts +115 -0
  97. package/dist/rules/course-rules.js +292 -0
  98. package/dist/rules/course-rules.schema.json +138 -0
  99. package/dist/rules/in-effect.d.ts +53 -0
  100. package/dist/rules/in-effect.js +142 -0
  101. package/dist/rules/rules.schema.json +1072 -0
  102. package/dist/rules/settings.d.ts +3 -0
  103. package/dist/rules/settings.js +290 -0
  104. package/dist/rules/waivers.d.ts +53 -0
  105. package/dist/rules/waivers.js +132 -0
  106. package/dist/session/PreferenceStore.d.ts +5 -0
  107. package/dist/session/PreferenceStore.js +9 -0
  108. package/docs/VERSIONING.md +2 -2
  109. package/docs/authoring/README.md +232 -40
  110. package/docs/authoring/agents.md +55 -9
  111. package/docs/authoring/components.md +178 -22
  112. package/docs/authoring/migrating.md +1324 -2
  113. package/docs/authoring/notation.md +616 -344
  114. package/docs/authoring/prose.md +551 -0
  115. package/docs/authoring/retired.md +35 -0
  116. package/docs/authoring/structure-rules.md +422 -40
  117. package/docs/authoring/structure.md +445 -62
  118. package/docs/authoring/versioning.md +20 -28
  119. package/docs/authoring/visualizations.md +142 -20
  120. package/docs/katex-bracket-scaling.md +191 -0
  121. package/package.json +3 -2
  122. package/src/compiler/cli.ts +139 -6
  123. package/src/compiler/collections.ts +144 -10
  124. package/src/compiler/course-config.ts +273 -34
  125. package/src/compiler/knowledge-version.ts +1 -1
  126. package/src/compiler/lesson-derivation.ts +54 -15
  127. package/src/compiler/logging.ts +21 -4
  128. package/src/compiler/manifest.ts +573 -104
  129. package/src/components/BlockHeading.astro +113 -0
  130. package/src/components/CourseContents.astro +221 -0
  131. package/src/components/CurriculumMap.astro +5 -6
  132. package/src/components/FloatIndex.astro +200 -0
  133. package/src/components/HoverPanel.astro +431 -79
  134. package/src/components/PartBridge.astro +27 -0
  135. package/src/components/PartOverview.astro +401 -0
  136. package/src/components/PartScope.astro +27 -0
  137. package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
  138. package/src/components/assessments/AssessmentRunner.tsx +8 -5
  139. package/src/components/assessments/AssessmentSet.astro +23 -26
  140. package/src/components/block-heading.ts +146 -0
  141. package/src/components/card-arrow.ts +243 -0
  142. package/src/components/card-drag.ts +631 -0
  143. package/src/components/card-jump.ts +45 -0
  144. package/src/components/card-manager.ts +1589 -0
  145. package/src/components/card-memory.ts +177 -0
  146. package/src/components/card-placement.ts +504 -0
  147. package/src/components/card-side.ts +336 -0
  148. package/src/components/card-travel.ts +197 -0
  149. package/src/components/citation/CitationLayer.astro +77 -76
  150. package/src/components/diagram/Diagram.astro +161 -316
  151. package/src/components/disclosure/course-disclosure.ts +24 -0
  152. package/src/components/disclosure/hover-preview.ts +122 -42
  153. package/src/components/disclosure/persisted-disclosure.ts +3 -3
  154. package/src/components/disclosure/print-as-shown.ts +252 -0
  155. package/src/components/examples/CompactExample.astro +3 -1
  156. package/src/components/examples/CompactExamples.astro +101 -94
  157. package/src/components/figure/Figure.astro +174 -274
  158. package/src/components/figure/FloatCaption.astro +87 -0
  159. package/src/components/figure/figure-state.ts +58 -0
  160. package/src/components/figure/figure-tools.ts +87 -0
  161. package/src/components/float-name.ts +101 -0
  162. package/src/components/fold/Fold.astro +125 -0
  163. package/src/components/fold/fold-id.ts +40 -0
  164. package/src/components/memo/Memo.astro +162 -231
  165. package/src/components/memo/ShortNote.astro +6 -6
  166. package/src/components/notation/NotationFormula.astro +5 -1
  167. package/src/components/notation/NotationGlossary.astro +24 -14
  168. package/src/components/notation/NotationLayer.astro +169 -162
  169. package/src/components/notation/NotationMath.astro +10 -1
  170. package/src/components/notation/render-notation-math.ts +111 -10
  171. package/src/components/reference/FloatRefLayer.astro +485 -423
  172. package/src/components/starlight/AboutPanel.astro +22 -6
  173. package/src/components/starlight/CollapsibleSections.astro +207 -93
  174. package/src/components/starlight/DiagramEnhancer.astro +15 -138
  175. package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
  176. package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
  177. package/src/components/starlight/LayoutHeader.astro +298 -19
  178. package/src/components/starlight/LayoutPageSidebar.astro +99 -12
  179. package/src/components/starlight/LayoutPageTitle.astro +120 -0
  180. package/src/components/starlight/LayoutSidebar.astro +229 -11
  181. package/src/components/starlight/LessonExportActions.astro +53 -72
  182. package/src/components/starlight/LessonFooter.astro +26 -4
  183. package/src/components/starlight/section-parts.ts +15 -2
  184. package/src/components/starlight/section-summary.ts +181 -0
  185. package/src/components/table/Table.astro +95 -76
  186. package/src/components/table/TableOverflowExpander.astro +65 -28
  187. package/src/content-config.ts +40 -4
  188. package/src/reference/base-accents.mjs +31 -0
  189. package/src/reference/base-roman-labels.mjs +2 -2
  190. package/src/reference/consistency.ts +176 -5
  191. package/src/reference/delimiter-size.ts +40 -61
  192. package/src/reference/diagrams-validate.ts +17 -1
  193. package/src/reference/equations-validate.ts +10 -5
  194. package/src/reference/equations.mjs +90 -44
  195. package/src/reference/examples-validate.ts +97 -0
  196. package/src/reference/figures-validate.ts +6 -1
  197. package/src/reference/folds-validate.ts +65 -0
  198. package/src/reference/gate-abbreviations.ts +3 -2
  199. package/src/reference/gate-course-rules.ts +134 -0
  200. package/src/reference/gate-heading-length.ts +68 -37
  201. package/src/reference/gate-heading-structure.ts +193 -0
  202. package/src/reference/gate-math-punctuation.ts +381 -0
  203. package/src/reference/gate-math.ts +119 -12
  204. package/src/reference/gate-part-pages.ts +217 -0
  205. package/src/reference/gate-prose-notation.ts +74 -13
  206. package/src/reference/katex-delimiters.mjs +138 -0
  207. package/src/reference/katex-options.mjs +4 -0
  208. package/src/reference/keyed-float-validate.ts +45 -11
  209. package/src/reference/keyed-jsx-floats.mjs +73 -20
  210. package/src/reference/knowledge-checks-validate.ts +1 -1
  211. package/src/reference/knowledge-checks.mjs +9 -4
  212. package/src/reference/lab-math-slot.ts +36 -0
  213. package/src/reference/math-glyphs.mjs +588 -39
  214. package/src/reference/raw-floats-validate.ts +1 -1
  215. package/src/reference/raw-floats.mjs +1 -1
  216. package/src/reference/rehype-heading-anchors.mjs +116 -17
  217. package/src/reference/rehype-table-header-labels.mjs +21 -3
  218. package/src/reference/remark-asides.mjs +180 -0
  219. package/src/reference/remark-memo.mjs +42 -11
  220. package/src/reference/remark-notation.mjs +387 -36
  221. package/src/reference/render-lab-math.ts +17 -32
  222. package/src/reference/sections-validate.ts +81 -0
  223. package/src/reference/sections.mjs +306 -0
  224. package/src/reference/tables-validate.ts +2 -1
  225. package/src/reference/types.ts +24 -0
  226. package/src/reference/unit-words.ts +233 -0
  227. package/src/rules/catalog.ts +1298 -0
  228. package/src/rules/course-rules.ts +456 -0
  229. package/src/rules/in-effect.ts +235 -0
  230. package/src/rules/settings.ts +448 -0
  231. package/src/rules/waivers.ts +249 -0
  232. package/src/session/PreferenceStore.ts +15 -0
  233. package/src/styles/global.css +1471 -163
  234. package/src/styles/surfaces.css +182 -0
  235. package/src/components/hover-panel.ts +0 -339
  236. package/src/components/popup-stack.ts +0 -345
@@ -39,8 +39,59 @@ whether it complies.
39
39
  3. Author discretion, recorded in the diff.
40
40
 
41
41
  A course may add its own rules on top — a house style for prose, a domain glyph
42
- table. It must not silently relax one of these; loosening a rule is a change to
43
- this folder, reviewed like any other change.
42
+ table — in `courseRules` in its `course.settings.json` (see
43
+ [Rules of your own](#rules-of-your-own)). It must not silently relax one of
44
+ these; loosening a rule is a change to this folder, reviewed like any other
45
+ change. Turning a rule off, or tuning it, for one course is a different thing
46
+ and a visible one: `rules.<ID>` in the course settings
47
+ (`course.settings.json`), which every `validate:content` reflects in
48
+ `build/rules-in-effect.md` — the page an agent reads instead of this folder.
49
+
50
+ ## Accessibility
51
+
52
+ Accessibility is the engine's job, not the author's: no rule here asks a course
53
+ author to write an accessible name, a description, `alt` text or a text
54
+ alternative. Every float renders a real `<figure>` and `<figcaption>`, so its
55
+ caption is its accessible name; a table header that is all math gets a hidden
56
+ text label from its notation entries; and every button and link the engine
57
+ draws names itself. The engine's own markup is held to that by axe in explico's
58
+ end-to-end tests. What an author writes anyway — a lab's `ariaLabel`, an
59
+ `aria-label` on an inline `<svg role="img">` — is welcome and never required.
60
+
61
+ ## Waiving a rule at one place
62
+
63
+ A rule is a strong recommendation, and the diff must state the reason when
64
+ one is broken. The in-source form of that reason is a waiver — an MDX comment
65
+ on its own line, above the block it is for:
66
+
67
+ ```mdx
68
+ {/* explico-allow N9: the unit is being defined here, not used */}
69
+ The unit of acceleration is written m/s² …
70
+ ```
71
+
72
+ It covers the next block — from the first non-blank line after the comment to
73
+ the line before the next blank line — and may name several rules
74
+ (`explico-allow N9, N14: reason`). The reason is mandatory. What a waiver does
75
+ depends on the rule:
76
+
77
+ - **A rule with a check that reports a line** (`waivable` on the rules-in-effect
78
+ page: N9, N14, S1, N17) — the build drops the findings the waiver covers. A
79
+ waiver that silences nothing is a `waiver-unused` warning, so a stale one
80
+ gets removed.
81
+ - **A guide rule** (every `P` and `V` rule, N2, N11, …) — nothing to silence;
82
+ the waiver is the reason on record, next to the sentence it is about, and a
83
+ reviewer reads it as such.
84
+ - **One of the course's own rules** (`X1`, …) — always waivable inline: an
85
+ enforced one reports a line, and a guide one has nothing to silence.
86
+ - **A rule whose check has no line** — a notation entry (N8, N10, N19–N21), a
87
+ per-lesson count (S3, S4, S5), the changelog (C3) — cannot be waived inline.
88
+ Silence one finding per key in `lint-ignore.yml`
89
+ (`- <code>:<key> — <reason>`), or turn the rule off in `rules.<ID>`.
90
+ - **A locked rule** (N4, N13, N15) and an invariant cannot be waived: the
91
+ page would not render. Naming one is a `waiver-invalid` error.
92
+
93
+ A waiver comment is not prose: the `N9` / `N14` scans blank it, so the reason
94
+ may quote the very text it excuses.
44
95
 
45
96
  ## How a rule is written
46
97
 
@@ -49,21 +100,39 @@ speak in: what a part, a chapter, a section, and a subsection are, and which of
49
100
  them an equation number counts.
50
101
 
51
102
  Each rule has a stable ID — a letter for its area (`N` notation,
52
- `V` visualizations, `C` versioning, `S` structure, `P` prose) plus a number — and this shape:
103
+ `V` visualizations, `C` versioning, `S` structure, `P` prose) plus a number — and this shape.
104
+ `X` is not one of them: it is reserved for the rules a course writes for itself
105
+ ([Rules of your own](#rules-of-your-own)), so the engine never ships an `X`
106
+ rule and a course can number its own without waiting for a release.
53
107
 
54
108
  > **N1 — One-line imperative.**
55
109
  > _Rationale._ Why the rule exists; the failure it prevents.
56
110
  > _How to apply._ Concrete steps, with the smallest example that shows it.
57
- > _Enforcement._ Build-enforced (which gate) or Review-only.
111
+ > _Enforcement._ Build-enforced (naming the gate module) or Review-only.
58
112
  > _Exceptions._ The stateable reasons that override it, if any.
59
113
 
60
- IDs are permanent. A retired rule keeps its number, is struck through in the
61
- index below, and gains a one-line `Retired <date>: <why>` note — so a `N3` in an
62
- old commit message never goes ambiguous.
114
+ IDs are permanent, so a `N3` in an old commit message never goes ambiguous — but
115
+ a retired rule leaves this folder entirely. Its section goes, its row leaves the
116
+ index, and one line (ID, the sentence it had, the date, its successor, why) lands
117
+ in the generated table on [retired.md](retired.md). Nothing an author or an agent
118
+ reads while drafting mentions it. Two rules that turn out to be one idea are
119
+ merged that way: the text moves into the rule that keeps its ID, the other
120
+ becomes a row on that page.
121
+
122
+ The catalog entry adds what a machine needs and the page does not repeat: the
123
+ element(s) the rule is about, the diagnostic codes it owns and the module and
124
+ function that emit them, its course settings, and whether it is locked. The
125
+ page's `_Enforcement._` paragraph must name every module the entry lists;
126
+ `gen-rules.mjs --check` holds the two together.
63
127
 
64
128
  ## Amending
65
129
 
66
- 1. Open a PR that touches exactly one area file plus the index table below.
130
+ 1. Open a PR that touches exactly one area file plus its entry in
131
+ `src/rules/catalog.ts` (ID, title, elements, checks, settings). The index
132
+ table below is generated from the catalog — run
133
+ `node scripts/agent/gen-rules.mjs` after `pnpm build`; `--check` runs in
134
+ `scripts/agent/verify` and CI and fails when the table or a rule page
135
+ disagrees with the catalog.
67
136
  2. Give a new rule the next unused number in its area. Never renumber.
68
137
  3. Write the four parts above. If the rule could be build-enforced but is not
69
138
  yet, say so and link the engine issue.
@@ -73,6 +142,82 @@ old commit message never goes ambiguous.
73
142
  A new area — a new letter and file — is the same PR shape plus a row in the
74
143
  file table.
75
144
 
145
+ ## Rules of your own
146
+
147
+ A classroom has house style the engine does not ship — a phrase this cohort must
148
+ not use, a paragraph shape a department agreed on, a convention that holds for
149
+ one course and nowhere else. Those are the course's own rules, and they live in
150
+ its `course.settings.json`, next to every other course setting and editable in
151
+ the same form editor (`tools/vscode-settings-editor/`). No engine change, no
152
+ fork, no waiting for a release.
153
+
154
+ They are numbered in the **`X` area**, which is reserved for them: `X1`…`X99`.
155
+ The engine never ships an `X` rule, so a course can add one today and upgrade
156
+ `explico` tomorrow without a collision, and one ID works everywhere an engine
157
+ rule's does — a diagnostic, a waiver, a commit message, the rules-in-effect
158
+ page.
159
+
160
+ ```json
161
+ "courseRules": [
162
+ {
163
+ "id": "X1",
164
+ "title": "A paragraph never ends in an exclamation.",
165
+ "summary": "End a paragraph on the statement it argues for. Move the emphasis into the sentence, or drop it.",
166
+ "elements": ["prose"],
167
+ "severity": "error",
168
+ "forbid": [
169
+ {
170
+ "scope": "paragraph",
171
+ "pattern": "Ni!$",
172
+ "message": "a paragraph ends in “Ni!” — end it on the statement instead."
173
+ }
174
+ ]
175
+ },
176
+ {
177
+ "id": "X2",
178
+ "title": "Every fifth paragraph is four sentences in Object–Subject–Verb order.",
179
+ "summary": "Count the paragraphs of the lesson body; the fifth, tenth, … is exactly four sentences, each in OSV order.",
180
+ "elements": ["prose"],
181
+ "enabled": false,
182
+ "unchecked": "everything — the rule is a guide, and sentence order is not something a pattern can see."
183
+ }
184
+ ]
185
+ ```
186
+
187
+ **Enforced or guide, from one field.** A rule with a `forbid` pattern is
188
+ enforced: the build matches it and reports every hit at the rule's `severity`
189
+ (`error` fails `pnpm validate:content`, `warning` does not), with the rule's ID
190
+ as the diagnostic code — `[X1] content/docs/…:42: a paragraph ends in "Ni!" …`.
191
+ A rule with no pattern is a guide: it appears on the rules-in-effect page for an
192
+ author or an agent to follow, and nothing runs. Both kinds are real rules on
193
+ that page, which is what an agent reads before drafting.
194
+
195
+ **`enabled: false` parks a rule.** It stays in the file, with its text, and moves
196
+ to the page's "Off" list — where an agent is told not to apply it *and* not to
197
+ flag it in review. That is how a rule that is drafted, or agreed for next term,
198
+ waits without affecting this term's lessons.
199
+
200
+ **What a pattern sees.** A JavaScript regular expression, matched against the
201
+ lesson's prose with code fences, inline code, `$…$` and `$$…$$`, `[[key]]`
202
+ references, `import` lines, JSX tags and MDX comments blanked out first —
203
+ exactly the surface rule N9 scans, so a pattern never trips over LaTeX or a
204
+ component prop. `scope` picks the unit: `paragraph` (the default — a block
205
+ between blank lines, right-trimmed, so `^` and `$` are its ends), `prose` (the
206
+ whole lesson, so a pattern may span paragraphs), or `heading` (the text of each
207
+ Markdown heading). `flags` takes `i`, `m`, `s`, `u`; `g` is always applied. A
208
+ pattern that does not compile, or that matches the empty string, fails the build
209
+ with the path of the setting — the rule never silently does nothing.
210
+
211
+ **Waiving one.** `{/* explico-allow X1: the sentence quotes the chant */}`
212
+ above a block, exactly like an engine rule, reason mandatory; a waiver that
213
+ silences nothing is reported so it gets removed.
214
+
215
+ **Where the text lives.** `summary` is what the rules-in-effect page prints, so
216
+ write it as an instruction, not a rationale. For a rule that needs a page of its
217
+ own, put it under the course's `content/docs/` (or anywhere), and name it in
218
+ `doc` — a path relative to `build/`, or a URL — and the page links there instead
219
+ of to the settings file.
220
+
76
221
  ## Files
77
222
 
78
223
  | File | Area | Covers |
@@ -80,41 +225,88 @@ file table.
80
225
  | [notation.md](notation.md) | `N` | how math symbols are written and bound |
81
226
  | [visualizations.md](visualizations.md) | `V` | diagrams, charts, interactive figures, randomness |
82
227
  | [versioning.md](versioning.md) | `C` | the knowledge version and `content/CHANGELOG.md` |
83
- | [structure-rules.md](structure-rules.md) | `S` | heading and figure/diagram/memo/short-note title length |
84
- | [prose.md](prose.md) | `P` | how a lesson's sentences are worded |
228
+ | [structure-rules.md](structure-rules.md) | `S` | heading and float title length; part page paragraphs; tables, diagrams and conventions declared so they can be cited |
229
+ | [prose.md](prose.md) | `P` | how a lesson's sentences, terms, and equation lead-ins are worded |
85
230
  | [components.md](components.md) | — | _catalog_: inline/block features available in a lesson body — tooltips, tables, figures, diagrams, memos, abbreviations, citations |
86
231
  | [agents.md](agents.md) | — | _guide_: working on a course repo (with an AI agent) |
87
232
  | [migrating.md](migrating.md) | — | _guide_: per-version steps when bumping the `explico` dep |
233
+ | [retired.md](retired.md) | — | _history_: one generated row per rule no longer in force, and its successor |
88
234
 
89
235
  ## Rule index
90
236
 
91
- | ID | Rule | Enforcement |
92
- | ------------------------------- | ---------------------------------------------------------------- | ------------------------------------------- |
93
- | [N1](notation.md#n1) | The normal distribution is `\mathcal{N}` | Build-enforced (completeness gate) + review |
94
- | [N2](notation.md#n2) | `\mathbb{N,Z,R,C}` are reserved for the number systems | Partial — gate forces every other `\mathbb{}` to resolve |
95
- | [N3](notation.md#n3) | Multi-letter roman labels use `\mathrm{…}` | Build-enforced (completeness gate) |
96
- | [N4](notation.md#n4) | One meaning per glyph; define once; `[[key]]` before first math | Build-enforced (completeness gate) |
97
- | [N5](notation.md#n5) | Referenced display equations carry `\label{eq:key}` | Review-only |
98
- | [N6](notation.md#n6) | Probability is `\mathbb{P}` (double-struck `P`) | Build-enforced (completeness gate) + review |
99
- | [N7](notation.md#n7) | The differential `d` is upright `\mathrm{d}` | Build-enforced (completeness gate) |
100
- | [N8](notation.md#n8) | A named operator is universal (shipped/course list) or explained | Tier-3 warning (`operator-explained`) |
101
- | [N9](notation.md#n9) | A quantity/unit/operator is typeset in `$…$`, never as plain text | Build-enforced (`gate-prose-notation.ts`) |
102
- | [N10](notation.md#n10) | A `\mathrm{…}` multi-letter label is universal, or it is explained | Tier-3 warning (`roman-label-explained`) |
103
- | [N11](notation.md#n11) | A worked example never drops a unit from a substituted value | Review-only |
104
- | [N12](notation.md#n12) | Math syntax never carries the sentence's own trailing punctuation | Review-only |
105
- | [N13](notation.md#n13) | A nonlinear function always takes a parenthesized argument | Build-enforced (`resolveMathGlyphs`) |
106
- | [N14](notation.md#n14) | An abbreviation-shaped word in prose is universal, or explained | Build-enforced (`gate-abbreviations.ts`) |
107
- | [N15](notation.md#n15) | A nested `(`, `[`, `{`, or `\|` must use scalable sizing | Build-enforced (`resolveMathGlyphs`) |
108
- | [N16](notation.md#n16) | AI-generated stylistic grouping alternates delimiter types | Review-only |
109
- | [N17](notation.md#n17) | Nested scalable delimiters grow strictly from inside to outside | Tier-3 warning (`delimiter-size-hierarchy`) |
110
- | [V1](visualizations.md#v1) | Graphs and flowcharts use the `Diagram` component | Review-only |
111
- | [V2](visualizations.md#v2) | Static charts: SVG → D3 → Observable Plot, chosen per figure | Review-only |
112
- | [V3](visualizations.md#v3) | Interactive figures: island + `renderLabMath` seam + text alt | Review-only |
113
- | [V4](visualizations.md#v4) | Randomised figures expose an adjustable, deterministic seed | Review-only |
114
- | [V5](visualizations.md#v5) | Axis ranges span the first 10 seeds at the current control values | Review-only |
115
- | [C1](versioning.md#c1) | Every content change lands with a `content/CHANGELOG.md` entry | Review-only |
116
- | [C2](versioning.md#c2) | The knowledge version is semver over learning-facing content | Review-only |
117
- | [C3](versioning.md#c3) | `about.version` equals the newest `content/CHANGELOG.md` heading | Build-enforced (Tier-3 `knowledge-version-mismatch`) |
118
- | [C4](versioning.md#c4) | On release, move `[Unreleased]` and bump `about.version` together | Review-only |
119
- | [S1](structure-rules.md#s1) | No heading, or figure/diagram/memo/short-note title, over 65 characters | Build-enforced (`gate-heading-length.ts`) for body/floats + review for `frontmatter.title` |
120
- | [P1](prose.md#p1) | "or" never introduces a second name for the same thing | Review-only |
237
+ <!-- RULES-INDEX:BEGIN (generated by scripts/agent/gen-rules.mjs from src/rules/catalog.ts — do not edit inside) -->
238
+
239
+ | ID | Element | Rule | Enforcement |
240
+ | --- | --- | --- | --- |
241
+ | [N2](notation.md#n2) | math | A reserved spelling is used only for its reserved meaning — settings: `reserved` = `["\\mathbb{N} — the natural numbers","\\mathbb{Z} — the integers","\\mathbb{R} — the reals","\\mathbb{C} — the complex numbers","\\mathbb{P} — probability","\\mathcal{N}(\\mu, \\sigma^2) — the normal distribution (mean, variance)","\\mathrm{d} — the differential"]` | Guide |
242
+ | [N4](notation.md#n4) | math, notation | One meaning per glyph; define once; `[[key]]` before first math | **Locked** — the notation layer is this rule — an unresolved glyph has no hover card and the render path needs every glyph bound. Script — `unresolved-glyph`, `glyph-unique-in-corpus` (warning), `weak-local` (info), `gloss-wants-promoting` (info), `card-wants-demoting` (info) in [`gate-math.ts`](../../src/reference/gate-math.ts) `resolveInto`; [`consistency.ts`](../../src/reference/consistency.ts) `checkGlyphUniqueness`, `checkWeakLocal`, `checkGlossWantsPromoting`, `checkCardWantsDemoting` |
243
+ | [N5](notation.md#n5) | equation | Referenced display equations carry `\label{eq:key}` | Partial — `eq-key-unused` (info) in [`equations-validate.ts`](../../src/reference/equations-validate.ts) `validateEquations`. review: that a referenced-in-prose equation actually carries a label |
244
+ | [N8](notation.md#n8) | math, notation | A named operator is universal (shipped/course list) or explained — settings: `allow` = `[]`, `deny` = `[]` | Script — `operator-explained` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkOperatorExplained` |
245
+ | [N9](notation.md#n9) | prose, assessment, table | A quantity/unit/operator is typeset in `$…$`, never as plain text — settings: `allow` = `[]` | Script — `prose-notation`, `spelled-unit` in [`gate-prose-notation.ts`](../../src/reference/gate-prose-notation.ts) `gateProseNotation`, `gateProseNotationInAssessments` |
246
+ | [N10](notation.md#n10) | math, notation | A multi-letter label is `\mathrm{…}`, and universal or explained — settings: `allow` = `[]`, `deny` = `[]` | Partial — `roman-label-explained` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkRomanLabelExplained`. review: that a bare multi-letter run was wrapped in `\mathrm{…}` at all — rule N4’s gate reports it as unresolved one-letter symbols, which is a different message for the same mistake |
247
+ | [N11](notation.md#n11) | math, prose | A worked example never drops a unit from a substituted value | Guide |
248
+ | [N12](notation.md#n12) | math, prose, assessment | Math syntax never carries the sentence's own trailing punctuation — settings: `allow` = `[]`, `deny` = `[]` | Script — `math-trailing-punctuation` in [`gate-math-punctuation.ts`](../../src/reference/gate-math-punctuation.ts) `gateMathPunctuation`, `gateMathPunctuationInAssessments` |
249
+ | [N13](notation.md#n13) | math | A nonlinear function always takes a parenthesized argument | **Locked** — the glyph resolver cannot tell where an unparenthesized argument ends, so it cannot bind the symbols inside it. Script — `unparenthesized-function` in [`math-glyphs.mjs`](../../src/reference/math-glyphs.mjs) `resolveMathGlyphs` |
250
+ | [N14](notation.md#n14) | prose | An abbreviation-shaped word in prose is universal, or explained — settings: `allow` = `[]`, `deny` = `[]` | Script — `abbreviation-unexplained` in [`gate-abbreviations.ts`](../../src/reference/gate-abbreviations.ts) `gateAbbreviations` |
251
+ | [N15](notation.md#n15) | math | A nested `(`, `[`, `{`, or `\|` must use scalable sizing | **Locked** — a bare `\|` is both an opener and a closer; nested, the resolver cannot pair it, so the rule is a parsing precondition. Script — `nested-bare-delimiter` in [`math-glyphs.mjs`](../../src/reference/math-glyphs.mjs) `resolveMathGlyphs` |
252
+ | [N17](notation.md#n17) | math | A delimiter’s shape is semantic; a nest is engine-sized, not hand-sized | Partial — `delimiter-manual-size` (warning) in [`delimiter-size.ts`](../../src/reference/delimiter-size.ts) `lintDelimiterSizes`. review: the shape half — `delimiter-manual-size` parses sizing syntax only, and never decides whether a bracket is semantic. A bare nested delimiter is rule N15’s error instead |
253
+ | [N18](notation.md#n18) | equation | A multi-step derivation is one aligned chain; reasons follow `\qquad` | Guide |
254
+ | [N19](notation.md#n19) | notation | A notation entry states `units` from the vocabulary, or `dimensionless` | Script — `notation-units` (warning), `units-vocab` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkNotationUnits` |
255
+ | [N20](notation.md#n20) | notation, source | A notation entry's source citation carries a locator | Script — `notation-source-locator` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkNotationSourceLocator` |
256
+ | [N21](notation.md#n21) | notation | A gloss is named by a short noun phrase, not a sentence | Script — `gloss-name-shape` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkGlossNameShape` |
257
+ | [N22](notation.md#n22) | math, notation | An accent or mark is an operator: universal, explained, or a whole form — settings: `allow` = `[]`, `deny` = `[]` | Script — `accent-explained` (warning), `suspended-operator-unresolved`, `operator-suspension-reason` (warning), `operator-suspended` (info), `operator-shadowed` (info) in [`consistency.ts`](../../src/reference/consistency.ts) `checkAccentExplained`, `checkOperatorSuspension`; [`gate-math.ts`](../../src/reference/gate-math.ts) `resolveInto` |
258
+ | [V1](visualizations.md#v1) | diagram | Graphs and flowcharts use the `Diagram` component | Guide |
259
+ | [V2](visualizations.md#v2) | plot | Static charts: SVG → D3 → Observable Plot, chosen per figure | Guide |
260
+ | [V3](visualizations.md#v3) | plot | Interactive figures: a hydrated island + the `renderLabMath` seam | Guide |
261
+ | [V4](visualizations.md#v4) | plot | Randomised figures expose an adjustable, deterministic seed | Guide |
262
+ | [V5](visualizations.md#v5) | plot | Axis ranges span the first 10 seeds at the current control values — settings: `seeds` = `10` | Guide |
263
+ | [V6](visualizations.md#v6) | plot | Every quantitative axis shows at least three labelled values — settings: `minLabels` = `3` | Guide |
264
+ | [C1](versioning.md#c1) | changelog | Every content change lands with a `content/CHANGELOG.md` entry | Guide |
265
+ | [C2](versioning.md#c2) | changelog | The knowledge version is semver over learning-facing content | Guide |
266
+ | [C3](versioning.md#c3) | changelog | `about.version` equals the newest changelog heading; a release moves both | Partial — `knowledge-version-mismatch` (warning), `knowledge-version-invalid` (warning), `knowledge-changelog-missing` (warning) in [`knowledge-version.ts`](../../src/compiler/knowledge-version.ts) `resolveKnowledgeVersion`. review: that the two edits landed in one commit — the build only ever sees the end state, so a release split across commits warns until the second half lands |
267
+ | [S1](structure-rules.md#s1) | heading | No heading, short caption or short-note title over 65 characters — settings: `maxLength` = `65` | Partial — `title-length` in [`gate-heading-length.ts`](../../src/reference/gate-heading-length.ts) `gateHeadingLength`. review: a lesson’s `frontmatter.title` — the gate scans the body only |
268
+ | [S2](structure-rules.md#s2) | part-page | A part page carries a `<PartBridge>` (earlier parts → this one) and a `<PartScope>` paragraph — settings: `bridgeMinWords` = `30`, `bridgeMaxWords` = `150`, `scopeMinWords` = `30`, `scopeMaxWords` = `150` | Partial — `part-page-paragraph` in [`gate-part-pages.ts`](../../src/reference/gate-part-pages.ts) `gatePartPages`. review: what each paragraph says — the gate cannot tell a bridge that links the parts from one that does not |
269
+ | [S3](structure-rules.md#s3) | table | A table is a `<Table id="…">`, never a raw Markdown/HTML table | Script — `raw-table-markdown` (warning), `raw-table-html` (warning) in [`raw-floats-validate.ts`](../../src/reference/raw-floats-validate.ts) `validateRawFloats` |
270
+ | [S4](structure-rules.md#s4) | diagram | A `<Diagram>` carries an `id` and sits where numbering can see it | Script — `diagram-unkeyed` (warning), `diagram-nested` (warning) in [`raw-floats-validate.ts`](../../src/reference/raw-floats-validate.ts) `validateRawFloats` |
271
+ | [S5](structure-rules.md#s5) | prose | A sign / perspective convention is declared once and cited elsewhere | Script — `convention-single-definition` (warning) in [`consistency.ts`](../../src/reference/consistency.ts) `checkConventionSingleDefinition` |
272
+ | [S6](structure-rules.md#s6) | heading | Headings descend one level at a time, from `##` | Script — `heading-skip` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
273
+ | [S7](structure-rules.md#s7) | heading | A heading is followed by body content, never by another heading | Script — `heading-adjacent` in [`gate-heading-structure.ts`](../../src/reference/gate-heading-structure.ts) `gateHeadingStructure` |
274
+ | [S8](structure-rules.md#s8) | prose | Prefer `<Memo>` over a Starlight aside when the content needs an anchor, a caption or collapsing | Guide |
275
+ | [S9](structure-rules.md#s9) | plot, table, diagram | A float’s short caption names it; the long caption goes on from that name | Guide |
276
+ | [P1](prose.md#p1) | prose | "or" never introduces a second name for the same thing | Guide |
277
+ | [P2](prose.md#p2) | prose | One term for each concept, repeated — no synonyms for variety | Guide |
278
+ | [P3](prose.md#p3) | prose | One meaning for each term; no technical term in its everyday sense | Guide |
279
+ | [P4](prose.md#p4) | prose | Use the established term; define it where the argument first needs it | Guide |
280
+ | [P5](prose.md#p5) | prose | Main text is literal: no idioms, metaphors, buzzwords, entertainment | Guide |
281
+ | [P6](prose.md#p6) | prose | Industry jargon only in an optional "Industry jargon" aside | Guide |
282
+ | [P7](prose.md#p7) | prose | One claim per sentence; every logical link stated | Guide |
283
+ | [P8](prose.md#p8) | prose, math | Words for a relation match the math exactly ("proportional to") | Guide |
284
+ | [P9](prose.md#p9) | prose, equation | A display equation's lead-in says what it gives and where it comes from | Guide |
285
+
286
+ _Element_ is what the rule is about; the first tag is primary. _Enforcement_ is
287
+ derived from the catalog: **Script** — every violation a check can see is
288
+ reported; **Partial** — checks exist and the rule says what they miss;
289
+ **Guide** — nothing runs, a reviewer (or the agent's own reading) is the
290
+ enforcement; **Locked** — the engine cannot run without it, so it has no
291
+ checkbox in the course settings. A `(warning)` / `(info)` code does not fail
292
+ the build. A rule with _settings_ takes them from `rules.<ID>` in the course
293
+ settings; the value shown is the default.
294
+
295
+ A rule that has been retired is not listed here at all — see
296
+ [retired.md](retired.md).
297
+
298
+ ### Always enforced (invariants)
299
+
300
+ Not rules — what the syntax means. One entry per feature, always on, documented
301
+ next to the syntax it belongs to.
302
+
303
+ | Invariant | Element | Holds that | Codes |
304
+ | --- | --- | --- | --- |
305
+ | [`notation-references`](components.md#reference-systems) | notation, math | Notation keys are well-formed, unique, defined, and acyclic. | `undefined-reference`, `invalid-key`, `duplicate-key`, `duplicate-page`, `duplicate-definition`, `conflicting-definition`, `reference-cycle`, `gloss-collides-with-card`, `unknown-explicit-key`, `malformed-explicit-binding`, `unused-definition` (info) in [`registry.ts`](../../src/reference/registry.ts) `buildNotationRegistry`; [`math-glyphs.mjs`](../../src/reference/math-glyphs.mjs) `resolveMathGlyphs`; [`gate-math.ts`](../../src/reference/gate-math.ts) `reportUnknownScopeKeys` |
306
+ | [`math-syntax`](notation.md) | math | Every math span parses and resolves unambiguously. | `invalid-latex`, `invalid-latex-input`, `invalid-notation`, `invalid-definitions`, `unsupported-katex-version`, `ambiguous-span`, `ambiguous-base-fallback`, `ambiguous-canonical-notation`, `math-compile-error` in [`math-glyphs.mjs`](../../src/reference/math-glyphs.mjs) `resolveMathGlyphs`; [`gate-math.ts`](../../src/reference/gate-math.ts) `resolveInto` |
307
+ | [`keyed-floats`](structure.md#the-invariant-and-why-it-is-build-enforced) | equation, table, plot, diagram, assessment, heading | A keyed float’s `id` is well-formed and unique, it is named by its caption pair, and every `[[…]]` to it or to a section resolves. | `eq-duplicate-key`, `eq-ref-resolves`, `tab-invalid-key`, `tab-duplicate-key`, `tab-ref-resolves`, `tab-key-unused` (info), `tab-removed-prop`, `fig-invalid-key`, `fig-duplicate-key`, `fig-ref-resolves`, `fig-key-unused` (info), `fig-removed-prop`, `dia-invalid-key`, `dia-duplicate-key`, `dia-ref-resolves`, `dia-key-unused` (info), `dia-removed-prop`, `ex-invalid-key`, `ex-duplicate-key`, `ex-ref-resolves`, `ex-key-unused` (info), `ex-removed-prop`, `check-invalid-key`, `check-duplicate-key`, `check-ref-resolves`, `ex-missing-prop`, `sec-ref-resolves`, `fold-unnamed` in [`equations-validate.ts`](../../src/reference/equations-validate.ts) `validateEquations`; [`keyed-float-validate.ts`](../../src/reference/keyed-float-validate.ts) `validateKeyedFloats`; [`knowledge-checks-validate.ts`](../../src/reference/knowledge-checks-validate.ts) `validateKnowledgeChecks`; [`examples-validate.ts`](../../src/reference/examples-validate.ts) `validateExamples`; [`sections-validate.ts`](../../src/reference/sections-validate.ts) `validateSectionRefs`; [`folds-validate.ts`](../../src/reference/folds-validate.ts) `validateFolds` |
308
+ | [`course-settings`](README.md#amending) | changelog | The course settings name real rules and current keys. | `config-deprecated-key` (warning), `config-retired-rule` (warning) in [`settings.ts`](../../src/rules/settings.ts) `resolveRules` |
309
+ | [`waivers`](README.md#waiving-a-rule-at-one-place) | prose | A per-place waiver names a waivable rule, gives a reason, and covers something. | `waiver-malformed`, `waiver-invalid`, `waiver-unused` (warning) in [`waivers.ts`](../../src/rules/waivers.ts) `collectWaivers`, `applyWaivers` |
310
+ | [`curriculum`](structure.md#the-names) | changelog | The curriculum graph is well-formed. | `invalid-id`, `duplicate-id`, `unknown-reference`, `duplicate-reference`, `self-prerequisite`, `cycle`, `lesson-order`, `track-order`, `assessment-coverage`, `invalid-assessment`, `review-state`, `teaches-support`, `alignment-unknown-competency`, `alignment-unknown-lesson`, `alignment-introduction`, `alignment-unavailable`, `alignment-review-state` in [`validation.ts`](../../src/curriculum/validation.ts) `validateCurriculum`; [`curriculum-alignment.ts`](../../src/reference/curriculum-alignment.ts) `validateNotationAlignment` |
311
+
312
+ <!-- RULES-INDEX:END -->
@@ -14,6 +14,24 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
14
14
 
15
15
  ## The loop
16
16
 
17
+ - **Read the rules in effect first.** `pnpm --filter <course> content rules`
18
+ (or `build/rules-in-effect.md` after a `validate:content`) lists every rule
19
+ this course has *on*, grouped by the element it is about — prose, math,
20
+ equation, notation, heading, part-page, plot, diagram, table, assessment,
21
+ source, changelog — with its enforcement, its diagnostic codes, its
22
+ settings in effect, and a one-line summary. `--element table,prose` narrows
23
+ it to what you are editing; `--enforcement guide` shows what no script
24
+ checks and is therefore on you. Open a rule's page only when it is listed
25
+ there and you need the example. A rule that is not on the page is off for
26
+ this course (or does not exist): do not apply it, do not flag it in review.
27
+ A rule marked `waivable` there can be broken at one place with
28
+ `{/* explico-allow <ID>: <reason> */}` above the block — that is where the
29
+ reason for breaking a rule goes ([README.md § Waiving](README.md#waiving-a-rule-at-one-place)).
30
+ A line tagged `course` with an `X` ID is this course's own rule, written in
31
+ `course.settings.json` `courseRules`: it binds exactly like an engine rule,
32
+ and it is the one kind you may be asked to *add*
33
+ ([README.md § Rules of your own](README.md#rules-of-your-own)) — never by
34
+ editing this folder.
17
35
  - **Start from the CLI, not a file hunt.** `pnpm --filter <course> content
18
36
  context <lesson>` prints a lesson plus exactly its notation, references, and
19
37
  assumptions. `pnpm content status` is the draft-debt overview. `pnpm content
@@ -23,8 +41,8 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
23
41
  non-zero on an error. Read those before opening files.
24
42
  - **Validate, twice.** `pnpm --filter <course> validate:content` regenerates
25
43
  `build/resolution/*` and then fails on the same run that changed them. Run it
26
- again; the second run is clean. Commit the regenerated `build/` and
27
- `resolution/` files.
44
+ again; the second run is clean. `build/` is git-ignored — there is nothing
45
+ to commit from it.
28
46
  - **Before handing work back:** `validate:content` (×2) → `astro check` →
29
47
  `astro build`. `astro build` is the only step that runs every gate the CI
30
48
  does.
@@ -45,23 +63,51 @@ and a rule disagree, the engine wins — see [`README.md`](README.md).
45
63
  - Pipe long command output to `| tail -n 40`. One-off probes and fetched pages
46
64
  go to a scratch file you then grep — not into the conversation whole.
47
65
 
66
+ ## Prose
67
+
68
+ Clarity comes before variety and entertainment, in lecture notes and
69
+ documentation alike. The `P` rules say how; which of them this course has on
70
+ is on the rules-in-effect page (`content rules --element prose`), so check a
71
+ draft against that list — not against a remembered version of
72
+ [`prose.md`](prose.md) — before handing it back. No script checks a `P` rule:
73
+ they are all `guide`, and the review is yours.
74
+
48
75
  ## Notation `formula:` fields (the sharp edges)
49
76
 
50
- - Every glyph in a `formula:` must resolve: to the entry itself, one of its
51
- `glosses:`, a card named with `\explain{key}{latex}`, or the base library.
52
- An unresolved bare letter fails the build with the entry name.
77
+ - Every glyph in a `formula:` must resolve (rule N4, locked): to the entry
78
+ itself, one of its `glosses:`, a card named with `\explain{key}{latex}`, or
79
+ the base library. An unresolved bare letter fails the build with the entry
80
+ name.
53
81
  - The notation reference graph must be **acyclic** — if `a`'s formula names `b`
54
82
  and `b`'s names `a`, the build rejects it. Link only *down* a chain
55
83
  (acceleration → velocity → position, not back up).
56
- - Use `\mathbf` for a vector, not `\vec`: `\vec` currently trips the KaTeX
57
- sanitizer in `render-notation-math`. (Engine bug, not a rule — but `\mathbf`
58
- is what builds today.)
84
+ - `\mathbf` is the house style for a vector, and a bold letter is its own
85
+ symbol (rule N4): `\mathbf{v}` needs an entry (`latex: \mathbf{v}`) or an
86
+ `\explain{key}{\mathbf{v}}` wrap even when a scalar `v` is on the page. The
87
+ same holds for `\boldsymbol`, `\bm`, `\mathcal`, `\mathbb`, `\mathscr`,
88
+ `\mathfrak`, `\mathsf` and `\mathtt`. `\vec` renders, but it is a decoration
89
+ (rule N22), not a font.
90
+
91
+ ## Decorations: `\hat`, `\bar`, `'`, `^*` (rule N22)
92
+
93
+ - A decoration is an operator. Before drafting, decide per decoration which of
94
+ the three it is on this page: a **whole form** with its own entry
95
+ (`latex: \hat{\beta}`) when the decorated symbol is a quantity in its own
96
+ right; an **operator entry** (`latex: \hat{}`, `{}'`, `{}^{*}`) when the
97
+ decoration means the same thing everywhere it appears; or on the course's
98
+ `rules.N22.allow` list. An unexplained one is an `accent-explained` warning.
99
+ - Reach for **suspension** (`notation.suspend` in the lesson frontmatter, with
100
+ a `reason`) only when the decoration is *not* its usual operator on that
101
+ page — primes that number versions (`b'`, `b''`) rather than
102
+ differentiate. Suspension makes every decorated form on the page need its
103
+ own entry, so it is stricter, not a shortcut; one whole-form entry is enough
104
+ when a single decorated symbol is special.
59
105
 
60
106
  ## Versioning
61
107
 
62
108
  Every content change adds a bullet under `## [Unreleased]` in
63
109
  `content/CHANGELOG.md`; a release moves it to a dated `## [x.y.z]` heading and
64
- sets `about.version` to match, in the same commit. Rules C1–C4,
110
+ sets `about.version` to match, in the same commit. Rules C1–C3,
65
111
  [`versioning.md`](versioning.md).
66
112
 
67
113
  ## Upgrading the engine