explico 0.8.0 → 0.11.1

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 (230) hide show
  1. package/CHANGELOG.md +1257 -1
  2. package/README.md +60 -15
  3. package/dist/compiler/astro.d.ts +6 -1
  4. package/dist/compiler/cli.d.ts +46 -2
  5. package/dist/compiler/cli.js +70 -4
  6. package/dist/compiler/collections.d.ts +35 -2
  7. package/dist/compiler/collections.js +68 -5
  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 +4 -1
  15. package/dist/compiler/logging.js +4 -1
  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 +3 -2
  27. package/dist/reference/diagrams-validate.d.ts +7 -2
  28. package/dist/reference/diagrams-validate.js +9 -1
  29. package/dist/reference/equations-validate.d.ts +8 -5
  30. package/dist/reference/equations-validate.js +1 -0
  31. package/dist/reference/equations.d.mts +60 -31
  32. package/dist/reference/equations.mjs +83 -43
  33. package/dist/reference/examples-validate.d.ts +31 -0
  34. package/dist/reference/examples-validate.js +48 -0
  35. package/dist/reference/figures-validate.d.ts +1 -1
  36. package/dist/reference/figures-validate.js +4 -1
  37. package/dist/reference/folds-validate.d.ts +33 -0
  38. package/dist/reference/folds-validate.js +32 -0
  39. package/dist/reference/gate-abbreviations.d.ts +3 -1
  40. package/dist/reference/gate-abbreviations.js +1 -0
  41. package/dist/reference/gate-course-rules.d.ts +32 -0
  42. package/dist/reference/gate-course-rules.js +85 -0
  43. package/dist/reference/gate-heading-length.d.ts +5 -2
  44. package/dist/reference/gate-heading-length.js +45 -21
  45. package/dist/reference/gate-heading-structure.d.ts +41 -0
  46. package/dist/reference/gate-heading-structure.js +113 -0
  47. package/dist/reference/gate-math-punctuation.d.ts +71 -0
  48. package/dist/reference/gate-math-punctuation.js +251 -0
  49. package/dist/reference/gate-math.d.ts +22 -6
  50. package/dist/reference/gate-math.js +42 -8
  51. package/dist/reference/gate-part-pages.d.ts +50 -0
  52. package/dist/reference/gate-part-pages.js +118 -0
  53. package/dist/reference/gate-prose-notation.d.ts +21 -11
  54. package/dist/reference/gate-prose-notation.js +25 -7
  55. package/dist/reference/katex-delimiters.mjs +20 -7
  56. package/dist/reference/keyed-float-validate.d.ts +27 -10
  57. package/dist/reference/keyed-float-validate.js +15 -1
  58. package/dist/reference/keyed-jsx-floats.d.mts +11 -5
  59. package/dist/reference/keyed-jsx-floats.mjs +73 -20
  60. package/dist/reference/knowledge-checks-validate.d.ts +1 -1
  61. package/dist/reference/knowledge-checks.d.mts +3 -1
  62. package/dist/reference/knowledge-checks.mjs +9 -4
  63. package/dist/reference/lab-math-scope.d.ts +1 -0
  64. package/dist/reference/lab-math-slot.d.ts +34 -0
  65. package/dist/reference/lab-math-slot.js +6 -0
  66. package/dist/reference/math-glyphs.d.mts +80 -4
  67. package/dist/reference/math-glyphs.mjs +572 -37
  68. package/dist/reference/page-notation.d.ts +6 -1
  69. package/dist/reference/raw-floats-validate.js +1 -1
  70. package/dist/reference/raw-floats.mjs +1 -1
  71. package/dist/reference/rehype-heading-anchors.d.mts +14 -0
  72. package/dist/reference/rehype-heading-anchors.mjs +116 -17
  73. package/dist/reference/rehype-table-header-labels.d.mts +6 -1
  74. package/dist/reference/rehype-table-header-labels.mjs +21 -3
  75. package/dist/reference/remark-asides.d.mts +3 -0
  76. package/dist/reference/remark-asides.mjs +180 -0
  77. package/dist/reference/remark-memo.d.mts +3 -1
  78. package/dist/reference/remark-memo.mjs +42 -11
  79. package/dist/reference/remark-notation.d.mts +60 -10
  80. package/dist/reference/remark-notation.mjs +387 -36
  81. package/dist/reference/render-lab-math.d.ts +4 -26
  82. package/dist/reference/render-lab-math.js +4 -4
  83. package/dist/reference/sections-validate.d.ts +27 -0
  84. package/dist/reference/sections-validate.js +41 -0
  85. package/dist/reference/sections.d.mts +97 -0
  86. package/dist/reference/sections.mjs +306 -0
  87. package/dist/reference/tables-validate.d.ts +1 -1
  88. package/dist/reference/types.d.ts +17 -1
  89. package/dist/reference/unit-words.d.ts +37 -0
  90. package/dist/reference/unit-words.js +170 -0
  91. package/dist/rules/catalog.d.ts +176 -0
  92. package/dist/rules/catalog.js +983 -0
  93. package/dist/rules/course-rules.d.ts +115 -0
  94. package/dist/rules/course-rules.js +292 -0
  95. package/dist/rules/course-rules.schema.json +138 -0
  96. package/dist/rules/in-effect.d.ts +53 -0
  97. package/dist/rules/in-effect.js +142 -0
  98. package/dist/rules/rules.schema.json +1072 -0
  99. package/dist/rules/settings.d.ts +3 -0
  100. package/dist/rules/settings.js +290 -0
  101. package/dist/rules/waivers.d.ts +53 -0
  102. package/dist/rules/waivers.js +132 -0
  103. package/dist/session/PreferenceStore.d.ts +5 -0
  104. package/dist/session/PreferenceStore.js +9 -0
  105. package/docs/VERSIONING.md +5 -3
  106. package/docs/authoring/README.md +232 -40
  107. package/docs/authoring/agents.md +55 -9
  108. package/docs/authoring/components.md +178 -22
  109. package/docs/authoring/migrating.md +1152 -2
  110. package/docs/authoring/notation.md +576 -322
  111. package/docs/authoring/prose.md +551 -0
  112. package/docs/authoring/retired.md +35 -0
  113. package/docs/authoring/structure-rules.md +422 -40
  114. package/docs/authoring/structure.md +445 -62
  115. package/docs/authoring/versioning.md +20 -28
  116. package/docs/authoring/visualizations.md +142 -20
  117. package/package.json +2 -2
  118. package/src/compiler/cli.ts +116 -6
  119. package/src/compiler/collections.ts +111 -7
  120. package/src/compiler/course-config.ts +273 -34
  121. package/src/compiler/knowledge-version.ts +1 -1
  122. package/src/compiler/lesson-derivation.ts +54 -15
  123. package/src/compiler/logging.ts +8 -2
  124. package/src/compiler/manifest.ts +573 -104
  125. package/src/components/BlockHeading.astro +113 -0
  126. package/src/components/CourseContents.astro +221 -0
  127. package/src/components/CurriculumMap.astro +5 -6
  128. package/src/components/FloatIndex.astro +200 -0
  129. package/src/components/HoverPanel.astro +431 -79
  130. package/src/components/PartBridge.astro +27 -0
  131. package/src/components/PartOverview.astro +401 -0
  132. package/src/components/PartScope.astro +27 -0
  133. package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
  134. package/src/components/assessments/AssessmentRunner.tsx +8 -5
  135. package/src/components/assessments/AssessmentSet.astro +23 -26
  136. package/src/components/block-heading.ts +146 -0
  137. package/src/components/card-arrow.ts +243 -0
  138. package/src/components/card-drag.ts +631 -0
  139. package/src/components/card-jump.ts +45 -0
  140. package/src/components/card-manager.ts +1589 -0
  141. package/src/components/card-memory.ts +177 -0
  142. package/src/components/card-placement.ts +504 -0
  143. package/src/components/card-side.ts +336 -0
  144. package/src/components/card-travel.ts +197 -0
  145. package/src/components/citation/CitationLayer.astro +77 -76
  146. package/src/components/diagram/Diagram.astro +161 -316
  147. package/src/components/disclosure/course-disclosure.ts +24 -0
  148. package/src/components/disclosure/hover-preview.ts +122 -42
  149. package/src/components/disclosure/persisted-disclosure.ts +3 -3
  150. package/src/components/disclosure/print-as-shown.ts +252 -0
  151. package/src/components/examples/CompactExample.astro +3 -1
  152. package/src/components/examples/CompactExamples.astro +101 -94
  153. package/src/components/figure/Figure.astro +174 -274
  154. package/src/components/figure/FloatCaption.astro +87 -0
  155. package/src/components/figure/figure-state.ts +58 -0
  156. package/src/components/figure/figure-tools.ts +87 -0
  157. package/src/components/float-name.ts +101 -0
  158. package/src/components/fold/Fold.astro +125 -0
  159. package/src/components/fold/fold-id.ts +40 -0
  160. package/src/components/memo/Memo.astro +162 -231
  161. package/src/components/memo/ShortNote.astro +6 -6
  162. package/src/components/notation/NotationFormula.astro +5 -1
  163. package/src/components/notation/NotationGlossary.astro +3 -4
  164. package/src/components/notation/NotationLayer.astro +169 -162
  165. package/src/components/notation/NotationMath.astro +10 -1
  166. package/src/components/reference/FloatRefLayer.astro +485 -423
  167. package/src/components/starlight/AboutPanel.astro +5 -4
  168. package/src/components/starlight/CollapsibleSections.astro +207 -93
  169. package/src/components/starlight/DiagramEnhancer.astro +15 -138
  170. package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
  171. package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
  172. package/src/components/starlight/LayoutHeader.astro +298 -19
  173. package/src/components/starlight/LayoutPageSidebar.astro +99 -12
  174. package/src/components/starlight/LayoutPageTitle.astro +120 -0
  175. package/src/components/starlight/LayoutSidebar.astro +229 -11
  176. package/src/components/starlight/LessonExportActions.astro +53 -72
  177. package/src/components/starlight/LessonFooter.astro +26 -4
  178. package/src/components/starlight/section-parts.ts +15 -2
  179. package/src/components/starlight/section-summary.ts +181 -0
  180. package/src/components/table/Table.astro +95 -76
  181. package/src/components/table/TableOverflowExpander.astro +65 -28
  182. package/src/content-config.ts +40 -4
  183. package/src/reference/base-accents.mjs +31 -0
  184. package/src/reference/base-roman-labels.mjs +2 -2
  185. package/src/reference/consistency.ts +176 -5
  186. package/src/reference/delimiter-size.ts +3 -2
  187. package/src/reference/diagrams-validate.ts +17 -1
  188. package/src/reference/equations-validate.ts +10 -5
  189. package/src/reference/equations.mjs +83 -43
  190. package/src/reference/examples-validate.ts +97 -0
  191. package/src/reference/figures-validate.ts +6 -1
  192. package/src/reference/folds-validate.ts +65 -0
  193. package/src/reference/gate-abbreviations.ts +1 -0
  194. package/src/reference/gate-course-rules.ts +134 -0
  195. package/src/reference/gate-heading-length.ts +66 -35
  196. package/src/reference/gate-heading-structure.ts +193 -0
  197. package/src/reference/gate-math-punctuation.ts +381 -0
  198. package/src/reference/gate-math.ts +69 -10
  199. package/src/reference/gate-part-pages.ts +217 -0
  200. package/src/reference/gate-prose-notation.ts +59 -12
  201. package/src/reference/katex-delimiters.mjs +20 -7
  202. package/src/reference/keyed-float-validate.ts +45 -11
  203. package/src/reference/keyed-jsx-floats.mjs +73 -20
  204. package/src/reference/knowledge-checks-validate.ts +1 -1
  205. package/src/reference/knowledge-checks.mjs +9 -4
  206. package/src/reference/lab-math-slot.ts +36 -0
  207. package/src/reference/math-glyphs.mjs +572 -37
  208. package/src/reference/raw-floats-validate.ts +1 -1
  209. package/src/reference/raw-floats.mjs +1 -1
  210. package/src/reference/rehype-heading-anchors.mjs +116 -17
  211. package/src/reference/rehype-table-header-labels.mjs +21 -3
  212. package/src/reference/remark-asides.mjs +180 -0
  213. package/src/reference/remark-memo.mjs +42 -11
  214. package/src/reference/remark-notation.mjs +387 -36
  215. package/src/reference/render-lab-math.ts +16 -31
  216. package/src/reference/sections-validate.ts +81 -0
  217. package/src/reference/sections.mjs +306 -0
  218. package/src/reference/tables-validate.ts +2 -1
  219. package/src/reference/types.ts +17 -0
  220. package/src/reference/unit-words.ts +233 -0
  221. package/src/rules/catalog.ts +1298 -0
  222. package/src/rules/course-rules.ts +456 -0
  223. package/src/rules/in-effect.ts +235 -0
  224. package/src/rules/settings.ts +448 -0
  225. package/src/rules/waivers.ts +249 -0
  226. package/src/session/PreferenceStore.ts +15 -0
  227. package/src/styles/global.css +1467 -163
  228. package/src/styles/surfaces.css +182 -0
  229. package/src/components/hover-panel.ts +0 -339
  230. package/src/components/popup-stack.ts +0 -345
