explico 0.11.1 → 0.13.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 (298) hide show
  1. package/CHANGELOG.md +1292 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +11 -4
  4. package/dist/compiler/astro.js +48 -15
  5. package/dist/compiler/cli.d.ts +27 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +33 -2
  8. package/dist/compiler/collections.js +91 -19
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +15 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +115 -8
  51. package/dist/compiler/manifest.js +221 -39
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +3 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +8 -2
  61. package/dist/reference/base-operators.mjs +8 -10
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figure-symbols.d.ts +32 -0
  88. package/dist/reference/figure-symbols.js +55 -0
  89. package/dist/reference/figures-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-abbreviations.js +1 -1
  92. package/dist/reference/gate-course-rules.d.ts +4 -3
  93. package/dist/reference/gate-course-rules.js +9 -4
  94. package/dist/reference/gate-heading-length.d.ts +2 -0
  95. package/dist/reference/gate-heading-length.js +7 -5
  96. package/dist/reference/gate-heading-structure.js +26 -0
  97. package/dist/reference/gate-indices.d.ts +35 -0
  98. package/dist/reference/gate-indices.js +299 -0
  99. package/dist/reference/gate-math.d.ts +32 -2
  100. package/dist/reference/gate-math.js +68 -12
  101. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  102. package/dist/reference/gate-mdx-expression.js +29 -0
  103. package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
  104. package/dist/reference/gate-non-chapter-pages.js +156 -0
  105. package/dist/reference/gate-part-pages.d.ts +4 -2
  106. package/dist/reference/gate-part-pages.js +2 -1
  107. package/dist/reference/gate-prop-text.d.ts +28 -0
  108. package/dist/reference/gate-prop-text.js +106 -0
  109. package/dist/reference/gate-prose-notation.d.ts +3 -1
  110. package/dist/reference/gate-prose-notation.js +4 -2
  111. package/dist/reference/gate-retired-keys.d.ts +35 -0
  112. package/dist/reference/gate-retired-keys.js +58 -0
  113. package/dist/reference/gloss.d.ts +2 -0
  114. package/dist/reference/katex-measure.d.ts +17 -0
  115. package/dist/reference/katex-measure.js +223 -0
  116. package/dist/reference/keyed-float-validate.js +12 -0
  117. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  118. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  119. package/dist/reference/lab-katex-validate.d.ts +43 -0
  120. package/dist/reference/lab-katex-validate.js +96 -0
  121. package/dist/reference/lab-math-scope.d.ts +1 -0
  122. package/dist/reference/lab-math-scope.js +13 -2
  123. package/dist/reference/lint-ignore.js +1 -1
  124. package/dist/reference/math-glyphs.d.mts +94 -2
  125. package/dist/reference/math-glyphs.mjs +851 -46
  126. package/dist/reference/mixed-content.d.ts +8 -3
  127. package/dist/reference/mixed-content.js +4 -2
  128. package/dist/reference/page-notation.d.ts +39 -7
  129. package/dist/reference/page-notation.js +62 -10
  130. package/dist/reference/raw-floats.mjs +37 -13
  131. package/dist/reference/references.d.ts +11 -1
  132. package/dist/reference/references.js +6 -0
  133. package/dist/reference/registry.js +115 -11
  134. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  135. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  136. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  137. package/dist/reference/remark-citation.mjs +0 -0
  138. package/dist/reference/remark-notation.mjs +275 -23
  139. package/dist/reference/render-lab-math.d.ts +13 -2
  140. package/dist/reference/render-lab-math.js +18 -6
  141. package/dist/reference/sections-validate.d.ts +28 -3
  142. package/dist/reference/sections-validate.js +31 -4
  143. package/dist/reference/sections.d.mts +24 -6
  144. package/dist/reference/sections.mjs +92 -17
  145. package/dist/reference/tables-validate.d.ts +1 -1
  146. package/dist/reference/tables-validate.js +24 -1
  147. package/dist/reference/text-measure.d.ts +10 -0
  148. package/dist/reference/text-measure.js +41 -0
  149. package/dist/reference/text-widths.d.ts +10 -0
  150. package/dist/reference/text-widths.js +1238 -0
  151. package/dist/reference/types.d.ts +64 -8
  152. package/dist/rules/catalog.js +223 -32
  153. package/dist/rules/in-effect.d.ts +1 -1
  154. package/dist/rules/rules.schema.json +100 -27
  155. package/dist/rules/settings.d.ts +1 -1
  156. package/dist/rules/settings.js +7 -0
  157. package/dist/session/PreferenceStore.d.ts +18 -1
  158. package/dist/session/PreferenceStore.js +29 -1
  159. package/docs/authoring/README.md +33 -21
  160. package/docs/authoring/agents.md +38 -7
  161. package/docs/authoring/components.md +115 -10
  162. package/docs/authoring/migrating.md +878 -0
  163. package/docs/authoring/notation.md +285 -30
  164. package/docs/authoring/retired.md +1 -0
  165. package/docs/authoring/structure-rules.md +84 -45
  166. package/docs/authoring/structure.md +67 -2
  167. package/docs/authoring/visualizations.md +167 -25
  168. package/package.json +12 -7
  169. package/src/compiler/astro.ts +72 -21
  170. package/src/compiler/cli.ts +143 -35
  171. package/src/compiler/collections.ts +158 -27
  172. package/src/compiler/content-schema.ts +108 -0
  173. package/src/compiler/content-schemas.ts +391 -0
  174. package/src/compiler/course-config.ts +86 -15
  175. package/src/compiler/exports/combined-html.ts +669 -0
  176. package/src/compiler/exports/download-index.ts +36 -0
  177. package/src/compiler/exports/export-chapters.ts +233 -0
  178. package/src/compiler/exports/export-pages.ts +44 -0
  179. package/src/compiler/exports/html-tree.ts +62 -0
  180. package/src/compiler/exports/latex/capture.ts +651 -0
  181. package/src/compiler/exports/latex/compile.ts +168 -0
  182. package/src/compiler/exports/latex/document.ts +479 -0
  183. package/src/compiler/exports/latex/explico.cls +415 -0
  184. package/src/compiler/exports/latex/numbering.ts +74 -0
  185. package/src/compiler/exports/latex/serialize.ts +787 -0
  186. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  187. package/src/compiler/exports/latex-export.ts +255 -0
  188. package/src/compiler/exports/markdown/serialize.ts +632 -0
  189. package/src/compiler/exports/markdown-export.ts +329 -0
  190. package/src/compiler/exports/standalone-html.ts +39 -70
  191. package/src/compiler/exports/static-tree.ts +944 -0
  192. package/src/compiler/exports/zip.ts +88 -0
  193. package/src/compiler/lesson-derivation.ts +13 -8
  194. package/src/compiler/manifest.ts +387 -40
  195. package/src/compiler/render-diagnostics.ts +111 -0
  196. package/src/components/CourseContents.astro +6 -0
  197. package/src/components/HoverPanel.astro +204 -6
  198. package/src/components/PartOverview.astro +18 -9
  199. package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
  200. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  201. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  202. package/src/components/assessments/AssessmentSet.astro +14 -1
  203. package/src/components/block-heading.ts +38 -0
  204. package/src/components/card-arrow.ts +18 -4
  205. package/src/components/card-carry.ts +406 -0
  206. package/src/components/card-drag.ts +1 -30
  207. package/src/components/card-fetch.ts +87 -0
  208. package/src/components/card-manager.ts +1222 -38
  209. package/src/components/card-memory.ts +5 -1
  210. package/src/components/card-placement.ts +211 -13
  211. package/src/components/card-side.ts +281 -31
  212. package/src/components/citation/CitationLayer.astro +45 -40
  213. package/src/components/diagram/Diagram.astro +123 -44
  214. package/src/components/disclosure/print-as-shown.ts +42 -6
  215. package/src/components/figure/Figure.astro +17 -1
  216. package/src/components/figure/FloatCaption.astro +4 -17
  217. package/src/components/figure/figure-state.ts +26 -17
  218. package/src/components/float-toolbar.ts +274 -0
  219. package/src/components/memo/Memo.astro +21 -10
  220. package/src/components/memo/ShortNote.astro +4 -0
  221. package/src/components/notation/NotationGlossary.astro +22 -17
  222. package/src/components/notation/NotationLayer.astro +300 -79
  223. package/src/components/notation/NotationMath.astro +8 -1
  224. package/src/components/notation/render-notation-math.ts +51 -5
  225. package/src/components/readable-text.ts +25 -0
  226. package/src/components/reference/FloatRefLayer.astro +162 -89
  227. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  228. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  229. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  230. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  231. package/src/components/starlight/LessonExportActions.astro +384 -310
  232. package/src/components/starlight/LessonFooter.astro +66 -8
  233. package/src/components/starlight/PrintController.astro +28 -0
  234. package/src/components/starlight/TextSizeControl.astro +137 -0
  235. package/src/components/starlight/print-controller.ts +380 -0
  236. package/src/components/starlight/section-summary.ts +13 -5
  237. package/src/components/table/Table.astro +14 -22
  238. package/src/components/table/table-truncation.ts +15 -0
  239. package/src/content-config.ts +20 -312
  240. package/src/curriculum/validation.ts +72 -24
  241. package/src/reference/base-abbreviations.mjs +2 -2
  242. package/src/reference/base-operators.mjs +8 -10
  243. package/src/reference/base-roman-labels.mjs +4 -3
  244. package/src/reference/citation-format.mjs +74 -0
  245. package/src/reference/consistency.ts +47 -61
  246. package/src/reference/curriculum-alignment.ts +4 -41
  247. package/src/reference/derived-entries.ts +68 -0
  248. package/src/reference/derived-notation.mjs +121 -0
  249. package/src/reference/diagram-fit.ts +104 -0
  250. package/src/reference/diagram-layout.ts +182 -51
  251. package/src/reference/diagrams-validate.ts +2 -1
  252. package/src/reference/equations-validate.ts +41 -2
  253. package/src/reference/equations.mjs +120 -22
  254. package/src/reference/examples-validate.ts +1 -0
  255. package/src/reference/figure-fit.ts +139 -0
  256. package/src/reference/figure-pictures.ts +96 -0
  257. package/src/reference/figure-symbols.ts +117 -0
  258. package/src/reference/figures-validate.ts +2 -1
  259. package/src/reference/gate-abbreviations.ts +1 -1
  260. package/src/reference/gate-course-rules.ts +22 -7
  261. package/src/reference/gate-heading-length.ts +25 -7
  262. package/src/reference/gate-heading-structure.ts +40 -1
  263. package/src/reference/gate-indices.ts +470 -0
  264. package/src/reference/gate-math.ts +102 -13
  265. package/src/reference/gate-mdx-expression.ts +45 -0
  266. package/src/reference/gate-non-chapter-pages.ts +241 -0
  267. package/src/reference/gate-part-pages.ts +1 -1
  268. package/src/reference/gate-prop-text.ts +145 -0
  269. package/src/reference/gate-prose-notation.ts +19 -5
  270. package/src/reference/gate-retired-keys.ts +101 -0
  271. package/src/reference/gloss.ts +2 -0
  272. package/src/reference/katex-measure.ts +384 -0
  273. package/src/reference/keyed-float-validate.ts +22 -0
  274. package/src/reference/keyed-jsx-floats.mjs +11 -14
  275. package/src/reference/lab-katex-validate.ts +156 -0
  276. package/src/reference/lab-math-scope.ts +15 -1
  277. package/src/reference/lint-ignore.ts +4 -1
  278. package/src/reference/math-glyphs.mjs +851 -46
  279. package/src/reference/mixed-content.ts +11 -2
  280. package/src/reference/page-notation.ts +114 -14
  281. package/src/reference/raw-floats.mjs +37 -13
  282. package/src/reference/references.ts +14 -0
  283. package/src/reference/registry.ts +170 -9
  284. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  285. package/src/reference/rehype-heading-anchors.mjs +11 -1
  286. package/src/reference/remark-citation.mjs +0 -0
  287. package/src/reference/remark-notation.mjs +275 -23
  288. package/src/reference/render-lab-math.ts +40 -5
  289. package/src/reference/sections-validate.ts +67 -3
  290. package/src/reference/sections.mjs +92 -17
  291. package/src/reference/tables-validate.ts +40 -2
  292. package/src/reference/text-measure.ts +63 -0
  293. package/src/reference/text-widths.ts +133 -0
  294. package/src/reference/types.ts +75 -8
  295. package/src/rules/catalog.ts +241 -31
  296. package/src/rules/settings.ts +10 -0
  297. package/src/session/PreferenceStore.ts +53 -0
  298. package/src/styles/global.css +322 -83
