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
@@ -33,6 +33,1156 @@ required config block throws with the section of this file to read.
33
33
 
34
34
  ---
35
35
 
36
+ ## 0.11.0 → 0.11.1
37
+
38
+ **No course changes required.** This release has one fix, in the engine's
39
+ stylesheet. A label in the navigation or the "On this page" panel that does
40
+ not fit now wraps onto more lines instead of scrolling sideways, and that
41
+ includes a panel the reader resized. No authoring rule, `CourseConfig` field,
42
+ Astro wiring, course script or CLI command changes. The manifest schema stays
43
+ v24.
44
+
45
+ ### 1. Update the dependency
46
+
47
+ ```bash
48
+ pnpm add -D explico@0.11.1
49
+ ```
50
+
51
+ **Coming from 0.8.0:** follow the "Coming from 0.8.0" note of "0.10.0 →
52
+ 0.11.0" below, and install 0.11.1 wherever it says to install 0.11.0.
53
+
54
+ ### 2. Only if your course CSS or browser tests reach into the side panels
55
+
56
+ ```bash
57
+ rg -n 'data-(navigation|contents)-resized|\.sidebar-pane|\.right-sidebar|layout-(navigation|page-sidebar)-content' src content tests
58
+ ```
59
+
60
+ No hit means nothing to do. Otherwise:
61
+
62
+ - **A resized panel's labels** have `white-space: normal` (it was `nowrap`),
63
+ and the panel never scrolls sideways. A test that asserted `nowrap`, or
64
+ `scrollWidth > clientWidth` after narrowing a panel, now asserts that the
65
+ labels wrap and stay inside the panel.
66
+ - **`.sidebar-pane`** has `overflow-x: hidden` at every width. The resized
67
+ "On this page" panel no longer sets `overflow-x: auto` or
68
+ `scrollbar-width: thin`, so it hides its scrollbar, as Starlight does by
69
+ default.
70
+ - **In a resized "On this page" panel**, the column
71
+ (`.right-sidebar-panel .sl-container`) fills the panel. Its width used to
72
+ come from the navigation's `--sl-sidebar-width`. The default,
73
+ shrink-to-fit rail is unchanged.
74
+ - **Word breaking.** Both panels' labels take `overflow-wrap: break-word`
75
+ and `hyphens: auto`, which replaces Starlight's `overflow-wrap: anywhere`
76
+ on the contents links. To break words differently, set these properties
77
+ on `.layout-navigation-content`, `.layout-page-sidebar-content` and
78
+ `.layout-page-sidebar-content a` in the course CSS.
79
+ - **Screenshots** of a narrowed panel need re-recording.
80
+
81
+ ### 3. Verify
82
+
83
+ ```bash
84
+ pnpm validate:content # run twice: the first run regenerates build/resolution/*
85
+ pnpm content check --changed
86
+ astro check
87
+ astro build
88
+ ```
89
+
90
+ Then run the course's browser tests, if it has any.
91
+
92
+ ## 0.10.0 → 0.11.0
93
+
94
+ **Course changes are required.** Figures and diagrams no longer compact.
95
+ `disclosure.diagrams`, `<Diagram defaultOpen>` and `<Diagram rememberState>`
96
+ are removed, and each one fails the build until you delete it (steps 2–3).
97
+ The rest is additive or affects only readers: `<Fold>`, a lab's `tools`
98
+ slot and live caption end, the short caption shown in the caption line (rule
99
+ S9, a guide), the "Figures and tables" page, unnumbered `[card=meta]`
100
+ sections, and explanation cards that pin, dock, drag and are remembered.
101
+ Nothing in `astro.config.mjs` changes. The manifest schema moves from v23 to
102
+ v24.
103
+
104
+ **Coming from 0.8.0, the last version on npm before this one.** 0.9.0 and
105
+ 0.10.0 were tagged but never published to npm, so a course that installs
106
+ from npm goes from 0.8.0 straight to 0.11.0. Install 0.11.0 once (step 1).
107
+ Then apply the course changes of "0.8.0 → 0.9.0" and "0.9.0 → 0.10.0" below,
108
+ oldest first, and skip their "Update the dependency" steps. Finish with this
109
+ section. Three steps of the 0.10.0 section read differently at 0.11.0. Apply
110
+ them in this form:
111
+
112
+ - Its step 3 puts `diagrams: { defaultOpen: true }` in `disclosure`, and its
113
+ step 4 adds `diagrams` to the settings schema. Leave `diagrams` out: it now
114
+ fails the build (step 2 below). `disclosure.figures` now sets whether a
115
+ lab's tools start open, not whether a figure starts compacted.
116
+ - Its step 14 describes the compaction markup of a figure and a diagram
117
+ (`.float-caption__toggle`, "a compacted float keeps the caption line").
118
+ That markup is gone (step 9 below).
119
+ - Its step 5 lists the diagnostic groups for `compile-manifest.ts`. Add
120
+ `folds` to them as well (step 4 below).
121
+
122
+ Work in this order: steps 1–5 fix the wiring, so that `pnpm validate:content`
123
+ runs; steps 6–8 are content, and steps 7–8 are conditional; steps 9–11 apply
124
+ only to a course whose own CSS, tests, code or stored keys reach into the
125
+ engine; step 12 is optional; step 13 records the content changes; step 15 is
126
+ the check.
127
+
128
+ ### 1. Update the dependency
129
+
130
+ ```bash
131
+ pnpm add -D explico@0.11.0
132
+ ```
133
+
134
+ ### 2. Delete `disclosure.diagrams` (the build fails until you do)
135
+
136
+ A diagram no longer folds by itself, so it has no default. `normalizeDisclosure`
137
+ throws on the key and names the edit to make, which stops both
138
+ `pnpm validate:content` and `astro build`. Delete `diagrams` from the
139
+ `disclosure` block of `content/course.settings.json`, or from
140
+ `CourseConfig.disclosure` in `content/course.config.ts`, wherever the course
141
+ keeps it.
142
+
143
+ `disclosure.figures` stays, but it now means something else: whether a
144
+ **lab's tools** start open (the `tools` slot, step 12). A figure's picture
145
+ and caption always show. A course that set `figures: { defaultOpen: false }`
146
+ to start its figures compacted now starts every lab's tools folded instead.
147
+ Delete the setting unless that is what you want. To let a reader fold a
148
+ figure away, put it in a `<Fold>` (step 12) or a collapsible section.
149
+
150
+ New and optional: `disclosure.folds: { defaultOpen }`, whether a `<Fold>`
151
+ starts open. The default is closed.
152
+
153
+ **Only if the course describes its settings in a JSON Schema** (the
154
+ playground's `course.settings.schema.json`, where `disclosure` has
155
+ `additionalProperties: false`), remove `diagrams`, add `folds` and
156
+ re-describe `figures`:
157
+
158
+ ```json
159
+ "folds": {
160
+ "title": "Folds",
161
+ "description": "A `<Fold>` folds like a memo, with no marker, letter or frame: a stretch of content — most often a figure or diagram — a reader can fold away. Default: closed.",
162
+ "$ref": "#/definitions/initialState"
163
+ },
164
+ "figures": {
165
+ "title": "A lab's tools",
166
+ "description": "Whether a lab's tools (the sliders, fields and live equation after a `<Figure>`'s caption) start open. The figure's picture and caption always show. Default: open.",
167
+ "$ref": "#/definitions/initialState"
168
+ }
169
+ ```
170
+
171
+ The `rules` and `courseRules` properties `$ref` the schemas in
172
+ `node_modules/explico/dist/rules/`, so rule S9 (step 6) needs no edit there.
173
+
174
+ ### 3. Delete `defaultOpen` / `rememberState` from every `<Diagram>`
175
+
176
+ ```bash
177
+ rg -n -U '<Diagram\b[^>]*\b(defaultOpen|rememberState)\b' content src
178
+ ```
179
+
180
+ Both props are removed. In a lesson, each one fails `pnpm validate:content`
181
+ (`dia-removed-prop`). A `<Diagram>` rendered by a wrapper component throws
182
+ when its page builds, and the manifest scan cannot see inside the wrapper.
183
+ Check every wrapper `.astro` file, including one that passes its own props
184
+ through to `<Diagram>`. To let a reader fold a diagram away, put it in a
185
+ `<Fold>` (step 12) or a collapsible section.
186
+
187
+ `<Figure defaultOpen>` and `<Figure rememberState>` stay. They now set that
188
+ figure's lab tools (step 12), and they do nothing on a figure without a
189
+ `tools` slot.
190
+
191
+ ### 4. `scripts/compile-manifest.ts`: add the `folds` group
192
+
193
+ The manifest has a new diagnostics group, `diagnostics.folds`
194
+ (`fold-unnamed`, step 12). Add it to both the warnings loop and the errors
195
+ loop, next to `examples`:
196
+
197
+ ```ts
198
+ const { /* … */ examples, folds, knowledge, rawFloats, config, waivers } =
199
+ manifest.diagnostics;
200
+
201
+ // In BOTH loops over [['equations', equations], …]:
202
+ ['examples', examples],
203
+ ['folds', folds],
204
+ ['rawFloats', rawFloats],
205
+ ```
206
+
207
+ Without it, a `<Fold>` with nothing to name its bar does not fail
208
+ `validate:content`, though the page build still fails on it. `content check`,
209
+ `content diagnostics` and `content status` read the group already.
210
+
211
+ ### 5. The manifest schema is now v24
212
+
213
+ It was v23 in 0.10.0. `pnpm validate:content` rewrites `build/manifest.json`,
214
+ so this step matters only for code that reads the manifest or pins its
215
+ version:
216
+
217
+ - **v24**: `disclosure` loses `diagrams` and gains `folds`, and
218
+ `diagnostics.folds` is new. When the course has a top-level
219
+ `content/docs/figures-and-tables.mdx`, the Reference group in `sidebar` also
220
+ lists "Figures and tables". The item has the same shape as the others.
221
+
222
+ ### 6. Captions: rule S9, and the short caption that now shows
223
+
224
+ A float's `short` caption now opens its caption line as a bold title:
225
+ "**Sampled flight data.** Position, velocity, …". It also names the float in
226
+ a folded section's "Hidden here" line, in a copied link, and on the "Figures
227
+ and tables" page. Nothing fails, but two kinds of float now read badly:
228
+
229
+ 1. **A long `caption` that restates the `short` one reads twice**, for
230
+ example "**Sampled flight data.** Sampled flight data: position, …". Rule
231
+ S9 (`structure-rules.md#s9`) says the long caption continues from the
232
+ short caption's name. It says what the float shows — its subject, its
233
+ marks and symbols, the case shown — and at most the one thing to notice.
234
+ 2. **A figure, table or diagram without `short`** has no title, and the
235
+ "Figures and tables" page lists it by its long caption, greyed out. Rule
236
+ S9 and `components.md` now ask for a `short` on every one. Find them:
237
+
238
+ ```bash
239
+ rg -n -U --pcre2 '<(Figure|Table|Diagram)\b(?![^>]*\bshort=)[^>]*>' content/docs
240
+ ```
241
+
242
+ Rule S9 also moves text out of figures. A figure holds no running text. The
243
+ argument drawn from a figure is lesson text after it, and a lab's
244
+ instructions are the first line of its tools (step 12). Rule S9 is a guide:
245
+ review-only, on `build/rules-in-effect.md` and `pnpm content rules`. Switch it
246
+ off with `rules.S9.enabled: false` if it does not fit the course.
247
+
248
+ ### 7. Only if a lesson has a `[card=meta]` section: section numbers change
249
+
250
+ ```bash
251
+ rg -n '^#{2,6} .*\[card=meta\]' content/docs
252
+ ```
253
+
254
+ A `[card=meta]` section is now unnumbered, like References, and so is every
255
+ heading inside it. The sections after it no longer count it: in a chapter
256
+ that opens with one, § 1.2.2 becomes § 1.2.1. Float, check and example
257
+ numbers do not change.
258
+
259
+ - `[[sec-…]]` references print from the manifest and need no edit. A
260
+ reference to the meta section itself now prints its title in quotes
261
+ ("What this chapter uses"), since the section has no number.
262
+ - Prose, captions and tests that type a section number, and number-form
263
+ fragments (`#sec-1.2.2`) past the meta section, now point at a different
264
+ section. Cite the section by key, or update the number.
265
+ - If the section should keep its number, drop the `=meta` (a plain `[card]`
266
+ accent card is numbered) or make it an aside (`:::note[Title]`).
267
+
268
+ Record the renumbering in `content/CHANGELOG.md` (step 13), because readers
269
+ see new numbers.
270
+
271
+ ### 8. Only if the course has its own `Fold` component or a `figures-and-tables` page
272
+
273
+ ```bash
274
+ rg -n '<Fold\b|import Fold\b' content src
275
+ ls content/docs/figures-and-tables.* 2>/dev/null
276
+ ```
277
+
278
+ - **`<Fold>` is now an engine name.** A hit that imports
279
+ `explico/components/fold/Fold.astro` is the engine's own component; any
280
+ other `Fold` is the course's. The manifest reads every `<Fold>` in a
281
+ lesson as the engine's, whatever it is imported from. The equations and
282
+ floats inside it are numbered in the lesson's sequence, and a `<Fold>` with
283
+ no `label` and no numbered float inside fails `fold-unnamed`. To keep the
284
+ old behaviour, import the course's own component under another name
285
+ (`import CourseFold from …`, `<CourseFold>`).
286
+ - **A top-level `content/docs/figures-and-tables.mdx`** (or `.md`) now adds
287
+ "Figures and tables" to the sidebar's Reference group. If the course
288
+ already has a page at that path for something else, rename it, or make it
289
+ the float index (step 12).
290
+
291
+ ### 9. Only if your course CSS, browser tests or scripts reach into engine markup
292
+
293
+ ```bash
294
+ rg -n 'float-caption__toggle|keyed-figure__details|diagram__details|float-compact|--float-toggle-|diagram-toolbar|diagram-(expand|shrink)-(button|icon)|data-diagram-(details|body|toolbar)|block-badge__icon|table-overflow-expand-btn|keyed-table__caption--paginated|data-panel-pin|citation-panel-pin|notation-popup-close|[0-9] questions|Link copied|Hidden here' src content tests
295
+ ```
296
+
297
+ - **Figure and diagram.** Neither one compacts. `.float-caption__toggle`,
298
+ `.keyed-figure__details`, `.diagram__details`, `.float-compact`,
299
+ `.float-compact__summary` and the `--float-toggle-size`,
300
+ `--float-toggle-gutter` and `--float-toggle-room` tokens are gone. A
301
+ figure's picture is `.keyed-figure > .keyed-figure__body`. The caption line
302
+ can open with `b.float-caption__title` (the short caption) and end with a
303
+ `.float-tools` row of boxed `.float-tool` buttons. A `<Figure>`'s caption
304
+ line ends with `span.float-caption__state[data-figure-state]`, outside
305
+ `.float-caption__text`. Print shows a figure's picture and caption, never
306
+ a lab's tools, and never a "compacted" note.
307
+ - **A diagram's widen button** is now
308
+ `figcaption .float-tools > [data-diagram-expand]`, and shows only while the
309
+ graph is wider than the column. `.diagram-toolbar`, `.diagram-expand-button`,
310
+ `.diagram-expand-icon` and `.diagram-shrink-icon` are gone.
311
+ - **Table.** The widen button ↔ and the fold button sit together in the
312
+ caption line's `.float-tools`. The fold toggle reads ⌃ / ⌄ (it read ▴ / ▾),
313
+ keeps `.keyed-table__pagination-toggle`, and is no longer a
314
+ `.table-overflow-expand-btn`. `.keyed-table__caption--paginated` is gone.
315
+ The "… N more rows …" label is centred on the part of the table on screen.
316
+ - **Knowledge check and example set.** The icon is `.block-heading__icon`,
317
+ before `.block-heading__title` (it was `.block-badge__icon`, inside the
318
+ badge). The badge (`.block-badge`: the word and `.block-heading__number`)
319
+ sits in the card's top-right corner. A check's score is empty until the
320
+ first answer, then reads "2 of 3 correct" under the questions. The
321
+ "3 questions" line is gone.
322
+ - **Explanation cards** (notation, `[[key]]` terms, citations,
323
+ abbreviations, and equation, table, figure, diagram and section
324
+ references) are all clones of `HoverPanel.astro`'s `<template>`, run by one
325
+ card manager. The header row holds the grip `[data-card-grip]` ("Move
326
+ card"), Side `[data-card-side]`, Pin `[data-card-pin]` (`aria-pressed`) and
327
+ × `[data-card-close]`. A note line `[data-card-note]` sits under it. A
328
+ dragged card has `data-card-mode="moved"`. `[data-panel-pin]`,
329
+ `.citation-panel-pin` and `.notation-popup-close` are gone.
330
+ `.notation-popup[data-popup-key="…"]` still finds a notation card. For
331
+ behaviour tests:
332
+ - A click or tap on a trigger pins its card and never navigates. The
333
+ card's "Go to …" link navigates, and ends in ↑ / ↓ (same page), → (another
334
+ page) or ↗ (a citation's source); every one used to end in ↗. A reference
335
+ whose target is already fully on screen still highlights it and
336
+ navigates.
337
+ - A click inside a card never closes it. A hovered card no longer closes a
338
+ pinned one, and several cards can be open at once. Escape closes the
339
+ newest card first. A `[[key]]` term shows no browser tooltip.
340
+ - A page restores the cards a reader kept on it, from local storage. A
341
+ fresh browser context starts empty, but a test that pins a card and then
342
+ reloads or goes back sees the card again.
343
+ - **Copy link and "Hidden here".** The confirmation names what was copied:
344
+ "Link copied · Figure 2.1.1 · Projectile trajectory explorer", or a
345
+ section's, check's or example set's title. Where the browser has
346
+ `ClipboardItem`, a `text/html` link goes on the clipboard next to the URL. A
347
+ folded section's line names its floats first: "Hidden here: **Figure
348
+ 2.1.1** Projectile trajectory explorer, 2 paragraphs". A heading's chain
349
+ icon has the title "Copy link" where a click copies.
350
+ - **Layout.** The header is 3rem tall at every width (`--sl-nav-height`), with
351
+ a 2rem search box. Check course header content sized for 3.5rem or 4rem.
352
+ Headings are smaller: `global.css` sets `--sl-text-h1`…`--sl-text-h5`, and
353
+ course CSS that sets them wins. The navigation and "On this page" rail
354
+ cover a card that reaches them. Accent and meta cards have less padding, a
355
+ lesson's first block adds no top margin, and Previous / Next stay side by
356
+ side down to a 26rem column. The notation panel's help text is inside the
357
+ panel. Re-record screenshots.
358
+ - **A figure's preview card** shows a still copy of its picture, a lab's
359
+ plot included (controls disabled, ids prefixed). A lab no longer needs
360
+ `previewable={false}`.
361
+
362
+ ### 10. Only if course code imports engine internals
363
+
364
+ - `components/hover-panel.ts` (`createHoverPanel`, `claimActiveSurface`,
365
+ `releaseActiveSurface`), `components/popup-stack.ts` and
366
+ `components/disclosure/float-compaction.ts` are gone. A layer of the
367
+ course's own registers a `CardSource` with `registerCardSource`. An overlay
368
+ that must clear the hovered cards calls `closeTransientCards()`. Both come
369
+ from `explico/components/card-manager.ts`.
370
+ - `countHiddenContent` in `components/starlight/section-summary.ts` is gone.
371
+ Use `collectHiddenContent`, then `hiddenContentParts`.
372
+ - `PreferenceStore` gains an optional `remove(key)`. A course's own store
373
+ still fits the interface. Implement `remove` so that closing a page's last
374
+ card forgets the page.
375
+
376
+ ### 11. Reader-side state
377
+
378
+ A figure's and a diagram's remembered compaction (`figure:<id>`,
379
+ `diagram:<id>`) is no longer read, so a returning reader sees every figure
380
+ and diagram open. The new keys are `figure-tools:<figure id>` (a lab's
381
+ tools), `section:fold-…` (a `<Fold>`), `explico:cards:<page path>` (the cards
382
+ kept on a page) and `explico:layout:keep-article-left`. You need to add them
383
+ only if the course clears preference keys by name.
384
+
385
+ ### 12. Optional: features to adopt
386
+
387
+ - **`<Fold>`** (`explico/components/fold/Fold.astro`) lets a reader fold away
388
+ a stretch of the lesson where it stands, most often one figure, diagram or
389
+ table. It has no heading, number, marker or frame:
390
+
391
+ ```mdx
392
+ import Fold from 'explico/components/fold/Fold.astro';
393
+
394
+ <Fold>
395
+ <Diagram id="launch-angle-dependency" short="Inputs to the launch-angle search" caption="…" />
396
+ </Fold>
397
+ ```
398
+
399
+ The bar is the `label`, or else the numbers and short captions of the
400
+ numbered floats inside. A fold with neither fails `fold-unnamed`. A float
401
+ the bar names drops its own bold caption title inside the fold. The fold's
402
+ first state comes from `defaultOpen`, or else `disclosure.folds` (closed);
403
+ `rememberState={false}` keeps the default on every visit. The equations
404
+ and floats inside keep their numbers.
405
+ - **A lab's tools.** In a `<Figure>`, the default slot is the picture. Give the
406
+ controls island `slot="tools"`, or wrap the controls in
407
+ `<Fragment slot="tools">`. The tools render after the caption line in a
408
+ tray with a fold button. They start as `defaultOpen` says, or else as
409
+ `disclosure.figures` says, and they never print. The picture and the tools
410
+ are separate islands, so they share their values through the course's own
411
+ store (the playground's `content/labs/useStoredState.ts`). `global.css`
412
+ styles `.lab-tools`, `.lab-instruction` (the tools' first line, saying
413
+ what to do) and a `.lab-shell` in the tray. A figure without the slot
414
+ renders as before.
415
+
416
+ ```mdx
417
+ <Figure id="launch-angle-explorer" short="Range vs. launch angle explorer" caption="…">
418
+ <LaunchAnglePlot client:visible />
419
+ <LaunchAngleTools slot="tools" client:visible />
420
+ </Figure>
421
+ ```
422
+
423
+ - **The caption's live end.** `setFigureState(element, runs)` from
424
+ `explico/components/figure/figure-state` writes the case the plot shows
425
+ at the end of the caption line. `element` is any element of the lab's
426
+ inside the figure. `runs` holds strings, `{ text, italic: true }` and
427
+ `{ sub }` items, never HTML:
428
+ `setFigureState(host, ['Shown for ', { text: 'v', italic: true }, { sub: '0' }, ' = 20 m/s'])`.
429
+ The clause prints with the caption.
430
+ - **Drop `previewable={false}`** from labs. The preview card now copies the
431
+ plot as drawn, and falls back to the caption and number when the plot is
432
+ not drawn yet.
433
+ - **The "Figures and tables" page** is LaTeX's `\listoffigures` and
434
+ `\listoftables` in one. Create `content/docs/figures-and-tables.mdx`:
435
+
436
+ ```mdx
437
+ ---
438
+ title: Figures and tables
439
+ description: Every numbered figure, table and diagram of the course, by part and chapter.
440
+ ---
441
+
442
+ import FloatIndex from 'explico/components/FloatIndex.astro';
443
+
444
+ <FloatIndex />
445
+ ```
446
+
447
+ It lists every numbered figure, table and diagram that has an `id`. A
448
+ diagram drawn by a wrapper component is listed when the lesson imports the
449
+ wrapper as `Diagram` and passes it `id`, `short` and `caption`
450
+ (`<Diagram id="…" short="…" caption="…" />`). That is also how the diagram
451
+ gets a number and a `[[dia-…]]` key.
452
+ - **Cards beside the article, dragged cards and remembered cards** need no
453
+ wiring.
454
+
455
+ ### 13. Record the content changes (rules C1–C3)
456
+
457
+ Steps 2, 6, 7 and 12 edit `content/`. A settings edit is a content change
458
+ too. Add bullets under `## [Unreleased]` in `content/CHANGELOG.md` (rule C1):
459
+ the dropped `disclosure.diagrams`, new and rewritten captions, renumbered
460
+ sections, and new folds or pages. Bump `about.version` per rule C2 when you
461
+ release, and keep it equal to the newest heading (rule C3).
462
+
463
+ ### 14. Fixes that need no action
464
+
465
+ A section, memo or fold that the browser opens itself, for find-in-page or
466
+ a linked fragment, now opens properly and stays open. A reference into a
467
+ closed memo or folded section opens its preview card and marks the fold's
468
+ bar. A figure's preview card shows an SVG sized only by its `viewBox`. A
469
+ touch screen no longer draws a badge icon over its word. A worked example's
470
+ closing display equation loses its stray bottom margin.
471
+
472
+ ### 15. Verify
473
+
474
+ ```bash
475
+ pnpm validate:content # run twice: the first run regenerates build/resolution/*
476
+ pnpm content rules # rule S9 is on the list now
477
+ pnpm content warnings
478
+ pnpm content check --changed
479
+ astro check
480
+ astro build
481
+ ```
482
+
483
+ Then run the course's browser tests, if it has any, and update the selectors
484
+ and texts from step 9. A course with its own gate script that runs these
485
+ stages (explico's is `scripts/agent/verify`) runs that instead.
486
+
487
+ ## 0.9.0 → 0.10.0
488
+
489
+ **Course changes are required.** This release carries four lines of work: the
490
+ rule catalog (per-rule settings and switches, waivers, the rules-in-effect
491
+ page, a course's own rules), the rule restructuring (six rules folded into
492
+ four), accents and styled letters as symbols of their own, and titles and
493
+ containers (outline numbering, one caption pair per float, one collapse
494
+ model, heading discipline, kind badges). Several props are removed with no
495
+ alias period, and several rules now fail the build where they did not.
496
+
497
+ Work in this order: steps 1–6 fix the wiring, so that `pnpm validate:content`
498
+ runs and reports; steps 7–13 clear what it reports; steps 14–16 are
499
+ conditional or optional; step 17 is the check. Where a diagnostic code leads
500
+ to a step, the step names it. The manifest schema moves from v16 to v23 in
501
+ seven bumps (step 6).
502
+
503
+ ### 1. Update the dependency
504
+
505
+ ```bash
506
+ pnpm add -D explico@0.10.0
507
+ ```
508
+
509
+ 0.10.0 was never published to npm. Coming from npm's 0.8.0, install 0.11.0
510
+ instead, and read the "Coming from 0.8.0" note of "0.10.0 → 0.11.0" first.
511
+
512
+ ### 2. `astro.config.mjs`
513
+
514
+ Make these edits; the result below shows all of them together.
515
+
516
+ 1. **Add `remarkAsides` last in `markdown.remarkPlugins`.** Starlight turns
517
+ `:::note` / `:::tip` / `:::caution` / `:::danger` into asides only under its
518
+ own `src/content/docs/`, so in a course's `content/docs/` they rendered as a
519
+ bare `<div>`. `remarkAsides` hands each one to Starlight's `<Aside>`. No
520
+ check fails without it; the asides just stay unstyled. Do **not** add
521
+ `content/docs/` to Starlight's `markdown.processedDirs` instead: every
522
+ heading would get two anchors.
523
+ 2. **Drop the `disclosureDefaults` mapping.** `CourseConfig.disclosureDefaults`
524
+ is gone (step 3). If the file builds a `resolvedCourseConfig = { ...courseConfig,
525
+ disclosureDefaults: { notation, references } }`, delete it and pass
526
+ `courseConfig` itself to both `loadManifest` and `explicoContentRefresh`.
527
+ 3. **Read the heading and References defaults from `courseConfig.disclosure`.**
528
+ `rehypeHeadingAnchors` takes `{ defaultCollapsible: courseConfig.disclosure }`
529
+ (it reads `sections` and `subsections`), and `remarkCitation` takes
530
+ `defaultOpen: courseConfig.disclosure?.references?.defaultOpen === true`.
531
+ A local `defaultCollapsible` object moves into the `disclosure` block of
532
+ step 3.
533
+ 4. **Wire `LayoutPageTitle.astro` as Starlight's `PageTitle` override.** It puts
534
+ the part's name, linked, above every chapter title. Optional, but it is the
535
+ one route to a part page (step 15) that does not depend on the sidebar.
536
+ 5. **Check that `PageSidebar` is `LayoutPageSidebar.astro`.** "On this page"
537
+ lists knowledge checks and worked-example sets only through it.
538
+ 6. **Only for a course deployed under a subpath** (an Astro `base`, such as a
539
+ GitHub Pages project site): pass `base` to `createRemarkNotationOptions` and
540
+ to `remarkAbbreviations`. Without it, the links these plugins write (a
541
+ symbol's glossary card, a cross-lesson reference, an abbreviation) are
542
+ root-relative and 404.
543
+
544
+ ```js
545
+ import remarkAsides from 'explico/reference/remark-asides.mjs';
546
+ import rehypeHeadingAnchors from 'explico/reference/rehype-heading-anchors.mjs';
547
+ import { courseConfig } from './content/course.config.ts';
548
+
549
+ const disclosure = courseConfig.disclosure ?? {};
550
+ const manifest = await loadManifest(courseConfig);
551
+
552
+ // … integrations:
553
+ explicoContentRefresh(courseConfig, manifest),
554
+ starlight({
555
+ // …
556
+ components: {
557
+ Footer: 'explico/components/starlight/LessonFooter.astro',
558
+ PageSidebar: 'explico/components/starlight/LayoutPageSidebar.astro',
559
+ PageTitle: 'explico/components/starlight/LayoutPageTitle.astro',
560
+ // … Header, Sidebar, SocialIcons as before
561
+ },
562
+ }),
563
+
564
+ // … markdown.remarkPlugins:
565
+ [remarkNotation, createRemarkNotationOptions(manifest, { strict: true, base })],
566
+ [remarkCitation, {
567
+ sources: manifest.sources,
568
+ defaultOpen: disclosure.references?.defaultOpen === true,
569
+ }],
570
+ [remarkAbbreviations, { abbreviations: manifest.abbreviations, base }],
571
+ remarkAsides, // last
572
+
573
+ // … markdown.rehypePlugins:
574
+ [rehypeHeadingAnchors, { defaultCollapsible: disclosure }],
575
+ ```
576
+
577
+ **Only if your course overrides `Footer` with its own component** instead of
578
+ `LessonFooter.astro`: read the notation panel's first state from
579
+ `manifest.disclosure.notation.defaultOpen` (it was
580
+ `manifest.disclosureDefaults.notation`), pass `KeyedFloatEnhancer` its new
581
+ `aliases` prop (`manifest.numbering.aliases`, step 11) and `NotationLayer` its
582
+ new `suspended` prop (the page's `notation.suspend`, step 10), as
583
+ `LessonFooter.astro` does.
584
+
585
+ ### 3. `CourseConfig` (`content/course.config.ts`)
586
+
587
+ 1. **`disclosureDefaults` → `disclosure`.** Every collapse default is now one
588
+ block, in the shape of the course settings' `disclosure` block. Move the
589
+ old `disclosureDefaults.notation` / `.references` booleans and the
590
+ `sections` / `subsections` objects from `astro.config.mjs` into it. Every
591
+ key is optional; the values below are the defaults, which match what each
592
+ kind had hardcoded before:
593
+
594
+ ```ts
595
+ disclosure: {
596
+ sections: { collapsible: false, defaultOpen: true }, // ## headings
597
+ subsections: { collapsible: false, defaultOpen: true }, // ### and deeper
598
+ notation: { defaultOpen: false }, // was disclosureDefaults.notation
599
+ references: { defaultOpen: false }, // was disclosureDefaults.references
600
+ knowledgeChecks: { collapsible: true, defaultOpen: true }, // new
601
+ memos: { defaultOpen: false }, // new
602
+ figures: { defaultOpen: true }, // new
603
+ diagrams: { defaultOpen: true }, // new
604
+ workedExamples: { defaultOpen: false }, // new
605
+ tables: { truncateAfter: 6 }, // new: rows shown, half from each end
606
+ },
607
+ ```
608
+
609
+ If the course keeps its settings in a JSON file and spreads it into
610
+ `CourseConfig`, stop splitting `disclosure` off: pass the block through as
611
+ it is. A course that calls `normalizeDisclosureDefaults` switches to
612
+ `normalizeDisclosure` (with `DISCLOSURE_DEFAULTS`); both are exported from
613
+ `explico/compiler/course-config`.
614
+ 2. **Move the deprecated top-level blocks into `rules`.** `operators`,
615
+ `romanLabels` and `abbreviations` still work with a `config-deprecated-key`
616
+ warning; move each `{ allow, deny }` block unchanged to `rules.N8`,
617
+ `rules.N10` and `rules.N14` (rule N8, rule N10, rule N14). Setting both
618
+ the old key and its replacement is an error. The old keys go away in a later
619
+ MINOR.
620
+ 3. **Only if you took `partPages` from `main` before this release:** it is
621
+ `rules.S2` now (`bridgeMinWords`, `bridgeMaxWords`, `scopeMinWords`,
622
+ `scopeMaxWords`), with no alias.
623
+ 4. **Optional new fields**, each with the default that applies when it is left
624
+ out:
625
+ - `numbering`: `{ sections: { show: true, depth: 4 }, floats: "chapter",
626
+ aliases: true }` (step 11).
627
+ - `blockLabels`: `"icon-word"`, or `"icon"` for a knowledge check's and an
628
+ example set's badge without its word (the word stays for a screen
629
+ reader).
630
+ - `rules`: per-rule switches and settings (step 12).
631
+ - `courseRules`: the course's own `X` rules (step 15).
632
+ - `CoursePart.overviewLabel`: renames a part's `Overview` sidebar row.
633
+
634
+ ### 4. Course settings schema (only if your course publishes one)
635
+
636
+ A course that describes its settings in a JSON Schema (the playground's
637
+ `course.settings.schema.json`) points two properties at the schemas
638
+ `pnpm build` now ships, so the rule blocks never need a copy:
639
+
640
+ ```json
641
+ "rules": { "$ref": "../node_modules/explico/dist/rules/rules.schema.json" },
642
+ "courseRules": { "$ref": "../node_modules/explico/dist/rules/course-rules.schema.json" }
643
+ ```
644
+
645
+ Add `numbering`, `blockLabels`, and the new `disclosure` keys
646
+ (`knowledgeChecks`, `memos`, `figures`, `diagrams`, `workedExamples`,
647
+ `tables.truncateAfter`) with the defaults in step 3; the playground's
648
+ [`course.settings.schema.json`](https://github.com/danieltuzes/explico/blob/v0.10.0/playground/content/course.settings.schema.json)
649
+ has each with a title and a description. If you use explico's settings form
650
+ editor (`tools/vscode-settings-editor/` in the explico repository), reinstall
651
+ it from a v0.10.0 checkout (`node tools/vscode-settings-editor/install.mjs`):
652
+ an older build shows `rules` and `courseRules` as text fields reading
653
+ `[object Object]`.
654
+
655
+ ### 5. Course scripts
656
+
657
+ 1. **`scripts/compile-manifest.ts`** — three new diagnostics groups, and the
658
+ rules-in-effect page. Without `examples` in the group lists, an `ex-…`
659
+ error does not fail `validate:content` (the page build still fails on it).
660
+
661
+ ```ts
662
+ import { createRunLog, diagnosticLine } from 'explico/compiler/logging';
663
+ import { writeRulesInEffect } from 'explico/rules/in-effect';
664
+
665
+ const { /* … */ checks, examples, knowledge, rawFloats, config, waivers } =
666
+ manifest.diagnostics;
667
+
668
+ // Add ['examples', examples] to BOTH the warnings loop and the errors loop
669
+ // over [['equations', equations], …, ['checks', checks], …].
670
+
671
+ // The course settings themselves: a deprecated block, a retired rule.
672
+ for (const warning of config) {
673
+ console.warn(log.warning('config', `[${warning.code}] ${warning.message}`));
674
+ }
675
+ // Per-place waivers: a stale one warns; a malformed or invalid one fails.
676
+ for (const warning of waivers.filter((d) => d.severity === 'warning')) {
677
+ console.warn(log.warning('waivers', diagnosticLine(warning)));
678
+ }
679
+ // … and next to the other failures:
680
+ for (const issue of waivers.filter((d) => d.severity === 'error')) {
681
+ fail('waivers', diagnosticLine(issue));
682
+ }
683
+
684
+ // Optional: format math findings with the owning rule's ID,
685
+ // `[N9 prose-notation] file:line: …`.
686
+ const formatMathIssue = (issue: (typeof math)[number]) => diagnosticLine(issue);
687
+
688
+ // The page an agent reads before drafting (step 12).
689
+ writeRulesInEffect(manifest);
690
+ ```
691
+
692
+ 2. **`scripts/content.ts`** — add the `rules` verb: `contentRules(manifest,
693
+ parseRulesFilter({ element, enforcement }))` from `explico/compiler/cli`,
694
+ taking `--element <e>[,<e>]`, `--enforcement script|partial|guide|locked`
695
+ and `--json`. `content status` can print the new `keyedExamples` count.
696
+ 3. **`.gitignore`** — `build/rules-in-effect.md` is regenerated on every run.
697
+ Ignore it unless `build/` is ignored already.
698
+ 4. **Tools that read `content … --json`.** Each diagnostic now carries its
699
+ owner as `rule`. A math finding's `code` is `math:<code>` for every finding
700
+ (`math:prose-notation`, `math:unresolved-glyph`, …), where it used to be
701
+ `math:prose` or `math:structure` for code-less ones.
702
+ `MathGateDiagnostic.code` is required in the type.
703
+ 5. **Code that imports engine internals.** `RuleEntry.retired` is gone
704
+ (`retiredRules()` / `retiredRule(id)` in `explico/rules/catalog`);
705
+ `proseOnly` takes `{ keepMath }`; `gateProseNotation` /
706
+ `gateProseNotationInAssessments` take `{ allowSpelledUnits }`;
707
+ `resolveMathGlyphs` takes `{ suspended }` as a third argument, and an
708
+ `unresolved` item may carry `suspended` or `font`. A lab island imports
709
+ `slotPlaceholder` / `RenderedLabMath` from `explico/reference/lab-math-slot`
710
+ (`render-lab-math` still re-exports both, but pulls the server-only KaTeX
711
+ chain into the browser graph and makes Vite warn about `node:fs`).
712
+
713
+ ### 6. The manifest schema is now v23
714
+
715
+ It was v16 in 0.9.0. `pnpm validate:content` rewrites `build/manifest.json`,
716
+ so this step is only for code that reads the manifest itself:
717
+
718
+ - **v17** (additive): `parts` — every part with its chapters in reading order
719
+ and its overview page; `sidebar[].items` is one mixed list of lesson slugs
720
+ and `{ label, link, attrs? }` entries (`SidebarItem`), and `SidebarGroup` is
721
+ one interface instead of a union; `lessons[].studyMinutes`,
722
+ `parts[].studyMinutes`.
723
+ - **v18** (additive): `rules` (`disabled`, each rule's effective settings);
724
+ `diagnostics.config`; `diagnostics.waivers`; every math-gate finding carries
725
+ a `code`.
726
+ - **v19** (additive): `rules.course`, the course's own rules;
727
+ `MathGateDiagnostic.context` gains `'course'`.
728
+ - **v20**: `numbering`; `lessons[].headings` (`slug`, `text`, `level`,
729
+ `number?`); `lessons[].numbering.prefix`. Under the default chapter scope,
730
+ the per-kind counts in `lessons[].numbering`, which used to carry a part's
731
+ running total, are all 0.
732
+ - **v21**: `lessons[].equations` / `.tables` / `.figures` / `.diagrams` and the
733
+ kinds' `labels` carry `short` and, for a JSX float, `caption`
734
+ (`ManifestLessonFloat`).
735
+ - **v22**: `disclosure` (every default applied) replaces
736
+ `disclosureDefaults`.
737
+ - **v23**: `examples`, `lessons[].examples`, `lessons[].numbering.examples`,
738
+ `diagnostics.examples`; `lessons[].knowledgeChecks[].title`;
739
+ `lessons[].titledBlocks`; `blockLabels`.
740
+
741
+ ### 7. Rename the removed float props (the build fails until you do)
742
+
743
+ Every float is named by one caption pair: a long `caption` and an optional
744
+ `short` caption, LaTeX's `\caption[short]{long}`. The other name props are
745
+ removed outright, and each fails with the edit to make. Find them:
746
+
747
+ ```bash
748
+ rg -n -U '<(Figure|Diagram|Memo|CompactExamples)\b[^>]*\b(title|label|description|summary)=' content src
749
+ ```
750
+
751
+ 1. `<Figure title="X" caption="Y">` → `<Figure short="X" caption="Y">`, or
752
+ drop `title` when X only repeats the caption (`fig-removed-prop`).
753
+ 2. `<Diagram label="X">` → `<Diagram caption="X">`, plus `short` for a long
754
+ caption; `<Diagram title>` fails too (`dia-removed-prop`). Check wrapper
755
+ `.astro` components as well: the manifest scan cannot see inside them, but
756
+ the component throws when it renders.
757
+ 3. `<Memo title="X" description="Y">` → `<Memo short="X" caption="…">`.
758
+ `caption` is required and is now the memo's bar, so write it as bar text;
759
+ `short` is optional (the marker's tooltip). `remarkMemo` fails the old props.
760
+ 4. `<CompactExamples summary="X">` → `<CompactExamples id="…" short="X">`.
761
+ Both are required now (`ex-missing-prop`; `summary` is `ex-removed-prop`):
762
+ `id` is lowercase kebab-case and anchors the set as `#ex-‹id›`; `short` names
763
+ the set's topic ("Speeding up and braking"), since the engine draws the
764
+ "Example" badge and the number before it. `short` no longer defaults to
765
+ "Worked examples", and `hint` has no default.
766
+
767
+ Rule S1 now measures the `short` caption (of a `Figure`, `Diagram`, `Table`,
768
+ `Memo` and `CompactExamples`), a display equation's `\caption{…}` and a
769
+ `ShortNote`'s `title`, and never a long `caption`; a heading's `[collapsed]`
770
+ / `[card]` markers no longer count toward it. A `Figure title` that passed rule
771
+ S1 passes the same way as `short`.
772
+
773
+ ### 8. Assessment titles and labs
774
+
775
+ 1. **Assessment titles name the topic alone.** A knowledge check's heading is
776
+ now the engine's badge ("Knowledge check"), its number, then the assessment
777
+ file's `title`, so a title that repeats the kind reads twice ("Knowledge
778
+ check 1.1.1 Velocity check"). In each `content/assessments/*.json`, drop
779
+ "check" / "Knowledge check" from `title`.
780
+ 2. **A lab carries no title or caption of its own**; the `<Figure>` around it
781
+ names the whole. In a lab inside a `<Figure>`, delete its own heading and
782
+ the `aria-labelledby` that names it, and turn an inner
783
+ `<figure><figcaption>` into a plain element, moving the sentence into the
784
+ `<Figure caption>`. The engine's `.lab-chart-figure` styles are gone; draw
785
+ the chart in a plain `.lab-chart`.
786
+ 3. **Rule V3 is narrowed.** It keeps the hydrated island, the `labMathScope` /
787
+ `renderLabMath` / `\slot{}` seam and labelled controls, and no longer asks
788
+ for an accessible name or description (`ariaLabel` / `ariaDescription` /
789
+ `ariaHidden`) or a `<details>` text alternative: the `<Figure>` caption is
790
+ the name. Nothing in a build changes; keep or drop what you wrote for the
791
+ old rule V3.
792
+
793
+ ### 9. Headings: rule S6 and rule S7 (errors)
794
+
795
+ Both read a lesson's outline, the same top-level headings the section numbers
796
+ count. A heading inside a component and a memo's bar are not in it; a
797
+ `<AssessmentSet />` and a `<CompactExamples>` are, as `##` headings.
798
+
799
+ - **Rule S6** (`heading-skip`): headings descend one level at a time. A lesson
800
+ body starts at `##` (the frontmatter `title` is the page's one `#`, so a `#`
801
+ in the body fails), and `##` → `####` fails; going back up is free. A `###`
802
+ → example set → `####` is a skip too. Fix it by making the heading one level
803
+ deeper than the one before it.
804
+ - **Rule S7** (`heading-adjacent`): a heading is followed by body content,
805
+ never directly by another heading, and a check or an example set counts as a
806
+ heading. Write a sentence between the two, or delete a heading that only
807
+ announces the block under it (`## Check yourself` above `<AssessmentSet />`,
808
+ `## Try it` above `<CompactExamples>`).
809
+
810
+ Otherwise waive one place with `{/* explico-allow S6: reason */}` on the line
811
+ above the heading, or switch a rule off with `rules.S6.enabled: false` /
812
+ `rules.S7.enabled: false`. For parallel items deep in the outline,
813
+ `docs/authoring/structure.md` § Structure from the bottom up recommends run-in
814
+ labels or a description list instead of deeper headings.
815
+
816
+ ### 10. Math and prose: what now fails or warns
817
+
818
+ Run `pnpm validate:content` and work through these by code.
819
+
820
+ 1. **Rule N9, `spelled-unit` (error).** A unit written as a word after a
821
+ number, around `per`, or with `square` / `cubic` / `squared` / `cubed`
822
+ fails, in lesson bodies and assessment `prompt` / `explanation`: "a time
823
+ of 4 seconds" → `$4\ \text{s}$`, "30 degrees above the horizontal" →
824
+ `$30^{\circ}$`, "$x = 3$ meters" → `$x = 3\ \text{m}$`. The message gives
825
+ the math. A unit named on its own ("measured in meters") passes, and rule
826
+ N9's old carve-out for a spelled-out unit in flowing prose is gone. Rewrite
827
+ it, waive a defining sentence with `{/* explico-allow N9: reason */}`, or
828
+ list unit words the course keeps in `rules.N9.allow`.
829
+ 2. **Rule N12, `math-trailing-punctuation` (error).** A `.`, `,`, `;` or `:` is
830
+ never the last visible thing in a `$…$` / `$$…$$` span, or in a row before
831
+ `\\`; move it out after the closing `$`. A trailing decimal point (`5.`) and
832
+ a typed `...` fail too; the decimal-point exception is gone. Dotted notation
833
+ abbreviations (`a.e.`, `i.i.d.`, …) pass; edit that list with
834
+ `rules.N12.allow` / `rules.N12.deny`, waive a place, or switch rule N12 off.
835
+ 3. **Rule N4, `unresolved-glyph` (error): a font-styled letter is its own
836
+ symbol.** `\mathbf{x}`, `\boldsymbol{\beta}`, `\bm{x}`, `\mathcal{N}`,
837
+ `\mathscr{L}`, `\mathfrak{g}`, `\mathbb{E}`, `\mathsf{x}` and `\mathtt{x}`
838
+ no longer borrow the plain letter's card, and `\mathbf{e}` /
839
+ `\boldsymbol{\pi}` no longer pass as Euler's number or π. Give each its own
840
+ entry (`latex: \mathbf{x}`) or an `\explain{key}{\mathbf{x}}` wrap — in
841
+ lessons, notation formulas, assessments and labs alike. `\mathit` /
842
+ `\mathnormal` still share the plain letter's card, and `\mathbb{N,Z,R,C}`
843
+ stay reserved.
844
+ 4. **Rule N22, `accent-explained` (warning): an accent or mark is an
845
+ operator.** A decoration (`\hat`, `\bar`, `\tilde`, `\dot`, `\vec`,
846
+ `\overline`, a superscript `'`, `^*`, `^\dagger`, …) needs one of: an entry
847
+ on `rules.N22.allow` (empty by default), an operator notation entry
848
+ (`latex: \hat{}`, `{}'`, `{}^{*}`), or a whole-form entry
849
+ (`latex: \hat{\beta}`). A page where a mark is not an operator declares it
850
+ in its frontmatter (`notation.suspend: [{ operator: "'", reason: … }]`);
851
+ every decorated form of it on that page then needs its own entry or a
852
+ whole-form `\explain`, or the build fails with
853
+ `suspended-operator-unresolved`. Two consequences for existing notation:
854
+ - A marked entry no longer lends its card to the bare letter: with only
855
+ `latex: b'` (or `\theta^{*}`), a bare `b` elsewhere on the page now needs
856
+ its own entry, or rule N4's gate fails with `unresolved-glyph`.
857
+ - A whole-form match now wraps the decoration too
858
+ (`\explain{k}{\hat{\beta}}`, not `\hat{\explain{k}{\beta}}`), and the wrap
859
+ for `\boldsymbol`, `\bm`, `\bold`, `\Bbb`, `\frak`, `\mathsf`, `\mathtt`
860
+ and `\mathnormal` lands around the command. Only snapshot tests of
861
+ rendered HTML notice.
862
+ 5. **Rule N8 and rule N10 now read labelled display equations**
863
+ (`operator-explained`, `roman-label-explained`, both warnings). A
864
+ `\label{eq:…}` used to hide the whole equation from both scans, so new
865
+ findings may appear. Explain the operator or label, or list it under
866
+ `rules.N8.allow` / `rules.N10.allow`.
867
+ 6. **Rule N5: hand-typed numbers are now wrong numbers.** Every float number
868
+ changed (step 11). Prose that types "Equation 2.4" instead of citing
869
+ `[[eq-…]]` now names the wrong equation; cite it by key.
870
+ 7. `x^\prime` no longer fails as an unresolved `\prime`.
871
+
872
+ ### 11. Numbering: every number a reader sees changed
873
+
874
+ 1. **Float numbers are chapter-scoped.** A display equation, table, figure,
875
+ diagram, knowledge check and example set is numbered
876
+ `‹part›.‹chapter›.‹n›`, and each kind restarts in every chapter:
877
+ `Figure 2.1` is now `Figure 2.2.1`. `[[…]]` references need no edit, since
878
+ they print from the manifest. `numbering.floats: "part"` keeps the old
879
+ `‹part›.‹n›` scheme.
880
+ 2. **The positional anchor of an unkeyed float changed** with its number
881
+ (`#eq-2-4` → `#eq-2-2-4`). Update links that point at one, or give the
882
+ float a key (`\label{eq:…}`, an `id`) so its anchor names its content.
883
+ 3. **Top-level headings are numbered** `1.1.2`, down to `####` by default
884
+ (`numbering.sections.depth`, `2`–`6`), on a wide screen, in print, and on a
885
+ folded section's bar. `numbering.sections.show: false` hides them, and a
886
+ knowledge check's and example set's numbers with them. The number is
887
+ generated content: the heading's text and anchor do not change.
888
+ 4. **A heading with inline math gets its anchor from its source**
889
+ (`## The range $R$` → `#the-range-r`), no longer from KaTeX's output.
890
+ Update links to such headings: `rg -n '^#{2,6} .*\$' content/docs`.
891
+ 5. **`sec-` and `ex-` are reserved reference prefixes.** `[[sec-…]]` cites a
892
+ section and `[[ex-…]]` an example set. Rename a notation key that starts
893
+ with either and is cited as `[[sec-…]]` or `[[ex-…]]`.
894
+ 6. **Number aliases** (`numbering.aliases`, on by default): a fragment a reader
895
+ types in number form (`#sec-1.1.2`, `#fig-2.2.1`, `#ex-1.1.1`) lands on the
896
+ element that shows that number. The site itself links and copies only
897
+ named anchors.
898
+ 7. Update prose, captions and tests that quote a number, and record the
899
+ renumbering in `content/CHANGELOG.md` (rule C1): readers see new numbers.
900
+
901
+ ### 12. Rules: retired IDs, settings, switches, waivers
902
+
903
+ 1. **Retired rules.** Rule N1 (the normal distribution is `\mathcal{N}`), rule
904
+ N6 (probability is `\mathbb{P}`) and rule N7 (the differential is
905
+ `\mathrm{d}`) are now lines of **rule N2**, whose table is the setting
906
+ `rules.N2.reserved`: by default the four number systems, `\mathbb{P}`,
907
+ `\mathcal{N}(\mu, \sigma^2)` and `\mathrm{d}`, one
908
+ `"<latex> — <meaning>"` string each. Replace the list to state the course's
909
+ own conventions: drop the probability lines in a course without
910
+ probability, or write `\mathcal{N}(\mu, \sigma)` for one that parameterises
911
+ the normal by standard deviation. Rule N3 (write a multi-letter label as
912
+ `\mathrm{…}`) is folded into **rule N10**, now `partial`. Rule N16
913
+ (alternate delimiter shapes to show nesting) is folded into **rule N17**,
914
+ and **reversed**: a delimiter's shape carries its meaning, and the engine
915
+ sizes a nest. Content that alternated shapes still builds, but the rule no
916
+ longer asks for it, and a `[` inside a grouping nest reads as an interval
917
+ first. Rule C4 (a release moves `[Unreleased]` and bumps `about.version`
918
+ together) is folded into **rule C3**, now `partial`. A `rules.N1` /
919
+ `rules.N3` / `rules.N6` / `rules.N7` / `rules.N16` / `rules.C4` block warns
920
+ `config-retired-rule`, naming the successor: delete it (only rule N2 takes
921
+ settings). A waiver naming a retired ID is `waiver-invalid`; name the
922
+ successor. `docs/authoring/retired.md` lists every retired rule.
923
+ 2. **Per-rule settings and switches: `rules`.** One block per rule ID, holding
924
+ only what differs from the defaults: rule S1's `maxLength` (65), rule
925
+ S2's `bridgeMinWords` / `bridgeMaxWords` / `scopeMinWords` /
926
+ `scopeMaxWords` (30 / 150), rule V5's `seeds` (10), rule V6's
927
+ `minLabels` (3), the `allow` / `deny` lists of rule N8, rule N10, rule
928
+ N12, rule N14 and rule N22, rule N9's `allow`, and rule N2's `reserved`.
929
+ With `rules.<ID>.enabled: false` a rule stops running and its diagnostics
930
+ are withheld. Switchable: N5, N8, N9, N10, N14, N17, N19, N20, N21, S1–S7, C3,
931
+ and every guide rule. The build fails on an unknown rule or key, a value
932
+ outside its spec, `enabled: false` on a locked rule (rule N4, rule N13,
933
+ rule N15), and `rules.X1` (course rules live in `courseRules`).
934
+ 3. **Per-place waivers.** `{/* explico-allow <ID>[, <ID>]: <reason> */}` on
935
+ its own line waives the named rules for the next block. A malformed waiver,
936
+ or one naming a locked rule, an invariant or an unknown ID, is an error
937
+ (`waiver-malformed`, `waiver-invalid`); one that silenced nothing warns
938
+ (`waiver-unused`). Record the reason: it is what a reviewer reads.
939
+ 4. **Read the rules in effect, not the rule set.** `pnpm validate:content`
940
+ writes `build/rules-in-effect.md`, and `pnpm content rules [--element …]
941
+ [--enforcement …]` prints it filtered: the rules this course has on, by
942
+ element, with codes and settings. An agent drafting for the course reads
943
+ that page; `docs/authoring/agents.md` now says so.
944
+ 5. **Rules that only gained a name.** Rule S3 (`raw-table-markdown`,
945
+ `raw-table-html`), rule S4 (`diagram-unkeyed`, `diagram-nested`), rule S5
946
+ (`convention-single-definition`), rule N19 (`notation-units`,
947
+ `units-vocab`), rule N20 (`notation-source-locator`) and rule N21
948
+ (`gloss-name-shape`) name checks that already ran. Nothing new fires; the
949
+ console line now carries the rule, `[S3 raw-table-markdown] …`.
950
+ 6. **New guide rules** — review-only, so nothing fails; switch one off with
951
+ `rules.<ID>.enabled: false` if it does not fit the course:
952
+ - rule N18: a multi-step derivation is one `aligned` chain, `&` before each
953
+ relation, a justification after `\qquad` as `\text{…}`.
954
+ - rule V6: every quantitative axis shows at least three labelled values.
955
+ - rule P2 (one canonical term per concept), rule P3 (one meaning per term),
956
+ rule P4 (the established term, defined where first needed), rule P5
957
+ (literal main text; analogies in an aside), rule P6 (industry jargon only
958
+ in an "Industry jargon" aside), rule P7 (one claim per sentence, links
959
+ stated), rule P8 (relation words match the math), rule P9 (a display
960
+ equation's lead-in says what it gives and where it comes from).
961
+ - rule S8: prefer `<Memo>` over a Starlight aside when the content needs an
962
+ anchor, a caption or collapsing.
963
+ 7. **Rule S2 applies only to a part page** (step 15). A course without
964
+ `content/docs/<dir>/index.mdx` is unaffected. With one, each needs a
965
+ `<PartScope>` paragraph and, for every part after the first, a
966
+ `<PartBridge>` paragraph before it, each within the `rules.S2` word range
967
+ (`part-page-paragraph`).
968
+
969
+ ### 13. Record the content changes (rules C1–C3)
970
+
971
+ The steps above edit `content/`. Add bullets under `## [Unreleased]` in
972
+ `content/CHANGELOG.md` (rule C1): renumbered floats and sections, retitled
973
+ checks and example sets, renamed sections whose anchors changed. Bump
974
+ `about.version` per rule C2 when you release (`pnpm release:content` in the
975
+ playground's shape), keeping it equal to the newest heading (rule C3).
976
+
977
+ ### 14. Only if your course CSS, browser tests or scripts reach into engine markup
978
+
979
+ ```bash
980
+ rg -n 'keyed-figure__(titlebar|summary|title-anchor)|diagram__(titlebar|summary|title-anchor)|lab-chart-figure|memo-block__summary|memo-preview__description|data-memo-(self|anchor)-link|compact-examples__(summary|title)|details\.compact-examples|assessment-heading-number' src content tests
981
+ ```
982
+
983
+ - **Figure and diagram.** The number moved into a caption line below the body:
984
+ `figcaption.float-caption` with `.float-caption__toggle`,
985
+ `.float-caption__number`, `.float-caption__anchor` and
986
+ `.float-caption__text`. `.keyed-figure__titlebar`, `.keyed-figure__summary`,
987
+ `.keyed-figure__title-anchor`, `.diagram__titlebar`, `.diagram__summary` and
988
+ `.diagram__title-anchor` are gone; `.diagram__number` is a link. A compacted
989
+ float keeps the caption line and hides only its body. The `<details>` keeps a
990
+ native `<summary>` shown only under `(scripting: none)`.
991
+ - **Table.** The caption line is the same `.float-caption` (caption size, a
992
+ chain icon after the number, no " — ").
993
+ - **Memo.** `.memo-block` is the frame; the `<details>`,
994
+ `.memo-block__summary`, `[data-memo-self-link]`, `[data-memo-anchor-link]`
995
+ and `.memo-preview__description` are gone. Its bar is an `<h6 role="none">`
996
+ with the `#memo-…` anchor, folded by `CollapsibleSections`. The marker is a
997
+ superscript italic letter (`a`, `b`, …), not `[1]`; `data-memo-number`
998
+ carries the letter.
999
+ - **Knowledge check.** The heading is `BlockHeading.astro`'s
1000
+ `<h2 id="check-‹id›" class="block-heading">` (badge, number, title in
1001
+ `.block-heading__title`), carrying `data-check-number`; its wrapper carries
1002
+ `data-card="accent"`. `.assessment-heading-number` is gone, and
1003
+ `.assessment-shell` draws no box.
1004
+ - **Example set.** `details.compact-examples`, `.compact-examples__summary`
1005
+ and `.compact-examples__title` are gone; `<compact-examples
1006
+ class="compact-examples">` is the card, `#ex-‹id›` its heading.
1007
+ - **Container tiers.** Every container is restyled from the tokens in
1008
+ `explico/styles/surfaces.css` (`--ex-surface-*`, `--ex-edge-*`,
1009
+ `--ex-depth-*`, `--ex-radius-*`; `.ex-tier-meta`, `.ex-tier-accent`,
1010
+ `.ex-tier-aside`), which `global.css` imports — `customCss` needs no second
1011
+ entry. A `<Figure>` and a `.lab-shell` lose their card; checks, example sets
1012
+ and `[card]` sections are accent cards; the notation panel, part-page rail,
1013
+ glossary entries, curriculum-map cards, About popover and a Starlight
1014
+ `<Card>` are meta; a memo, an open `<ShortNote>` and Starlight asides are
1015
+ asides. No container has a top rule. Override the tokens rather than the
1016
+ old per-container rules.
1017
+ - **Screenshots and layout assertions.** Printed and displayed numbers
1018
+ (step 11); a folded section's "Hidden here: …" line and, on a narrow screen,
1019
+ its in-line chevron and "(tap to expand)"; the chain icon's "Link copied ·
1020
+ § 1.1.2" confirmation; 0.25em more block padding on each display equation
1021
+ (about 8px); the sidebar's leading part caret, filled hover rows, reading
1022
+ progress, and `Overview` rows; print as shown (a closed disclosure prints
1023
+ its title and a link, where print used to expand everything).
1024
+ - **Reader state.** A memo's open state moved from `memo:‹id›` to
1025
+ `section:memo-‹id›`, so a returning reader's choice resets once. An example
1026
+ set remembers its state under `section:ex-‹id›` (new). A course that clears
1027
+ preference keys by name adds both.
1028
+
1029
+ ### 15. Optional: features to adopt
1030
+
1031
+ - **Captions.** `\caption{…}` inside a display equation's `$$…$$` (next to its
1032
+ `\label`) gives it a short caption: the `[[eq-…]]` preview card's heading
1033
+ and the copy tooltip ("Copy LaTeX — Range formula"). `short` on a
1034
+ `<Table>`, `<Figure>` or `<Diagram>` heads its preview card.
1035
+ - **References.** `[[sec-slug]]` prints `§ 1.1.2` with a preview card;
1036
+ `[[ex-id]]` prints `Example 1.1.1`; `[[check-…]]` now previews the check.
1037
+ `content context <lesson> --json` lists a lesson's `sections` and
1038
+ `examples`, so a drafter need not derive a slug.
1039
+ - **Cards and asides.** `[card]` / `[card=meta]` after a heading
1040
+ (`### Explore the range [collapsed] [card]`) cards its whole section.
1041
+ `:::note` / `:::tip` / `:::caution` / `:::danger` render as asides (step 2);
1042
+ `{icon="heart"}` names another Starlight icon and `icon=""` drops it, on a
1043
+ directive or an `<Aside>`.
1044
+ - **Part pages.** `content/docs/<dir>/index.mdx` is a part's own page: render
1045
+ it with `<PartOverview>` and state its argument with `<PartScope>` /
1046
+ `<PartBridge>` (rule S2). `<CourseContents>` lists every part and chapter for
1047
+ a landing page. `studyMinutes: 40` (or `[30, 40]`) in a lesson's frontmatter
1048
+ feeds the per-part totals.
1049
+ - **Disclosure per instance.** `defaultOpen` is new on `<Memo>` and
1050
+ `<CompactExamples>`, and `rememberState` on `<CompactExamples>`.
1051
+ - **Course rules.** `courseRules` in the settings is a list of the course's
1052
+ own rules, `X1`…`X99` (the `X` area is never used by the engine), each
1053
+ `{ id, title, summary?, elements?, enabled?, severity?, forbid?, unchecked?,
1054
+ doc? }`. With `forbid` patterns a rule is enforced at its `severity`
1055
+ (`error` or `warning`), and each finding's code is the rule's ID; without
1056
+ one it is a guide. A bad definition fails the build with the setting's path.
1057
+ See `docs/authoring/README.md` § Rules of your own.
1058
+ - **Accessibility** is the engine's job (`docs/authoring/README.md`
1059
+ § Accessibility): nothing asks an author for an accessible name or a text
1060
+ alternative.
1061
+
1062
+ ### 16. Fixes that need no action
1063
+
1064
+ Grey boxes get a background in the dark theme; an equation's copy button keeps
1065
+ its "Copy LaTeX" tooltip; a collapsed section's chevron is on screen on a
1066
+ phone; the copy-link icon shows on touch screens; labs hydrate under
1067
+ `astro dev` again; a printed `\sqrt` keeps its radical sign; `<Diagram>`
1068
+ formulas render in Safari and iOS, on screen and in print; display equations
1069
+ are no longer clipped at the bottom; pointing at the letter under an accent
1070
+ shows the letter's card.
1071
+
1072
+ ### 17. Verify
1073
+
1074
+ ```bash
1075
+ pnpm validate:content # run twice: the first run regenerates build/resolution/*
1076
+ pnpm content rules # the rules this course now has on; read before drafting
1077
+ pnpm content warnings # new warnings: rule N22, rule N8 / rule N10 in labelled equations
1078
+ pnpm content check --changed
1079
+ astro check
1080
+ astro build
1081
+ ```
1082
+
1083
+ Then run the course's browser tests, if it has any, and re-record the numbers
1084
+ and selectors of step 11 and step 14. A course with a gate script of its own
1085
+ that runs these stages (explico's is `scripts/agent/verify`) runs that
1086
+ instead of the list above.
1087
+
1088
+ ## 0.8.0 → 0.9.0
1089
+
1090
+ **No course changes are required** beyond the dependency bump, unless your
1091
+ course's own CSS reaches into the layout chrome (step 2). Everything in this
1092
+ release is engine-side UI: the panel collapse controls, reader-set panel
1093
+ widths, and the compact-chapter tooltip. No authoring rule, manifest field,
1094
+ CLI command, `CourseConfig` field, or Astro wiring moved, and the manifest
1095
+ schema version is unchanged.
1096
+
1097
+ ### 1. Update the dependency
1098
+
1099
+ ```bash
1100
+ pnpm add -D explico@0.9.0
1101
+ ```
1102
+
1103
+ 0.9.0 was never published to npm. Coming from npm's 0.8.0, install 0.11.0
1104
+ instead, and read the "Coming from 0.8.0" note of "0.10.0 → 0.11.0" first.
1105
+
1106
+ Or set `"explico": "0.9.0"` in `package.json` and run `pnpm install`. Nothing
1107
+ in `astro.config.mjs`, `content/course.config.ts`, `content/`, or your course
1108
+ scripts needs to change for this release.
1109
+
1110
+ ### 2. Only if your course CSS targets the collapse control
1111
+
1112
+ Two names in the layout chrome changed. Check your own stylesheets and
1113
+ components:
1114
+
1115
+ ```bash
1116
+ rg -n 'layout-edge-icon|--layout-edge-toggle-size' src content
1117
+ ```
1118
+
1119
+ - `--layout-edge-toggle-size` no longer exists. It was one square; the control
1120
+ is now a rectangle, so set `--layout-edge-toggle-width` and
1121
+ `--layout-edge-toggle-height` instead.
1122
+ - `svg.layout-edge-icon` / `.layout-edge-icon-hide` / `.layout-edge-icon-restore`
1123
+ are now `span.layout-edge-glyph` / `.layout-edge-glyph-hide` /
1124
+ `.layout-edge-glyph-restore`.
1125
+
1126
+ No hit means nothing to do.
1127
+
1128
+ ### 3. Optional: retune the docked control
1129
+
1130
+ The control docks into the corner the header and the panel's separator line
1131
+ make. One gap sets both of those edges, and the radius applies to the one
1132
+ corner free of them (bottom-left on the navigation panel, bottom-right on the
1133
+ contents panel, mirrored in RTL). Override any of these in your course CSS:
1134
+
1135
+ ```css
1136
+ :root {
1137
+ --layout-edge-toggle-gap: 2px; /* under the header, and in from the line */
1138
+ --layout-edge-toggle-width: 1.875rem;
1139
+ --layout-edge-toggle-height: 1.125rem;
1140
+ --layout-edge-toggle-radius: 0.4375rem;
1141
+ --layout-peek-delay: 40ms; /* rest this long on a rail before it peeks */
1142
+ }
1143
+ ```
1144
+
1145
+ `--layout-peek-delay` is read by the script, not only by the stylesheet, so it
1146
+ accepts any CSS `<time>` (`40ms`, `0.04s`) and falls back to 40ms if it cannot
1147
+ be parsed. Set it to `0ms` for the previous behaviour, where crossing a
1148
+ collapsed rail opened it immediately.
1149
+
1150
+ Widening or heightening the control also widens the strip each panel keeps
1151
+ clear at its top (`--layout-edge-toggle-reserve`), so the first chapter row
1152
+ stays out from under it; you do not set that one yourself.
1153
+
1154
+ ### 4. Reader-side state
1155
+
1156
+ Layout preferences moved from `credit-playground:layout:*` to
1157
+ `explico:layout:*`, and two new keys join them
1158
+ (`explico:layout:navigation-width`, `explico:layout:contents-width`). The old
1159
+ keys are read once as a fallback, so a returning reader keeps the layout they
1160
+ left and there is nothing to migrate. Only if your course clears preference
1161
+ keys by name — few do — add the new names to that list.
1162
+
1163
+ ### 5. Only if your course has browser tests over the layout chrome
1164
+
1165
+ A test that asserts the collapse control's size, shape, or position, or that
1166
+ clicks it by an `svg` selector, needs updating: it is a rectangle roughly
1167
+ 30 × 18 px docked 2 px under the header and 2 px in from the separator line,
1168
+ both panels' controls share one block start, and its glyphs are text. While a
1169
+ collapsed panel peeks open on hover, its control also *grows* towards the
1170
+ separator — its outer edge stays put — so a test that hovers a rail sees a
1171
+ much wider control than the resting one. A peek also no longer opens the
1172
+ instant a rail is hovered: a test that hovers one and asserts immediately must
1173
+ poll, or set `--layout-peek-delay: 0ms` for the duration of that test. Panels also now carry
1174
+ `.layout-edge-resizer` handles, and a resized panel stamps
1175
+ `data-navigation-resized` / `data-contents-resized` on `<html>`.
1176
+
1177
+ ### 6. Verify
1178
+
1179
+ ```bash
1180
+ pnpm validate:content # run twice: the first run regenerates build/resolution/*
1181
+ pnpm content check --changed
1182
+ astro check
1183
+ astro build
1184
+ ```
1185
+
36
1186
  ## 0.7.0 → 0.8.0
37
1187
 
38
1188
  ### 1. Rule N17 is inverted: delimiters are sized by the engine (`\left`/`\right` everywhere)
@@ -379,8 +1529,8 @@ needs to change to keep building. To clear the warnings, run
379
1529
  `pnpm content diagnostics warning`, and enlarge each named outer pair to the
380
1530
  next manual size without changing its delimiter shape. Rule N15's build-enforced
381
1531
  behavior is unchanged — its documentation now also accepts an N17 manual pair as
382
- "sized". See [`notation.md#n16`](notation.md#n16) and
383
- [`notation.md#n17`](notation.md#n17).
1532
+ "sized". See [`notation.md#n17`](notation.md#n17) — and, for rule N16, which
1533
+ was later folded into N17, [`retired.md`](retired.md).
384
1534
 
385
1535
  ### 7. Rule P1: "or" never introduces a second name for the same thing
386
1536