@@ -1,7 +1,9 @@
1
1
  # Structure rules
2
2
 
3
- `S` rules govern the shape of a lesson's headings and the short titles on its
4
- keyed floats — the parts of a page a reader scans before reading prose. This
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 —
6
+ the parts of a page a reader scans, links to, or cites. This
5
7
  is not [structure.md](structure.md), which is the taxonomy these rules (and
6
8
  the rest of the engine) speak in, not a rule set itself.
7
9
 
@@ -12,58 +14,438 @@ and "Review-only" mean here, and how to amend a rule.
12
14
 
13
15
  ## S1
14
16
 
15
- **No heading, and no figure/diagram/memo/short-note title, runs past 65
16
- characters.**
17
+ **No heading, no short caption (`short`, an equation's `\caption{}`) and no
18
+ short-note title runs past 65 characters.**
17
19
 
18
20
  This caps every Markdown heading (`##` through `######`), a lesson's own
19
- `frontmatter.title` (rendered as the page's `<h1>`), and the short `title` /
20
- `label` prop on `Figure`, `Diagram`, `Memo`, and `ShortNote` — the text shown
21
- on a collapsed title bar, a diagram's caption line, or a chapter's sidebar
22
- entry. `Table` is exempt: it has only a `caption`, no separate `title`, and
23
- that caption is the LaTeX-convention full sentence shown above the table.
21
+ `frontmatter.title` (rendered as the page's `<h1>`), the `short` caption of a
22
+ `Figure`, `Diagram`, `Table`, `Memo` or `CompactExamples`, a display
23
+ equation's `\caption{…}`, and a `ShortNote`'s `title` (its inline link
24
+ text). A short caption is the name a float goes by away from itself — the
25
+ heading of its `[[…]]` preview card, an equation's "Copy LaTeX — ‹caption›"
26
+ tooltip — the `[short]` half of LaTeX's `\caption[short]{long}`. The long
27
+ `caption` is never measured: it is the full sentence shown with the float's
28
+ number (see [components.md § The caption pair](components.md#the-caption-pair)).
29
+ A heading is measured as the reader sees it: its markers (`[collapsed]`,
30
+ `[card]`, …) never render, so they never count.
24
31
 
25
- A heading or float title that runs long is usually doing a caption's job. Move
26
- the rest of the sentence into the paragraph right after a heading, or into the
27
- float's own `caption`.
32
+ A heading or short caption that runs long is usually doing a caption's job.
33
+ Move the rest of the sentence into the paragraph right after a heading, or
34
+ into the float's long `caption`.
28
35
 
29
36
  ```mdx
30
- <!-- Over the limit: the title carries the whole explanation. -->
37
+ <!-- Over the limit: the short caption carries the whole explanation. -->
31
38
  <Figure
32
39
  id="ar1-forecast-fan"
33
- caption="Multi-step AR(1) forecast: the conditional mean (teal) decays geometrically to the unconditional mean while the Gaussian 95% band (shaded) widens toward the stationary dispersion Γ₀; the coral line is one seeded future path."
40
+ short="Multi-step AR(1) forecast: the conditional mean decays while the band widens"
41
+ caption="…"
34
42
  >
35
43
 
36
- <!-- At the limit: a short label, the rest moved into `caption`. -->
44
+ <!-- At the limit: a short noun phrase, the rest in the long caption. -->
37
45
  <Figure
38
46
  id="ar1-forecast-fan"
39
- title="Multi-step AR(1) forecast"
47
+ short="Multi-step AR(1) forecast"
40
48
  caption="The conditional mean (teal) decays geometrically to the unconditional mean while the Gaussian 95% band (shaded) widens toward the stationary dispersion Γ₀; the coral line is one seeded future path."
41
49
  >
42
50
  ```
43
51
 
44
- _Rationale._ A `Figure`'s `title` falls back to `caption` when omitted (see
45
- `Figure.astro`), so an unset `title` on a long-captioned figure silently
46
- becomes the text on the always-visible collapsible title bar — the reader
47
- sees the entire caption twice, once as the "heading" and once again in
48
- `<figcaption>` once they expand it. The same abuse happens with a heading
49
- authored as a full sentence instead of a label: the sidebar, the table of
50
- contents, and every anchor link now carry a paragraph instead of a title.
51
- Sixty-five characters is roughly one sidebar line at normal zoom before
52
- Starlight starts wrapping or truncating it.
53
-
54
- _How to apply._ Write the heading or `title` as a short noun phrase (a topic,
55
- not a claim about it); put the claim in the prose or the `caption`. If a
56
- `Figure` needs no distinct title beyond its caption's own first clause,
57
- extract that clause into `title` explicitly rather than omitting the prop and
58
- relying on the fallback — an omitted `title` on a long caption is exactly the
59
- failure this rule exists to catch.
52
+ _Rationale._ A short name lands where space is tight: the sidebar, the table
53
+ of contents, an anchor link, the heading of a preview card, a tooltip. A
54
+ heading authored as a full sentence instead of a label puts a paragraph in
55
+ every one of them. Sixty-five characters is roughly one sidebar line at
56
+ normal zoom before Starlight starts wrapping or truncating it.
57
+
58
+ _How to apply._ Write the heading or short caption as a short noun phrase (a
59
+ topic, not a claim about it); put the claim in the prose or the long
60
+ `caption`. A float needs no `short` at all when its long caption is already
61
+ short — leave it out rather than repeat the caption.
60
62
 
61
63
  _Enforcement._ Build-enforced (`gate-heading-length.ts`, `pnpm
62
- validate:content`) for every Markdown heading in a lesson body and every
63
- `Figure`/`Diagram`/`Memo`/`ShortNote` `title`/`label` prop. Review-only for a
64
- lesson's `frontmatter.title` — the gate scans lesson body text and cannot see
65
- frontmatter, so a long page title (`<h1>`) is caught by a reviewer, not the
66
- build.
67
-
68
- _Exceptions._ None — a heading or float title that must exceed the limit
69
- should not be a heading or a float title; find the shorter label first.
64
+ validate:content`) for every Markdown heading in a lesson body, every literal
65
+ `short` prop on a `Figure`/`Diagram`/`Table`/`Memo`/`CompactExamples`, every
66
+ display equation's `\caption{…}`, and every `ShortNote` `title`. Review-only
67
+ for a lesson's `frontmatter.title` — the gate scans lesson body text and
68
+ cannot see frontmatter, so a long page title (`<h1>`) is caught by a
69
+ reviewer, not the build — and for a `short` set in a wrapper component the
70
+ gate never sees (`DerivationDiagram.astro` passing `short` to `<Diagram>`).
71
+
72
+ _Exceptions._ None — a heading or short caption that must exceed the limit
73
+ should not be one; find the shorter label first.
74
+
75
+ ## S2
76
+
77
+ **A part's overview page carries two authored paragraphs: a `<PartBridge>`
78
+ on how the earlier parts lead into this one, then a `<PartScope>` on what
79
+ this part covers.**
80
+
81
+ This applies to every `content/docs/<dir>/index.mdx` a course authors (see
82
+ [structure.md § Part pages](structure.md#part-pages)). Each slot holds exactly
83
+ one paragraph, whose length falls inside the course's word range. The bridge
84
+ comes before the scope. The first part has no earlier part to link from, so
85
+ its bridge is optional; every other part must have one.
86
+
87
+ ```mdx
88
+ import PartBridge from 'explico/components/PartBridge.astro';
89
+ import PartOverview from 'explico/components/PartOverview.astro';
90
+ import PartScope from 'explico/components/PartScope.astro';
91
+
92
+ <PartBridge>
93
+
94
+ Part 1 gave two results for a point that moves under a constant acceleration:
95
+ its velocity and its position at any later time. … So this part needs no new
96
+ law of motion.
97
+
98
+ </PartBridge>
99
+
100
+ <PartScope>
101
+
102
+ This part answers two questions about a projectile launched over level
103
+ ground. The first chapter derives the range … The second chapter finds the
104
+ launch angle that gives the largest range.
105
+
106
+ </PartScope>
107
+
108
+ <PartOverview />
109
+ ```
110
+
111
+ Keep a blank line inside each tag, so MDX parses the paragraph as Markdown and
112
+ its math and `[[key]]` references render.
113
+
114
+ The word range is a course setting, `rules.S2` in `course.settings.json`
115
+ (`CourseConfig.rules`). Each end defaults to 30–150 words; list only what
116
+ differs:
117
+
118
+ ```json
119
+ "rules": {
120
+ "S2": { "bridgeMaxWords": 120, "scopeMaxWords": 120 }
121
+ }
122
+ ```
123
+
124
+ The four keys are `bridgeMinWords`, `bridgeMaxWords`, `scopeMinWords`,
125
+ `scopeMaxWords`.
126
+
127
+ A math span and a `[[key]]` reference count as one word each; a link counts
128
+ by its text; a citation and markup count nothing.
129
+
130
+ _Rationale._ `<PartOverview>` lists a part's chapters and the parts it builds
131
+ on, but a list does not say _why_ the part follows the one before it. A reader
132
+ who lands on a part page from a search result or a link needs to know which
133
+ earlier results this part reuses before the chapter list means anything.
134
+ Without a required slot, an intro drifts into one of two failures: it restates
135
+ the chapter list, or it describes this part as if nothing came before it. A
136
+ separate scope paragraph stops the bridge from turning into a summary of the
137
+ part itself. A length range keeps both paragraphs substantive but short enough
138
+ to read before the chapter list.
139
+
140
+ _How to apply._ In the bridge, name the earlier part and the results from it
141
+ that this part uses, and state what those results now make possible. In the
142
+ scope, state the question this part answers and how its chapters answer it,
143
+ in reading order. Keep prose that fits neither paragraph outside both slots.
144
+
145
+ _Enforcement._ Build-enforced (`gate-part-pages.ts`, `pnpm validate:content`)
146
+ for the presence, count, order, paragraph count, and word range of both
147
+ slots. Review-only for what each paragraph says — the gate cannot tell a
148
+ bridge that links the parts from one that does not.
149
+
150
+ _Exceptions._ None for a part that has an overview page. A part without an
151
+ `index.mdx` is out of scope: overview pages stay opt-in.
152
+
153
+ ## S3
154
+
155
+ **A table is a `<Table id="…">`, never a raw Markdown or HTML table.**
156
+
157
+ A pipe table or a `<table>` element written straight into a lesson renders,
158
+ but it has no number, no anchor, and `[[tab-…]]` cannot reach it. Wrapping it
159
+ gives it all three:
160
+
161
+ ```mdx
162
+ <Table id="kinematics-summary" caption="Position and velocity under constant acceleration.">
163
+
164
+ | Quantity | Formula |
165
+ | --- | --- |
166
+ | $x(t)$ | $x_0 + v_0 t + \tfrac{1}{2} a t^2$ |
167
+
168
+ </Table>
169
+ ```
170
+
171
+ _Rationale._ Every other float on a page — equation, figure, diagram, knowledge
172
+ check — is numbered and citable; a table that is not breaks the reader's
173
+ expectation that "Table 2.3" exists and the author's ability to say "see
174
+ [[tab-kinematics-summary]]". The keyed-float validator never sees a raw table,
175
+ so its header labels, its overflow handling, and its place in the page's float
176
+ count are all skipped too.
177
+
178
+ _How to apply._ Wrap the table in `<Table id="…" caption="…">` with a blank
179
+ line inside each tag. The `id` is kebab-case and unique on the page; the
180
+ `caption` is the full sentence shown above the table.
181
+
182
+ _Enforcement._ Build-enforced (`raw-floats-validate.ts`, `pnpm
183
+ validate:content`) — a warning per lesson (`raw-table-markdown`,
184
+ `raw-table-html`); `remarkNotation` repeats it at render time for content
185
+ added since the manifest was compiled. It does not fail the build.
186
+
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.
190
+
191
+ ## S4
192
+
193
+ **A `<Diagram>` carries an `id` and sits where diagram numbering can see it.**
194
+
195
+ Diagram numbering scans the top level of a lesson and the bodies of
196
+ `<Memo>`, `<ShortNote>`, `<Table>`, and `<Figure>`. A `<Diagram>` with no `id`,
197
+ or one nested anywhere else (inside `<CompactExample>`, say), is drawn but
198
+ never numbered, and `[[dia-…]]` cannot reach it.
199
+
200
+ ```mdx
201
+ <!-- Numbered, citable. -->
202
+ <Diagram id="velocity-dependencies" caption="What the velocity depends on" … />
203
+
204
+ <!-- Drawn, but invisible to numbering. -->
205
+ <CompactExample>
206
+ <Diagram caption="…" … />
207
+ </CompactExample>
208
+ ```
209
+
210
+ _Rationale._ The same reason as S3: a float the reader can see but the author
211
+ cannot cite is a hole in the reference system. Nesting is the subtler case —
212
+ the diagram *looks* fine on the page, and only the missing number gives it
213
+ away.
214
+
215
+ _How to apply._ Give the diagram an `id`. Move it to the top level, or into a
216
+ `<Memo>`/`<ShortNote>`, or wrap it in `<Figure id="…">` so the figure carries
217
+ the identity.
218
+
219
+ _Enforcement._ Build-enforced (`raw-floats-validate.ts`, `pnpm
220
+ validate:content`) — a warning per lesson (`diagram-unkeyed`,
221
+ `diagram-nested`); repeated by `remarkNotation` at render time. It does not
222
+ fail the build.
223
+
224
+ _Exceptions._ A purely decorative diagram that nothing will ever cite may stay
225
+ unkeyed; say so in a comment next to it.
226
+
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
+ ## S6
265
+
266
+ **Headings descend one level at a time: a lesson body starts at `##` and
267
+ never skips a level on the way down.**
268
+
269
+ The page title (`frontmatter.title`) is the lesson's `<h1>`, so the body's
270
+ first heading is a `##`, and a `#` in the body is a second title. From there
271
+ each heading is at most one level deeper than the one before it: `##` → `###`
272
+ → `####`, never `##` → `####`. Going back up is free — a `####` may be
273
+ followed by a `##`. A knowledge check's heading (`<AssessmentSet />`) and a
274
+ worked-example set's (`<CompactExamples>`) are a `##` here, since that is what
275
+ each renders.
276
+
277
+ ```mdx
278
+ <!-- A skip: the #### has no ### above it. -->
279
+ ## Two ways to name the same point
280
+ The same point has a Cartesian and a polar name.
281
+ #### From Cartesian to polar
282
+
283
+ <!-- One level at a time. -->
284
+ ## Two ways to name the same point
285
+ The same point has a Cartesian and a polar name.
286
+ ### From Cartesian to polar
287
+ ```
288
+
289
+ _Rationale._ A heading's level is its place in the outline, not its type
290
+ size. A skipped level leaves a subsection with no section above it: the
291
+ numbering has to invent a `0` for it (`1.1.1.0.1`), "On this page" indents it
292
+ under nothing, and a screen reader's heading list — the way many readers move
293
+ through a page — reports a level that does not exist.
294
+
295
+ _How to apply._ Pick the level from what the heading belongs under, never
296
+ from how large it should look. When the material near the bottom of the
297
+ outline needs more structure, do not open deeper headings to get it: see
298
+ [structure.md § Structure from the bottom up](structure.md#structure-from-the-bottom-up).
299
+
300
+ _Enforcement._ Build-enforced (`gate-heading-structure.ts`, `pnpm
301
+ validate:content`, `heading-skip`) over a lesson's outline — the top-level
302
+ headings the section numbers count. A heading inside a component's children
303
+ (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.
305
+
306
+ _Exceptions._ None in the rule; waive one place with
307
+ `{/* explico-allow S6: reason */}` above the heading.
308
+
309
+ ## S7
310
+
311
+ **A heading is followed by body content, never directly by another heading.**
312
+
313
+ Between a heading and the next one — whatever their levels — comes body
314
+ content: at least a sentence on what the section is about, or a figure, a
315
+ table, an equation. A comment renders nothing and does not count.
316
+ `<AssessmentSet />` and `<CompactExamples>` count as headings here, because
317
+ each draws an `<h2>` of its own (the kind's badge, its number and its topic):
318
+ no heading goes directly above either.
319
+
320
+ ```mdx
321
+ <!-- Two titles in a row: "Try it", then the set's own heading. -->
322
+ ## Try it
323
+
324
+ <CompactExamples id="coordinate-conversions" short="Calculation examples between coordinate systems">
325
+
326
+ <!-- A section heading with no body under it. -->
327
+ ## Two ways to name the same point
328
+ ### From Cartesian to polar
329
+
330
+ <!-- The section says what it is about before it subdivides. -->
331
+ ## Two ways to name the same point
332
+ The same point has a Cartesian and a polar name; each converts to the other.
333
+ ### From Cartesian to polar
334
+ ```
335
+
336
+ _Rationale._ A heading introduces the text below it. Two in a row give the
337
+ reader two labels and nothing to read; the first says nothing of its own,
338
+ and a collapsed section with only a heading inside it folds away nothing.
339
+ The engine already names a knowledge check and a worked-example set, so a
340
+ heading written above either repeats a title the reader sees next.
341
+
342
+ _How to apply._ Write a sentence under a heading that has subsections, on
343
+ what the section covers. Drop a heading that only announces the block below
344
+ it ("Try it" above worked examples, "Check yourself" above a knowledge
345
+ check): the block's own title does that.
346
+
347
+ _Enforcement._ Build-enforced (`gate-heading-structure.ts`, `pnpm
348
+ validate:content`, `heading-adjacent`) over a lesson's outline, reported at
349
+ the first heading of the pair. A memo, a figure and every other component
350
+ count as body content.
351
+
352
+ _Exceptions._ None in the rule; waive one place with
353
+ `{/* explico-allow S7: reason */}` above the first heading.
354
+
355
+ ## S8
356
+
357
+ **Use a `<Memo>`, not a Starlight aside, for content that deserves an anchor,
358
+ a caption, or collapsing.**
359
+
360
+ A Starlight aside — `:::note`, `:::tip`, `:::caution`, `:::danger`, or
361
+ `<Aside>` — is a short callout read in passing. It renders in the aside tier
362
+ (a left rule and a tint, Starlight's icon per type kept), but it has no
363
+ number, no link, and it never folds. A `<Memo>` is the same aside tier with
364
+ all three: a letter and a chain icon a reader can link to, a caption shown on
365
+ its bar and in its `[[memo-…]]` preview, and a collapsed state that
366
+ remembers the reader's choice.
367
+
368
+ ```mdx
369
+ <!-- A passing remark: an aside is right. -->
370
+ :::note
371
+ Air resistance is ignored throughout this part.
372
+ :::
373
+
374
+ <!-- A derivation a reader may skip, and a later lesson may cite: a memo. -->
375
+ <Memo id="range-check" short="Checking the range formula" caption="…">
376
+ …
377
+ </Memo>
378
+ ```
379
+
380
+ The icon is Starlight's default for the type (note → `information`, tip →
381
+ `rocket`, caution → `warning`, danger → `error`). Name any Starlight
382
+ built-in icon to replace it (`:::note{icon="heart"}`, `<Aside icon="heart">`),
383
+ or leave the value empty to drop it (`:::note{icon=""}`, `<Aside icon="">`).
384
+ `note` and `tip` take the course accent; `caution` and `danger` keep a
385
+ warning and a danger hue.
386
+
387
+ _Rationale._ An aside that grows into a derivation, or that another lesson
388
+ wants to point at, has outgrown its container: nothing can link to it, and a
389
+ reader who has already understood it cannot fold it away. Choosing the memo
390
+ up front keeps the reference system whole.
391
+
392
+ _How to apply._ Ask whether anyone would cite, skip, or return to the block.
393
+ If so, make it a `<Memo id="…">`; if it is one or two sentences read once,
394
+ keep the aside.
395
+
396
+ _Enforcement._ Review-only — no check can tell a passing remark from a
397
+ derivation that deserves a number.
398
+
399
+ _Exceptions._ None.
400
+
401
+ ## S9
402
+
403
+ **A float’s short caption names it, and its long caption goes on from that
404
+ name instead of restating it; a figure holds no running text, and a lab’s
405
+ instructions go in its tools.**
406
+
407
+ A figure's, a table's or a diagram's `short` caption is its name wherever the
408
+ page names it: the bold title that opens its caption line, a folded
409
+ section's "Hidden here", a `<Fold>`'s bar, a copied link and the "Figures and
410
+ tables" page ([components.md § The caption pair](components.md#the-caption-pair)).
411
+ Printed as the title, it is read right before the long caption, so the long
412
+ caption continues from it, as a journal's figure legend does ("Fig. 1 |
413
+ Title. Description"), instead of saying the same thing twice. Each part of a
414
+ figure's text has one job:
415
+
416
+ - the short caption names the float;
417
+ - the long caption says what it shows — its subject, its marks and symbols,
418
+ the case shown with its parameters — and may state the one thing to
419
+ notice;
420
+ - the argument drawn from the figure is lesson text, after the figure;
421
+ - what to do with a lab's controls is the first line of its tools, never the
422
+ caption.
423
+
424
+ ```mdx
425
+ <!-- Restated: the title says "sampled flight data", then the caption does. -->
426
+ <Table id="projectile-telemetry" short="Sampled flight data"
427
+ caption="Flight data sampled at eleven times: position, velocity, …">
428
+
429
+ <!-- Goes on from the title. -->
430
+ <Table id="projectile-telemetry" short="Sampled flight data"
431
+ caption="Position, velocity, acceleration and specific mechanical energy through one flight, from launch to landing.">
432
+ ```
433
+
434
+ _Rationale._ With the short caption printed as the caption line's title, a
435
+ long caption that opens with the same words reads as a stutter. A caption
436
+ that carries the argument hides it where a reader of the text does not look,
437
+ and one that tells the reader to move a slider prints that instruction on a
438
+ page with no slider on it.
439
+
440
+ _How to apply._ Write the `short` first: the name a reader would use for the
441
+ float ("Sampled flight data"). Then read the caption line as it prints, the
442
+ title and the long caption together, and cut what the title already says.
443
+ Move a sentence that argues into the paragraph after the float, and a
444
+ sentence that says what to do with a lab's controls into its tools.
445
+
446
+ _Enforcement._ Review-only — no check can tell a caption that restates its
447
+ title from one that builds on it.
448
+
449
+ _Exceptions._ A float that needs no name of its own may go without a
450
+ `short`; the "Figures and tables" page then lists it by its long caption,
451
+ muted.