@@ -77,6 +77,8 @@ _How to apply._
77
77
  (thin space, then `\mathrm{d}x`), `\dfrac{\mathrm{d}f}{\mathrm{d}x}`,
78
78
  `\dfrac{\mathrm{d}^2 f}{\mathrm{d}x^2}`, `\mathrm{d}s`, `\mathrm{d}A`,
79
79
  `\mathrm{d}t`. An italic `d` is always a variable (a distance, a day count).
80
+ The resolver still reads a bare `d` as the differential (the base library,
81
+ [N4](#n4)), so an italic one does not fail the build: a reviewer catches it.
80
82
  This is the ISO 80000-2 convention, and it keeps the differential clear of a
81
83
  lesson that binds `d`. A partial derivative uses `\partial` bare —
82
84
  `\dfrac{\partial f}{\partial x}` — which is already distinct; do not wrap
@@ -122,8 +124,8 @@ _Rationale._ This is the core contract the completeness gate enforces
122
124
  corpus-wide; the rule here is just its author-facing statement. A reader hovers
123
125
  any symbol and gets exactly one card.
124
126
 
125
- _How to apply._ See the engine README's notation section and the `notation`
126
- collection schema. A letter that exists only so one entry's own `formula` can
127
+ _How to apply._ See the `notation` collection schema
128
+ (`src/compiler/content-schemas.ts`). A letter that exists only so one entry's own `formula` can
127
129
  be stated is a **gloss**, not a card — it creates no curriculum edge.
128
130
 
129
131
  Do not satisfy this rule by dumping every `[[key]]` a page uses into one
@@ -152,6 +154,12 @@ the body — otherwise the build fails with "Stale Explico manifest for
152
154
  […]." So every local entry still needs one `\explain{key}{…}` (or `[[key]]`)
153
155
  mention, even though the math gate alone wouldn't ask for it.
154
156
 
157
+ A local entry's key is its own: one that equals a shared entry's key wins on
158
+ its page, so `[[key]]` would mean one thing there and another in the
159
+ glossary and on every other page. `pnpm validate:content` warns
160
+ (`local-shadows-shared`); give the local entry a key of its own, or use the
161
+ shared one.
162
+
155
163
  The auto-annotator (`resolveMathGlyphs` in `math-glyphs.mjs`) re-derives every
156
164
  `\explain{…}` marker's insertion point from KaTeX's own parsed offsets, then
157
165
  re-applies those offsets to the raw source. Three shapes used to corrupt that
@@ -220,12 +228,51 @@ lesson and supplied component/formula context; a final Astro build check also
220
228
  rejects any `data-notation-key` in generated HTML that has no matching
221
229
  `data-notation-entry` source.
222
230
 
231
+ **Indexed symbols.** How the resolver binds a symbol with a subscript is part
232
+ of this rule and always on; [N23](#n23) builds on it. A whole form wins under
233
+ a power or a mark (`v_0^{2}` is `v_0` squared). An instance takes its
234
+ family's card ahead of a plain entry for its base (`x_1` opens the `x_k`
235
+ card), and only from a family whose index has the same shape. An indexed
236
+ entry lends its card to no bare symbol: a family (`\alpha_i`) explains its
237
+ own instances only, and an entry with a fixed index (`v_0`, `H_0`) only
238
+ itself. A bare `\alpha` that had a card only by borrowing one fails
239
+ `unresolved-glyph`, and the message names the indexed entry with that base:
240
+ give it its own entry, or `\explain{key}{\alpha}` where it really is the
241
+ family's shorthand. A function form still lends by its head (`V_0(1_t)`
242
+ explains `V_0(1_2)`), and an entry with only a superscript (`j^{(m)}`) still
243
+ lends to its base.
244
+
245
+ **A definition that is a convention.** Some definitions are one of several in
246
+ use: the sign on a moving-average coefficient, kurtosis with or without the
247
+ Gaussian 3 subtracted, information per observation or per sample, whom a cash
248
+ flow is positive for. The mathematics is the same and only the choice differs,
249
+ so a formula brought in from a source that chose the other way gives a wrong
250
+ answer and no error. Such an entry is defined once and cited with `[[key]]`
251
+ like any other, and it also says that it is a choice: its `meaning` names the
252
+ other choice in a clause, so the card warns where the symbol is used, and its
253
+ body says how to convert. A definition nobody writes differently (a residual
254
+ as observed minus fitted) is not a convention and needs neither. This part is
255
+ a guide; no check reads it.
256
+
257
+ ```md
258
+ ---
259
+ key: ma.coefficient
260
+ latex: \theta
261
+ meaning: Weight on the lagged innovation, added to the current one; Box–Jenkins and statsmodels subtract it instead.
262
+ ---
263
+ A formula from a source that subtracts the term holds for this course's
264
+ $\theta$ after replacing $\theta$ with $-\theta$.
265
+ ```
266
+
223
267
  _Enforcement._ Build-enforced. The completeness gate (`gate-math.ts`, `pnpm
224
268
  validate:content`; `astro build` re-runs the same resolver) fails on an
225
269
  unresolved glyph (`unresolved-glyph`) in every rendered-math context;
226
270
  `consistency.ts` adds the corpus-level lints `glyph-unique-in-corpus`
227
271
  (warning), `weak-local`, `gloss-wants-promoting`, `card-wants-demoting`
228
- (info); a final rendered-trigger/payload parity check backs it at render time.
272
+ (info); a final rendered-trigger/payload parity check backs it at render time,
273
+ and a last pass over every built page fails a symbol a figure shows that KaTeX
274
+ drew outside a notation binding (`figure-symbols.ts`: a lab that called KaTeX
275
+ itself — see [V3](visualizations.md#v3)).
229
276
  This rule is **locked** — the notation layer is this rule, so a course cannot
230
277
  turn it off.
231
278
 
@@ -260,8 +307,17 @@ stripped before KaTeX like the label, heads the `[[eq-…]]` preview card and
260
307
  names the copy action ("Copy LaTeX — Range formula"). An equation takes no
261
308
  long caption ([components.md § The caption pair](components.md#the-caption-pair)).
262
309
 
310
+ A `$$…$$` block is one numbered equation, so it takes one label: to refer to
311
+ two rows, give each its own block. LaTeX's own numbering tools have no place
312
+ in it — the block already is the display, and the engine numbers it — so
313
+ `\begin{align}`, `\begin{equation}`, `\begin{gather}` and `\tag{…}` fail
314
+ with what to write instead (the rows of an `align` go in `\begin{aligned}`),
315
+ and so do `\eqref{…}`, `\ref{…}` and a `\label` without `eq:`: a reference
316
+ is `[[eq-key]]` in the prose.
317
+
263
318
  _Enforcement._ Partial. The converse is checked: `equations-validate.ts`
264
- reports `eq-key-unused` (info) for a labelled equation nothing references.
319
+ reports `eq-key-unused` (info) for a labelled equation nothing references,
320
+ and `eq-label-repeated` (an error) for a second `\label` in one block.
265
321
  An unlabelled-but-referenced equation builds; a reviewer catches the
266
322
  hand-numbering.
267
323
 
@@ -389,14 +445,14 @@ next to it was invisible to every check until this rule.
389
445
  _How to apply._
390
446
 
391
447
  - A table header cell that names a quantity uses the same bound symbol the
392
- lesson already introduced, `\explain{key}{…}`-wrapped if the column needs a
393
- component or a different style than the card's own notation (`v_x` for
394
- `[[motion.velocity]]`'s `v(t)`, exactly as `motion.acceleration`'s own
395
- `formula:` already does for `\explain{motion.velocity}{\mathbf{v}}`), with
396
- its unit folded into the same span as a `\text{}` suffix:
448
+ lesson already introduced, with its unit folded into the same span as a
449
+ `\text{}` suffix. A component takes its subscript the way [N23](#n23)
450
+ asks: an index entry `{}_x` makes `v_x` one derived card, "Horizontal
451
+ component of velocity". An `\explain{motion.velocity}{v_x}` wrap fails
452
+ `index-swallowed`, since that card describes the vector:
397
453
 
398
454
  ```text
399
- | $t\ \text{(s)}$ | $\explain{motion.velocity}{v_x}\ \text{(m/s)}$ |
455
+ | $t\ \text{(s)}$ | $v_x\ \text{(m/s)}$ |
400
456
  ```
401
457
 
402
458
  - An absolute value or norm is `\lvert…\rvert`, never a literal `|` — a raw
@@ -474,6 +530,12 @@ as a hard error with no Tier-3 warming-up period and no `lint-ignore.yml`
474
530
  entry, because each part landed in the same change that brought the whole
475
531
  corpus to zero — see the two files for the exact patterns.
476
532
 
533
+ A currency amount is prose too, its dollar escaped: `\$5 to \$6`. remark-math
534
+ pairs any two unescaped `$` on a line, so `$5 to $6` is one math span that
535
+ shows "5 to", dollars gone; `pnpm validate:content` fails such a pair
536
+ (`currency-dollar`, invariant `math-syntax`) — one whose content starts with
537
+ a digit and ends in a space or runs into a digit.
538
+
477
539
  _Settings._ `rules.N9.allow` in the course settings lists unit words the
478
540
  course lets prose spell out (`["hour", "minute"]`), singular or plural; a
479
541
  phrase is skipped only when every unit word in it is allowed.
@@ -519,11 +581,13 @@ _How to apply._
519
581
  itself a notation key stays a key.
520
582
 
521
583
  - A generic qualifier that functions like an operator's extremum subscript —
522
- `x_{\mathrm{opt}}`, an "optimal" reading with no quantity of its own — just
523
- works. The full baseline is the greppable set in `base-roman-labels.mjs`,
524
- kept deliberately small: unlike a trig function, an upright label is almost
525
- always a named quantity specific to one course, so it defaults to needing a
526
- card rather than defaulting to universal.
584
+ `x_{\mathrm{opt}}`, an "optimal" reading with no quantity of its own —
585
+ needs no card of its own under this rule. The symbol it indexes still does
586
+ ([N23](#n23)): a whole form `x_{\mathrm{opt}}`, or an index entry
587
+ `{}_{\mathrm{opt}}`. The full baseline is the greppable set in
588
+ `base-roman-labels.mjs`, kept deliberately small: unlike a trig function,
589
+ an upright label is almost always a named quantity specific to one course,
590
+ so it defaults to needing a card rather than defaulting to universal.
527
591
 
528
592
  - A named quantity gets a notation entry, exactly like a glyph ([N4](#n4)) —
529
593
  a shared card if the course teaches it broadly, otherwise `notation.local`,
@@ -691,7 +755,7 @@ resolves, table cells and component slots included — and
691
755
  `gateMathPunctuationInAssessments` every assessment `prompt` /
692
756
  `explanation`. It flags a `.`, `,`, `;` or `:` that is the last visible
693
757
  thing in the span, or in a row before a `\\`. It looks through what renders
694
- nothing there: spacing (`\,`, `\quad`), `\label{…}` / `\tag{…}`, a row
758
+ nothing there: spacing (`\,`, `\quad`), `\label{…}` / `\caption{…}`, a row
695
759
  break, `\end{…}`, the null delimiter `\right.`. It looks into a closing
696
760
  `\text{…}` / `\mathrm{…}`, so `5\ \text{m,}` is caught. A finding is located
697
761
  at the mark, so a per-place waiver can cover it.
@@ -1021,7 +1085,9 @@ or `a = b \le c` — write it as an `aligned` block. The first line carries the
1021
1085
  left-hand side. Every later line starts with `&` directly before its relation
1022
1086
  (`&=`, `&\le`, `&\approx`, `&\propto`), so the relation signs stand in one
1023
1087
  column. A step that needs a reason gets it at the end of its own line, after
1024
- `\qquad` (or `\quad` when the line is tight), inside `\text{…}`:
1088
+ `\qquad` (or `\quad` when the line is tight), inside `\text{…}`. A `\quad`
1089
+ clause that names an index and its set, `\quad i = 1, \dots, n`, is
1090
+ [N23](#n23)'s set statement, not a reason:
1025
1091
 
1026
1092
  ```text
1027
1093
  $$
@@ -1131,7 +1197,9 @@ _Enforcement._ Build-enforced (`consistency.ts` `checkNotationUnits`,
1131
1197
  and `units-vocab` (a token outside the vocabulary).
1132
1198
 
1133
1199
  _Exceptions._ A `lint-ignore.yml` entry (`- notation-units:<key> — <reason>`)
1134
- for a symbol whose unit is genuinely context-dependent.
1200
+ for a symbol whose unit is genuinely context-dependent. An index entry
1201
+ (`latex: "{}_0"`, [N23](#n23)) takes no units at all: a subscript has none,
1202
+ and the derived card it makes takes the indexed symbol's.
1135
1203
 
1136
1204
  ## N20
1137
1205
 
@@ -1165,33 +1233,33 @@ definition table) may cite the whole; say so in the locator (`"whole"`).
1165
1233
 
1166
1234
  **A gloss is named by a short noun phrase, not described by a sentence.**
1167
1235
 
1168
- A gloss binds a symbol on one page with a `label` shown in the hover card. The
1169
- label *names* the symbol; it does not explain it.
1236
+ A gloss binds a symbol on one page with a `name` shown in the hover card. The
1237
+ name *names* the symbol; it does not explain it.
1170
1238
 
1171
1239
  ```yaml
1172
1240
  # A name.
1173
- - key: launch-angle
1174
- label: launch angle
1241
+ - latex: \theta
1242
+ name: launch angle
1175
1243
 
1176
- # A description — belongs in a full notation entry's body, not a gloss label.
1177
- - key: launch-angle
1178
- label: the angle between the launch velocity and the horizontal, which we vary
1244
+ # A description — belongs in a full notation entry's body, not a gloss name.
1245
+ - latex: \theta
1246
+ name: the angle between the launch velocity and the horizontal, which we vary
1179
1247
  ```
1180
1248
 
1181
1249
  _Rationale._ A gloss is the lightweight form: a name and a glyph. Once the
1182
- label grows a clause or a sentence it is doing a notation entry's job without
1250
+ name grows a clause or a sentence it is doing a notation entry's job without
1183
1251
  that entry's units, sources, or shared identity — and the card shows a wall of
1184
1252
  text where every other card shows a name.
1185
1253
 
1186
- _How to apply._ Keep the label to a few words with no sentence punctuation or
1254
+ _How to apply._ Keep the name to a few words with no sentence punctuation or
1187
1255
  relative clause. If the symbol needs a description, promote the gloss to a
1188
1256
  notation entry (the `gloss-wants-promoting` hint under N4 says when).
1189
1257
 
1190
1258
  _Enforcement._ Build-enforced (`consistency.ts` `checkGlossNameShape`, `pnpm
1191
- validate:content`) — warning `gloss-name-shape` for a label over the word
1259
+ validate:content`) — warning `gloss-name-shape` for a name over the word
1192
1260
  limit or containing sentence punctuation / a relative clause.
1193
1261
 
1194
- _Exceptions._ None — a label that cannot be short is a notation entry.
1262
+ _Exceptions._ None — a name that cannot be short is a notation entry.
1195
1263
 
1196
1264
  ---
1197
1265
 
@@ -1292,6 +1360,7 @@ operator on the page, and why.
1292
1360
 
1293
1361
  | Code | Severity | When |
1294
1362
  | --- | --- | --- |
1363
+ | `accent-swallowed` | warning | `\explain{key}{\hat{\beta}}` names a key whose own `latex` has no `\hat`, so its card cannot explain the hat |
1295
1364
  | `suspended-operator-unresolved` | error | a decorated form of a suspended decoration has no whole form |
1296
1365
  | `operator-suspended` | info | a suspension with a `reason` — recorded, nothing to fix |
1297
1366
  | `operator-suspension-reason` | warning | a suspension without a `reason` |
@@ -1305,7 +1374,12 @@ _Enforcement._ Tier-3 warning `accent-explained` (`consistency.ts`
1305
1374
  `checkAccentExplained`) for a decoration that composes with no operator entry
1306
1375
  and is not on the allow list, one per decoration per lesson, surfaced by `pnpm
1307
1376
  validate:content` and silenced only by a reviewed line in `lint-ignore.yml`
1308
- (`accent-explained:\hat — <reason>`). Suspension is build-enforced: the
1377
+ (`accent-explained:\hat — <reason>`). The same function warns
1378
+ `accent-swallowed` for an `\explain{key}{…}` around a decorated form whose
1379
+ key's `latex` lacks that decoration (`\explain{beta}{\hat{\beta}}`): the wrap
1380
+ counts as the whole form, but its card says nothing about the hat. Wrap only
1381
+ the letter (`\hat{\explain{beta}{\beta}}`) and explain the hat, or name an entry
1382
+ for `\hat{\beta}`. Suspension is build-enforced: the
1309
1383
  completeness gate (`gate-math.ts`) and the render path (`remark-notation.mjs`)
1310
1384
  both fail `suspended-operator-unresolved`; `checkOperatorSuspension` emits the
1311
1385
  other three codes.
@@ -1313,3 +1387,184 @@ other three codes.
1313
1387
  _Exceptions._ Suspension (above) is the sanctioned way to give a decoration a
1314
1388
  different meaning on one page. Quoting a source that uses a decoration
1315
1389
  differently **and** showing what it means in the same passage.
1390
+
1391
+ ## N23
1392
+
1393
+ **An index is part of the symbol: a subscript is explained by the indexed
1394
+ symbol's entry, a family entry, or an index entry.**
1395
+
1396
+ `\mathbf{v}_0` is the initial velocity, not the velocity: hovering it must
1397
+ say so. A subscript on a symbol is explained in one of three ways, and a card
1398
+ on the base alone does not count. If more than one applies, the first wins,
1399
+ the same precedence [N22](#n22) uses for decorations:
1400
+
1401
+ 1. **Whole form** — the indexed symbol is a quantity of its own. Give it its
1402
+ own entry; that form gets that card, under a power too (`v_0^{2}` is `v_0`
1403
+ squared):
1404
+
1405
+ ```yaml
1406
+ key: initial-velocity
1407
+ latex: \mathbf{v}_0
1408
+ label: Initial velocity
1409
+ ```
1410
+
1411
+ 2. **Family** — the entry's index is a running letter: `latex: x_k`, "k-th
1412
+ iterate". It gives its card to every instance with the same shape — the
1413
+ same base and number of comma-separated slots: `x_1`, `x_{k+1}`, `x_N` —
1414
+ ahead of a plain `x` entry, and never to a bare `x`, which is a different
1415
+ object (it needs its own entry, or an `\explain{key}{x}` where it really
1416
+ is shorthand). A digit fills a running slot, but a `\mathrm{…}` or
1417
+ `\text{…}` label never does: `x_{\mathrm{e}}` is not an instance of
1418
+ `x_k`. Two families on one base that both fit an instance fail
1419
+ `ambiguous-base-fallback`: keep one per page, or `\explain` the instance.
1420
+ An entry whose index is fixed (`v_0`, `H_1`, `T_{\mathrm{opt}}`, or one
1421
+ marked `fixedIndex: true`) is a single symbol and lends its card to
1422
+ nothing. A function form still lends by its head: `V_0(1_t)` explains
1423
+ `V_0(1_2)`.
1424
+
1425
+ 3. **Index entry** — the subscript means the same thing on every symbol of
1426
+ the page. Give the subscript alone an entry: `latex` is the subscript on
1427
+ an empty group, quoted, because a bare `{}` starts a YAML mapping.
1428
+
1429
+ ```yaml
1430
+ key: motion.initial
1431
+ latex: "{}_0"
1432
+ label: Initial value
1433
+ meaning: The value of a quantity at the start of the scenario, the instant when time is zero.
1434
+ reads: "{base} evaluated at t = 0"
1435
+ formula: "{base}_0 = {base}\\,(\\explain{motion.time}{t} = 0)"
1436
+ ```
1437
+
1438
+ With the entry of the symbol it indexes, it makes a **derived card**: the
1439
+ whole `\mathbf{v}_0` is one target, titled "Velocity vector evaluated at
1440
+ t = 0". Its summary is the index entry's `meaning`, never the base's; its
1441
+ formula is the index entry's `formula` with `{base}` bound to the base
1442
+ symbol, so the reader opens the base's card from it; its units are the
1443
+ base's. `reads` defaults to "`<label>` of `{base}`" ("Horizontal component
1444
+ of velocity"); it names the symbol with `{base}` and is plain prose, with
1445
+ no `$`, `\` or backtick. An index entry is listed with a square for the
1446
+ symbol, `□₀`; a derived card is never listed. The loader rejects `units`,
1447
+ `dimensionless`, `range` or `fixedIndex` on an index entry ([N19](#n19)
1448
+ does not apply), and `reads` on any other entry. Two index entries for the
1449
+ same subscript on one page fail `ambiguous-canonical-notation`: an index
1450
+ has one meaning per page.
1451
+
1452
+ Arithmetic on an index letter with its own card is explained already:
1453
+ `X_{t-1}` is `X`'s card, `t`'s card (`time.index`) and an offset, and every
1454
+ comma-separated slot needs such a letter (`F_{0,T}` needs the `0`
1455
+ explained). A letter in a subscript is an index, so a base-library `i`, `e`
1456
+ or `d` there needs a card too.
1457
+
1458
+ A subscript on an operator or a bracket is not an index: `\sum_{i=1}`,
1459
+ `\lim_{t \to 0}`, `\log_{10}`, `\lVert x\rVert_2`, `\left.f\right\rvert_{x=0}`.
1460
+ A superscript is a power (`y^{(m)}` is out of scope). An entry whose `latex`
1461
+ has a superscript and no subscript (`j^{(m)}`) is a whole form for that
1462
+ symbol and, having no index, still lends its card to its bare base.
1463
+
1464
+ ### Running and fixed indices
1465
+
1466
+ A **running** index is a lowercase letter (`i`, `k`, `t`) that ranges over a
1467
+ set; the rule asks for that set, in one of two places:
1468
+
1469
+ - on the letter's card, or on the family entry that covers it, as `range`
1470
+ (LaTeX, shown on the card as "Runs over …"):
1471
+
1472
+ ```yaml
1473
+ key: time.index
1474
+ latex: t
1475
+ range: "1, \\dots, T"
1476
+ ```
1477
+
1478
+ - after a `\quad` on the first display line where the letter is an index,
1479
+ for a set that belongs to the page:
1480
+
1481
+ ```latex
1482
+ x_i \sim \mathcal{N}(\mu,\ \sigma^2), \quad i = 1, \dots, n
1483
+ ```
1484
+
1485
+ The clause names the letter and a relation (`=`, `\in`, `\le`, `\dots`, …).
1486
+ It is a different slot from [N18](#n18)'s `\qquad` reason.
1487
+
1488
+ A **fixed** index names one member: digits, a `\mathrm{…}` or `\text{…}`
1489
+ label, a subscript an index entry declares (`{}_x`), or a letter whose card
1490
+ says `fixedIndex: true` (a component `x`, a label). An uppercase or Greek
1491
+ letter in a subscript (`X_N`, `p_\theta`) is a size or a symbol, not a
1492
+ running index, and needs no set.
1493
+
1494
+ ### `\explain` around an indexed symbol
1495
+
1496
+ `\explain{key}{v_x}` makes the whole `v_x` one target with `key`'s card. If
1497
+ `key`'s own `latex` has no index (`motion.velocity` is `v(t)`), that card
1498
+ cannot explain the `x`, and the wrap hides the gap from the resolver: the
1499
+ rule reports it. Give the subscript an index entry (`{}_x`) and drop the wrap,
1500
+ or name an entry for the indexed symbol. An index inside a larger wrap
1501
+ (`\explain{survival-probability}{S(0,t_i)}`) is the wrap's, as every glyph
1502
+ inside one is.
1503
+
1504
+ | Code | Severity | When |
1505
+ | --- | --- | --- |
1506
+ | `index-explained` | error | a subscript on a symbol has no whole form, family or index entry, and a slot has no letter with its own card — a digit, a label, a base-library letter — or the base itself has no card |
1507
+ | `index-swallowed` | error | `\explain{key}{X_i}` names a key whose `latex` has no index |
1508
+ | `index-range` | error | a running index letter's card (and the family entry over it) has no `range`, and the first display line where it is an index has no `\quad` clause stating its set |
1509
+ | `derived-key-taken` | error | an authored entry's key equals a derived card's, `<base>.sub.<index>` |
1510
+
1511
+ In a lesson body each is reported once per indexed form (per letter, for
1512
+ `index-range`) per page, at its line; a `notation.local` formula counts with
1513
+ its page. An assessment counts on its own, and a shared entry's formula once
1514
+ per formula, with no line.
1515
+
1516
+ **Where the rule applies.** Everywhere math is rendered:
1517
+
1518
+ | Where | Derived cards | Checked by |
1519
+ | --- | --- | --- |
1520
+ | Lesson body (and a page that is not a chapter) | yes | `validate:content` (all four codes) |
1521
+ | Assessment prompt, explanation, option | yes | `validate:content` (`index-explained`, `index-swallowed`) |
1522
+ | Lab math (`renderLabMath` with a `labMathScope` table) | yes | `astro build`, as the lab renders |
1523
+ | `<Diagram>` node formula | yes | `astro build`, as the diagram renders |
1524
+ | Notation entry `formula:` | no | `validate:content` (`index-explained`, `index-swallowed`) |
1525
+
1526
+ A notation formula resolves against its own card's table, so an index entry
1527
+ does not compose there. Explain an indexed symbol in a formula with a whole
1528
+ form instead: `\explain{motion.launch-speed}{v_0}`, or a gloss on the entry
1529
+ (`glosses:` with `latex: v_0` and its `name`, as in [N21](#n21)). Or wrap the subscript in the index entry's key,
1530
+ `\mathbf{v}_{\explain{motion.initial}{0}}`, which gives the `0` that card.
1531
+ `index-range` reads lesson bodies only: a letter's set is stated once, on its
1532
+ card or on the page.
1533
+
1534
+ _Rationale._ The resolver binds a letter by its shape, so before this rule a
1535
+ subscript inherited whatever its base meant: `\mathbf{v}_0` opened "Velocity
1536
+ vector", with only the `v` highlighted and the `0` explained by nothing. An
1537
+ indexed entry also lent its card to symbols it is not: the family `\alpha_i`
1538
+ ("ARCH coefficient") opened on a bare `\alpha` meaning a learning rate, and
1539
+ `H_0` ("Null hypothesis") on `H_1`. A printed book's list of symbols makes
1540
+ the same split this rule does: `v_0` when it is a named quantity, and a
1541
+ "Subscripts" group that gives `0` once when it means the same thing on many
1542
+ symbols. And a running index without its set is a familiar omission: the
1543
+ reader cannot tell whether `i` runs to `n` or to `N`.
1544
+
1545
+ _How to apply._ For each reported form, pick the first that fits: a whole
1546
+ form for a named quantity, a family for a running index, an index entry for a
1547
+ subscript that recurs with one meaning. The same index on the same letter can
1548
+ mean different things on two pages (`x_0` as a starting point, a center, a
1549
+ first observation), so an index entry is often page-local
1550
+ (`notation.local`). Give each index letter's card a `range`, or state the set
1551
+ after a `\quad`.
1552
+
1553
+ _Enforcement._ Partial (`gate-indices.ts` `gateIndices`, `pnpm
1554
+ validate:content`) — the four errors above, over lesson bodies, notation
1555
+ formulas and assessments. A lesson-body finding is located, so `{/*
1556
+ explico-allow N23: <reason> */}` waives one place. Lab math
1557
+ (`render-lab-math.ts`) and `<Diagram>` formulas fail `astro build` with the
1558
+ same findings as they render (`indexFindings`). A reviewer checks the rest:
1559
+ that a `\quad` clause states the right set, and that a lowercase index the
1560
+ build treats as running really runs. A course switches the rule
1561
+ off with `rules.N23.enabled: false`, which stops all of it; the derived cards
1562
+ are built either way. The resolver behaviors the rule rests on — whole forms
1563
+ under a power, families by shape, no lending from a fixed label or to a bare
1564
+ symbol — are part of [N4](#n4) and always on: a bare symbol that has no card
1565
+ of its own fails `unresolved-glyph`, and its message names the indexed
1566
+ entry with that base.
1567
+
1568
+ _Exceptions._ A `\quad` clause the check misreads takes a waiver with its
1569
+ reason. A lab whose glyph table is not the one `labMathScope` returned (a
1570
+ copy) composes no derived card and is not checked.
@@ -31,5 +31,6 @@ fails when it is stale.
31
31
  | N7 | The differential `d` is upright `\mathrm{d}`. | 2026-09-26 | [N2](README.md#rule-index) | `\mathrm{d}` is a line in N2’s `reserved` table. |
32
32
  | N16 | In AI-generated mathematical content, alternate delimiter types for nested grouping when their shape is stylistic; preserve every conventional or semantic delimiter shape. | 2026-09-26 | [N17](README.md#rule-index) | Folded into N17 with the preference dropped: a delimiter’s shape carries its meaning, so depth is shown by size — which this engine handles automatically — not by alternating shapes. The rule also scoped itself to “AI-generated” content, which is not a property of the page. |
33
33
  | C4 | On a release, move `## [Unreleased]` to `## [x.y.z] - YYYY-MM-DD` and bump `about.version` in the same commit. | 2026-09-26 | [C3](README.md#rule-index) | The release procedure and the invariant it keeps are one rule, and `knowledge-changelog-missing` (owned by C3) was already the check that fired on a half-finished release. |
34
+ | S5 | A sign or perspective convention is declared once and cited everywhere else. | 2026-10-07 | [N4](README.md#rule-index) | Its check knew one finance course’s phrasing and one key per course, and never fired in any course; the `conventions` setting it read is gone. A convention is a definition, so it is now N4’s: defined once on its notation entry, whose `meaning` names the other choice and whose body says how to convert. |
34
35
 
35
36
  <!-- RETIRED-RULES:END -->
@@ -1,8 +1,9 @@
1
1
  # Structure rules
2
2
 
3
3
  `S` rules govern the shape of a lesson's headings, the short captions on its
4
- keyed floats, the paragraphs that open a part page, and how a table, a
5
- diagram, or a convention is declared so the rest of the course can reach it —
4
+ keyed floats, the paragraphs that open a part page, what a page that is not
5
+ a chapter may carry, and how a table or a
6
+ diagram is declared so the rest of the course can reach it —
6
7
  the parts of a page a reader scans, links to, or cites. This
7
8
  is not [structure.md](structure.md), which is the taxonomy these rules (and
8
9
  the rest of the engine) speak in, not a rule set itself.
@@ -27,7 +28,9 @@ tooltip — the `[short]` half of LaTeX's `\caption[short]{long}`. The long
27
28
  `caption` is never measured: it is the full sentence shown with the float's
28
29
  number (see [components.md § The caption pair](components.md#the-caption-pair)).
29
30
  A heading is measured as the reader sees it: its markers (`[collapsed]`,
30
- `[card]`, …) never render, so they never count.
31
+ `[card]`, …) never render, so they never count; a formula counts as its
32
+ glyphs (`$\explain{motion.range}{R}$` is one character) and a `[[key]]` term
33
+ as its label.
31
34
 
32
35
  A heading or short caption that runs long is usually doing a caption's job.
33
36
  Move the rest of the sentence into the paragraph right after a heading, or
@@ -109,7 +112,8 @@ launch angle that gives the largest range.
109
112
  ```
110
113
 
111
114
  Keep a blank line inside each tag, so MDX parses the paragraph as Markdown and
112
- its math and `[[key]]` references render.
115
+ its math and `[[key]]` references render with their cards
116
+ ([S10](#s10)). A part page recalls symbols; it defines none.
113
117
 
114
118
  The word range is a course setting, `rules.S2` in `course.settings.json`
115
119
  (`CourseConfig.rules`). Each end defaults to 30–150 words; list only what
@@ -184,9 +188,13 @@ validate:content`) — a warning per lesson (`raw-table-markdown`,
184
188
  `raw-table-html`); `remarkNotation` repeats it at render time for content
185
189
  added since the manifest was compiled. It does not fail the build.
186
190
 
187
- _Exceptions._ A table inside a component that already gives it identity is
188
- out of scope: the scan only counts tables at the top level of the lesson
189
- body.
191
+ _Exceptions._ The scan counts a table only where a `<Table>` would number
192
+ it: at the top level of the lesson body, or in a `<Memo>`, `<ShortNote>` or
193
+ `<Fold>`, which keep their place in the reading order. A table already inside
194
+ a `<Table>` or `<Figure>` has its identity. A table in a worked example
195
+ (`<CompactExample>`) or in another component is illustrative, like an
196
+ equation there: leave it a plain Markdown table. A `<Table>` there gets no
197
+ number, and the build fails on it with that reason.
190
198
 
191
199
  ## S4
192
200
 
@@ -224,43 +232,6 @@ fail the build.
224
232
  _Exceptions._ A purely decorative diagram that nothing will ever cite may stay
225
233
  unkeyed; say so in a comment next to it.
226
234
 
227
- ## S5
228
-
229
- **A sign or perspective convention is declared once and cited everywhere
230
- else.**
231
-
232
- A course names one notation entry as its sign convention
233
- (`conventions.signConventionKey`) and one competency as its cash-flow /
234
- perspective convention (`conventions.cashFlowPerspectiveCompetency`). A lesson
235
- whose prose states such a convention — "outflows are negative", "angles are
236
- measured counter-clockwise from the horizontal" — resolves it to that single
237
- declaration rather than restating it.
238
-
239
- ```mdx
240
- <!-- Restated: a second, drifting definition. -->
241
- In this chapter, an outflow is a negative cash flow.
242
-
243
- <!-- Resolved to the declaration. -->
244
- Cash flows follow the [[cash-flow.sign]] convention: an outflow is negative.
245
- ```
246
-
247
- _Rationale._ A convention restated in several lessons drifts — one says
248
- "negative", another "shown in parentheses" — and a reader who learned it in
249
- one place cannot tell whether the other place means the same thing. One
250
- declaration, cited, keeps the meaning in one file.
251
-
252
- _How to apply._ Cite the convention's notation entry with `[[key]]` in the
253
- sentence that invokes it, or list the convention's competency in the lesson's
254
- `teaches` / `requires`. Either anchors the prose to the declaration.
255
-
256
- _Enforcement._ Build-enforced (`consistency.ts`
257
- `checkConventionSingleDefinition`, `pnpm validate:content`) — a warning
258
- (`convention-single-definition`) for a lesson whose prose matches the
259
- convention pattern and is anchored by neither the term nor the competency.
260
-
261
- _Exceptions._ The lesson that *is* the declaration — the one that teaches the
262
- convention's competency — is anchored by definition.
263
-
264
235
  ## S6
265
236
 
266
237
  **Headings descend one level at a time: a lesson body starts at `##` and
@@ -301,7 +272,11 @@ _Enforcement._ Build-enforced (`gate-heading-structure.ts`, `pnpm
301
272
  validate:content`, `heading-skip`) over a lesson's outline — the top-level
302
273
  headings the section numbers count. A heading inside a component's children
303
274
  (a `<Memo>`, a `<CompactExample>`) belongs to that component and is not part
304
- of the outline, and a memo's own bar is not a heading of the lesson.
275
+ of the outline, and a memo's own bar is not a heading of the lesson. A
276
+ `<CompactExamples>` or an `<AssessmentSet>` draws an `<h2>` of its own, so
277
+ one inside a `<Memo>` or a `<ShortNote>` fails (`heading-nested`): its
278
+ heading would land under the memo's `<h6>`, and "On this page" would never
279
+ list it. Put it at the top level, after the memo.
305
280
 
306
281
  _Exceptions._ None in the rule; waive one place with
307
282
  `{/* explico-allow S6: reason */}` above the heading.
@@ -449,3 +424,67 @@ title from one that builds on it.
449
424
  _Exceptions._ A float that needs no name of its own may go without a
450
425
  `short`; the "Figures and tables" page then lists it by its long caption,
451
426
  muted.
427
+
428
+ ## S10
429
+
430
+ **A page that is not a chapter — the landing page, a part’s overview page —
431
+ explains its symbols like a chapter, but carries no numbered float: it links
432
+ the chapter’s float instead.**
433
+
434
+ This applies to `content/docs/index.mdx`, to every
435
+ `content/docs/<part>/index.mdx`, and to any other page outside a part
436
+ directory. What makes a card works there as in a chapter:
437
+
438
+ - a `[[key]]` term and `\explain{key}{…}` in math, each opening its card;
439
+ - plain math, bound symbol by symbol, so every symbol it shows is explained;
440
+ - a citation `[@id]`, an abbreviation, and a link to a chapter’s float,
441
+ `[[motion/constant-acceleration#tab-kinematics-summary]]`, whose card
442
+ previews the table.
443
+
444
+ The math and prose rules hold there too ([N4](notation.md#n4),
445
+ [N9](notation.md#n9), [N10](notation.md#n10), [N14](notation.md#n14), and a
446
+ course’s `X` rules). What such a page may not carry:
447
+
448
+ | Not here | Instead |
449
+ | --- | --- |
450
+ | `<Table>`, `<Figure>`, `<Diagram>`, `<CompactExamples>`, `<AssessmentSet>` | Link the chapter’s float: `[[<chapter>#tab-<key>]]` (`fig-`, `dia-`, …). |
451
+ | A lab, or any `client:*` island | Link the chapter’s `<Figure>` that holds it. |
452
+ | `\label{eq:…}` on a display equation | Drop the label (the equation shows unnumbered), or link the chapter’s equation. |
453
+ | `notation.local`, `notation.suspend` | Define the symbol in the chapter that introduces it, or in shared notation, and name it with `[[key]]`. |
454
+ | `$…$` or `[[…]]` in `hero.title`, `hero.tagline`, a `<PartOverview chain>` step | Write words; these strings are plain text and print markup literally. |
455
+
456
+ ```mdx
457
+ <PartScope>
458
+
459
+ Part 1 ended on a point that moves under a constant acceleration
460
+ [[motion.acceleration]]. The worked examples in
461
+ [[motion/constant-acceleration#tab-kinematics-summary]] give its velocity.
462
+
463
+ </PartScope>
464
+ ```
465
+
466
+ A plain Markdown table is ordinary text on such a page: it has no caption and
467
+ no number, so it is not a float.
468
+
469
+ _Rationale._ A float number starts with its chapter’s number, and a page that
470
+ is not a chapter has none: a table on a part page would need a number that
471
+ collides with a chapter’s, or no number, which makes it the one float in the
472
+ course a reference cannot reach. A part page in a printed book points to the
473
+ chapters’ floats and does not copy them. The reference card gives the reader
474
+ the same view without the copy. Symbols are different: an overview that
475
+ recalls a result needs its symbols, and a symbol without its card breaks the
476
+ promise every other page keeps.
477
+
478
+ _How to apply._ Write the overview’s recap with the chapter’s own symbols,
479
+ each introduced with `[[key]]`. Where the recap leans on a table, figure or
480
+ equation, link it instead of repeating it. If a symbol has no shared entry,
481
+ recall it in words or give it one in `content/notation/`.
482
+
483
+ _Enforcement._ Build-enforced (`gate-non-chapter-pages.ts`,
484
+ `pnpm validate:content`): `non-chapter-float` for a numbered block, a lab or
485
+ an equation label; `non-chapter-local-notation` for a notation block;
486
+ `non-chapter-plain-text` for markup in a plain-text string. `astro build`
487
+ fails the same numbered block and equation label at render time. This rule is
488
+ **locked**: turning it off would not give the page a number to print.
489
+
490
+ _Exceptions._ None.