explico 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/CHANGELOG.md +1292 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +11 -4
  4. package/dist/compiler/astro.js +48 -15
  5. package/dist/compiler/cli.d.ts +27 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +33 -2
  8. package/dist/compiler/collections.js +91 -19
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +15 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +115 -8
  51. package/dist/compiler/manifest.js +221 -39
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +3 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +8 -2
  61. package/dist/reference/base-operators.mjs +8 -10
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figure-symbols.d.ts +32 -0
  88. package/dist/reference/figure-symbols.js +55 -0
  89. package/dist/reference/figures-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-abbreviations.js +1 -1
  92. package/dist/reference/gate-course-rules.d.ts +4 -3
  93. package/dist/reference/gate-course-rules.js +9 -4
  94. package/dist/reference/gate-heading-length.d.ts +2 -0
  95. package/dist/reference/gate-heading-length.js +7 -5
  96. package/dist/reference/gate-heading-structure.js +26 -0
  97. package/dist/reference/gate-indices.d.ts +35 -0
  98. package/dist/reference/gate-indices.js +299 -0
  99. package/dist/reference/gate-math.d.ts +32 -2
  100. package/dist/reference/gate-math.js +68 -12
  101. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  102. package/dist/reference/gate-mdx-expression.js +29 -0
  103. package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
  104. package/dist/reference/gate-non-chapter-pages.js +156 -0
  105. package/dist/reference/gate-part-pages.d.ts +4 -2
  106. package/dist/reference/gate-part-pages.js +2 -1
  107. package/dist/reference/gate-prop-text.d.ts +28 -0
  108. package/dist/reference/gate-prop-text.js +106 -0
  109. package/dist/reference/gate-prose-notation.d.ts +3 -1
  110. package/dist/reference/gate-prose-notation.js +4 -2
  111. package/dist/reference/gate-retired-keys.d.ts +35 -0
  112. package/dist/reference/gate-retired-keys.js +58 -0
  113. package/dist/reference/gloss.d.ts +2 -0
  114. package/dist/reference/katex-measure.d.ts +17 -0
  115. package/dist/reference/katex-measure.js +223 -0
  116. package/dist/reference/keyed-float-validate.js +12 -0
  117. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  118. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  119. package/dist/reference/lab-katex-validate.d.ts +43 -0
  120. package/dist/reference/lab-katex-validate.js +96 -0
  121. package/dist/reference/lab-math-scope.d.ts +1 -0
  122. package/dist/reference/lab-math-scope.js +13 -2
  123. package/dist/reference/lint-ignore.js +1 -1
  124. package/dist/reference/math-glyphs.d.mts +94 -2
  125. package/dist/reference/math-glyphs.mjs +851 -46
  126. package/dist/reference/mixed-content.d.ts +8 -3
  127. package/dist/reference/mixed-content.js +4 -2
  128. package/dist/reference/page-notation.d.ts +39 -7
  129. package/dist/reference/page-notation.js +62 -10
  130. package/dist/reference/raw-floats.mjs +37 -13
  131. package/dist/reference/references.d.ts +11 -1
  132. package/dist/reference/references.js +6 -0
  133. package/dist/reference/registry.js +115 -11
  134. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  135. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  136. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  137. package/dist/reference/remark-citation.mjs +0 -0
  138. package/dist/reference/remark-notation.mjs +275 -23
  139. package/dist/reference/render-lab-math.d.ts +13 -2
  140. package/dist/reference/render-lab-math.js +18 -6
  141. package/dist/reference/sections-validate.d.ts +28 -3
  142. package/dist/reference/sections-validate.js +31 -4
  143. package/dist/reference/sections.d.mts +24 -6
  144. package/dist/reference/sections.mjs +92 -17
  145. package/dist/reference/tables-validate.d.ts +1 -1
  146. package/dist/reference/tables-validate.js +24 -1
  147. package/dist/reference/text-measure.d.ts +10 -0
  148. package/dist/reference/text-measure.js +41 -0
  149. package/dist/reference/text-widths.d.ts +10 -0
  150. package/dist/reference/text-widths.js +1238 -0
  151. package/dist/reference/types.d.ts +64 -8
  152. package/dist/rules/catalog.js +223 -32
  153. package/dist/rules/in-effect.d.ts +1 -1
  154. package/dist/rules/rules.schema.json +100 -27
  155. package/dist/rules/settings.d.ts +1 -1
  156. package/dist/rules/settings.js +7 -0
  157. package/dist/session/PreferenceStore.d.ts +18 -1
  158. package/dist/session/PreferenceStore.js +29 -1
  159. package/docs/authoring/README.md +33 -21
  160. package/docs/authoring/agents.md +38 -7
  161. package/docs/authoring/components.md +115 -10
  162. package/docs/authoring/migrating.md +878 -0
  163. package/docs/authoring/notation.md +285 -30
  164. package/docs/authoring/retired.md +1 -0
  165. package/docs/authoring/structure-rules.md +84 -45
  166. package/docs/authoring/structure.md +67 -2
  167. package/docs/authoring/visualizations.md +167 -25
  168. package/package.json +12 -7
  169. package/src/compiler/astro.ts +72 -21
  170. package/src/compiler/cli.ts +143 -35
  171. package/src/compiler/collections.ts +158 -27
  172. package/src/compiler/content-schema.ts +108 -0
  173. package/src/compiler/content-schemas.ts +391 -0
  174. package/src/compiler/course-config.ts +86 -15
  175. package/src/compiler/exports/combined-html.ts +669 -0
  176. package/src/compiler/exports/download-index.ts +36 -0
  177. package/src/compiler/exports/export-chapters.ts +233 -0
  178. package/src/compiler/exports/export-pages.ts +44 -0
  179. package/src/compiler/exports/html-tree.ts +62 -0
  180. package/src/compiler/exports/latex/capture.ts +651 -0
  181. package/src/compiler/exports/latex/compile.ts +168 -0
  182. package/src/compiler/exports/latex/document.ts +479 -0
  183. package/src/compiler/exports/latex/explico.cls +415 -0
  184. package/src/compiler/exports/latex/numbering.ts +74 -0
  185. package/src/compiler/exports/latex/serialize.ts +787 -0
  186. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  187. package/src/compiler/exports/latex-export.ts +255 -0
  188. package/src/compiler/exports/markdown/serialize.ts +632 -0
  189. package/src/compiler/exports/markdown-export.ts +329 -0
  190. package/src/compiler/exports/standalone-html.ts +39 -70
  191. package/src/compiler/exports/static-tree.ts +944 -0
  192. package/src/compiler/exports/zip.ts +88 -0
  193. package/src/compiler/lesson-derivation.ts +13 -8
  194. package/src/compiler/manifest.ts +387 -40
  195. package/src/compiler/render-diagnostics.ts +111 -0
  196. package/src/components/CourseContents.astro +6 -0
  197. package/src/components/HoverPanel.astro +204 -6
  198. package/src/components/PartOverview.astro +18 -9
  199. package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
  200. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  201. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  202. package/src/components/assessments/AssessmentSet.astro +14 -1
  203. package/src/components/block-heading.ts +38 -0
  204. package/src/components/card-arrow.ts +18 -4
  205. package/src/components/card-carry.ts +406 -0
  206. package/src/components/card-drag.ts +1 -30
  207. package/src/components/card-fetch.ts +87 -0
  208. package/src/components/card-manager.ts +1222 -38
  209. package/src/components/card-memory.ts +5 -1
  210. package/src/components/card-placement.ts +211 -13
  211. package/src/components/card-side.ts +281 -31
  212. package/src/components/citation/CitationLayer.astro +45 -40
  213. package/src/components/diagram/Diagram.astro +123 -44
  214. package/src/components/disclosure/print-as-shown.ts +42 -6
  215. package/src/components/figure/Figure.astro +17 -1
  216. package/src/components/figure/FloatCaption.astro +4 -17
  217. package/src/components/figure/figure-state.ts +26 -17
  218. package/src/components/float-toolbar.ts +274 -0
  219. package/src/components/memo/Memo.astro +21 -10
  220. package/src/components/memo/ShortNote.astro +4 -0
  221. package/src/components/notation/NotationGlossary.astro +22 -17
  222. package/src/components/notation/NotationLayer.astro +300 -79
  223. package/src/components/notation/NotationMath.astro +8 -1
  224. package/src/components/notation/render-notation-math.ts +51 -5
  225. package/src/components/readable-text.ts +25 -0
  226. package/src/components/reference/FloatRefLayer.astro +162 -89
  227. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  228. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  229. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  230. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  231. package/src/components/starlight/LessonExportActions.astro +384 -310
  232. package/src/components/starlight/LessonFooter.astro +66 -8
  233. package/src/components/starlight/PrintController.astro +28 -0
  234. package/src/components/starlight/TextSizeControl.astro +137 -0
  235. package/src/components/starlight/print-controller.ts +380 -0
  236. package/src/components/starlight/section-summary.ts +13 -5
  237. package/src/components/table/Table.astro +14 -22
  238. package/src/components/table/table-truncation.ts +15 -0
  239. package/src/content-config.ts +20 -312
  240. package/src/curriculum/validation.ts +72 -24
  241. package/src/reference/base-abbreviations.mjs +2 -2
  242. package/src/reference/base-operators.mjs +8 -10
  243. package/src/reference/base-roman-labels.mjs +4 -3
  244. package/src/reference/citation-format.mjs +74 -0
  245. package/src/reference/consistency.ts +47 -61
  246. package/src/reference/curriculum-alignment.ts +4 -41
  247. package/src/reference/derived-entries.ts +68 -0
  248. package/src/reference/derived-notation.mjs +121 -0
  249. package/src/reference/diagram-fit.ts +104 -0
  250. package/src/reference/diagram-layout.ts +182 -51
  251. package/src/reference/diagrams-validate.ts +2 -1
  252. package/src/reference/equations-validate.ts +41 -2
  253. package/src/reference/equations.mjs +120 -22
  254. package/src/reference/examples-validate.ts +1 -0
  255. package/src/reference/figure-fit.ts +139 -0
  256. package/src/reference/figure-pictures.ts +96 -0
  257. package/src/reference/figure-symbols.ts +117 -0
  258. package/src/reference/figures-validate.ts +2 -1
  259. package/src/reference/gate-abbreviations.ts +1 -1
  260. package/src/reference/gate-course-rules.ts +22 -7
  261. package/src/reference/gate-heading-length.ts +25 -7
  262. package/src/reference/gate-heading-structure.ts +40 -1
  263. package/src/reference/gate-indices.ts +470 -0
  264. package/src/reference/gate-math.ts +102 -13
  265. package/src/reference/gate-mdx-expression.ts +45 -0
  266. package/src/reference/gate-non-chapter-pages.ts +241 -0
  267. package/src/reference/gate-part-pages.ts +1 -1
  268. package/src/reference/gate-prop-text.ts +145 -0
  269. package/src/reference/gate-prose-notation.ts +19 -5
  270. package/src/reference/gate-retired-keys.ts +101 -0
  271. package/src/reference/gloss.ts +2 -0
  272. package/src/reference/katex-measure.ts +384 -0
  273. package/src/reference/keyed-float-validate.ts +22 -0
  274. package/src/reference/keyed-jsx-floats.mjs +11 -14
  275. package/src/reference/lab-katex-validate.ts +156 -0
  276. package/src/reference/lab-math-scope.ts +15 -1
  277. package/src/reference/lint-ignore.ts +4 -1
  278. package/src/reference/math-glyphs.mjs +851 -46
  279. package/src/reference/mixed-content.ts +11 -2
  280. package/src/reference/page-notation.ts +114 -14
  281. package/src/reference/raw-floats.mjs +37 -13
  282. package/src/reference/references.ts +14 -0
  283. package/src/reference/registry.ts +170 -9
  284. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  285. package/src/reference/rehype-heading-anchors.mjs +11 -1
  286. package/src/reference/remark-citation.mjs +0 -0
  287. package/src/reference/remark-notation.mjs +275 -23
  288. package/src/reference/render-lab-math.ts +40 -5
  289. package/src/reference/sections-validate.ts +67 -3
  290. package/src/reference/sections.mjs +92 -17
  291. package/src/reference/tables-validate.ts +40 -2
  292. package/src/reference/text-measure.ts +63 -0
  293. package/src/reference/text-widths.ts +133 -0
  294. package/src/reference/types.ts +75 -8
  295. package/src/rules/catalog.ts +241 -31
  296. package/src/rules/settings.ts +10 -0
  297. package/src/session/PreferenceStore.ts +53 -0
  298. package/src/styles/global.css +322 -83
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Every figure has its picture in the built page (rule V3). A lab draws its
3
+ * plot in the browser, after the page loads, so without a copy drawn at
4
+ * build time the page holds an empty box until then: a reference card made
5
+ * from the page (from another page, or before the figure scrolls into view)
6
+ * has only the caption, and so do print without JavaScript and the
7
+ * standalone HTML export. A lab draws its default picture at build time and
8
+ * hands it to its island as children (`still-plot.ts` in the playground).
9
+ *
10
+ * `undrawnFigurePictures` reads rendered HTML: a `figure.keyed-figure` whose
11
+ * picture (`.keyed-figure__body`) holds an `<astro-island>` but nothing drawn
12
+ * (`svg`, `img`, `picture`, `canvas`, `video` or `table`) is reported.
13
+ * `compiler/astro.ts` runs it on every built page and warns; it is a
14
+ * warning, not an error, since the figure still works once hydrated.
15
+ */
16
+ /** The diagnostic a built page with an undrawn picture gets. */
17
+ interface UndrawnFigurePicture {
18
+ readonly code: 'lab-picture-undrawn';
19
+ /** The figure's anchor id, e.g. `fig-launch-angle-explorer`. */
20
+ readonly figure: string;
21
+ }
22
+ /** The figures in `html` whose picture is an island with nothing drawn. */
23
+ declare function undrawnFigurePictures(html: string): UndrawnFigurePicture[];
24
+ /** The build's warning for one page, naming each figure and what to do. */
25
+ declare function undrawnFigureWarning(page: string, figures: readonly UndrawnFigurePicture[]): string;
26
+
27
+ export { type UndrawnFigurePicture, undrawnFigurePictures, undrawnFigureWarning };
@@ -0,0 +1,49 @@
1
+ import { parse } from "parse5";
2
+ const DRAWN = /* @__PURE__ */ new Set(["svg", "img", "picture", "canvas", "video", "table"]);
3
+ function isElement(node) {
4
+ return "tagName" in node;
5
+ }
6
+ function attribute(node, name) {
7
+ return node.attrs.find((attr) => attr.name === name)?.value;
8
+ }
9
+ function hasClass(node, name) {
10
+ return (attribute(node, "class") ?? "").split(/\s+/u).includes(name);
11
+ }
12
+ function childrenOf(node) {
13
+ if (isElement(node) && node.tagName === "template") return [];
14
+ return "childNodes" in node ? node.childNodes : [];
15
+ }
16
+ function find(node, test) {
17
+ for (const child of childrenOf(node)) {
18
+ if (!isElement(child)) continue;
19
+ if (test(child)) return child;
20
+ const inner = find(child, test);
21
+ if (inner) return inner;
22
+ }
23
+ return void 0;
24
+ }
25
+ function undrawnFigurePictures(html) {
26
+ if (!html.includes("keyed-figure") || !html.includes("astro-island")) return [];
27
+ const found = [];
28
+ const visit = (node) => {
29
+ for (const child of childrenOf(node)) {
30
+ if (!isElement(child)) continue;
31
+ if (child.tagName === "figure" && hasClass(child, "keyed-figure")) {
32
+ const body = find(child, (el) => hasClass(el, "keyed-figure__body"));
33
+ if (body && find(body, (el) => el.tagName === "astro-island") && !find(body, (el) => DRAWN.has(el.tagName))) {
34
+ found.push({ code: "lab-picture-undrawn", figure: attribute(child, "id") ?? "" });
35
+ }
36
+ }
37
+ visit(child);
38
+ }
39
+ };
40
+ visit(parse(html));
41
+ return found;
42
+ }
43
+ function undrawnFigureWarning(page, figures) {
44
+ return `lab-picture-undrawn (rule V3): on rendered page "${page}", ` + figures.map(({ figure }) => `#${figure}`).join(", ") + " draw nothing until the browser runs the island, so a preview card, print without JavaScript and the HTML export show no picture. Draw the default picture at build time and pass it to the island as its children.";
45
+ }
46
+ export {
47
+ undrawnFigurePictures,
48
+ undrawnFigureWarning
49
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Every symbol a figure shows opens its notation card (rule N4). Lesson
3
+ * math, lab math (`renderLabMath`), a diagram's formulas and a caption
4
+ * clause's `{ math }` run all go through the glyph resolver, which binds
5
+ * each symbol to its key: KaTeX then wraps it in a `[data-notation-key]`
6
+ * span. KaTeX that a course renders itself — `katex.renderToString` in a
7
+ * lab's legend, axis title or slider label — skips the resolver, so its
8
+ * symbols have no card and nothing else would report them.
9
+ *
10
+ * `unboundFigureSymbols` reads rendered HTML: inside each
11
+ * `figure.keyed-figure` (a lab's tools included), every letter KaTeX draws
12
+ * must sit in a `[data-notation-key]`. Upright text is not a symbol:
13
+ * `\text{…}`, `\mathrm{…}` (the resolver skips its body, rule N10) and a
14
+ * named operator (`\sin`, `\max`). `compiler/astro.ts` runs it on every
15
+ * built page, and a course's browser test can run it on a hydrated figure's
16
+ * `outerHTML`, which also holds what an island draws only in the browser.
17
+ */
18
+ interface UnboundFigureSymbols {
19
+ /** The figure's anchor id, e.g. `fig-latent-factor-model`. */
20
+ readonly figure: string;
21
+ /** Each unbound symbol's text, in document order, once. */
22
+ readonly symbols: readonly string[];
23
+ }
24
+ /** The figures in `html` that draw a symbol with no card, and those symbols. */
25
+ declare function unboundFigureSymbols(html: string): UnboundFigureSymbols[];
26
+ /**
27
+ * Fail a build whose rendered page shows a symbol in a figure that opens no
28
+ * card, naming the figure, the symbols, and the way to bind them.
29
+ */
30
+ declare function assertFigureSymbolsBound(page: string, html: string): void;
31
+
32
+ export { type UnboundFigureSymbols, assertFigureSymbolsBound, unboundFigureSymbols };
@@ -0,0 +1,55 @@
1
+ import { parse } from "parse5";
2
+ const UPRIGHT = /* @__PURE__ */ new Set(["text", "mathrm", "textrm", "mop"]);
3
+ const LETTER = new RegExp("\\p{L}", "u");
4
+ function isElement(node) {
5
+ return "tagName" in node;
6
+ }
7
+ function attribute(node, name) {
8
+ return node.attrs.find((attr) => attr.name === name)?.value;
9
+ }
10
+ function classes(node) {
11
+ return (attribute(node, "class") ?? "").split(/\s+/u).filter(Boolean);
12
+ }
13
+ function childrenOf(node) {
14
+ if (isElement(node) && node.tagName === "template") {
15
+ return node.content.childNodes;
16
+ }
17
+ return "childNodes" in node ? node.childNodes : [];
18
+ }
19
+ function unboundFigureSymbols(html) {
20
+ if (!html.includes("keyed-figure") || !html.includes("katex-html")) return [];
21
+ const found = /* @__PURE__ */ new Map();
22
+ const visit = (node, figure, inKatex, covered) => {
23
+ if (isElement(node)) {
24
+ const names = classes(node);
25
+ if (node.tagName === "figure" && names.includes("keyed-figure")) {
26
+ figure = attribute(node, "id") ?? "(a figure with no id)";
27
+ }
28
+ if (names.includes("katex-html")) inKatex = true;
29
+ if (attribute(node, "data-notation-key") !== void 0 || inKatex && names.some((name) => UPRIGHT.has(name))) {
30
+ covered = true;
31
+ }
32
+ } else if (node.nodeName === "#text" && figure !== void 0 && inKatex && !covered) {
33
+ const text = node.value.trim();
34
+ if (LETTER.test(text)) {
35
+ const symbols = found.get(figure) ?? [];
36
+ if (!symbols.includes(text)) symbols.push(text);
37
+ found.set(figure, symbols);
38
+ }
39
+ }
40
+ for (const child of childrenOf(node)) visit(child, figure, inKatex, covered);
41
+ };
42
+ visit(parse(html), void 0, false, false);
43
+ return [...found].map(([figure, symbols]) => ({ figure, symbols }));
44
+ }
45
+ function assertFigureSymbolsBound(page, html) {
46
+ const unbound = unboundFigureSymbols(html);
47
+ if (unbound.length === 0) return;
48
+ throw new Error(
49
+ `Explico figure symbols without a notation card on rendered page "${page}": ` + unbound.map(({ figure, symbols }) => `#${figure} shows ${symbols.map((s) => `"${s}"`).join(", ")}`).join("; ") + ". Every symbol a figure shows opens its card (rule N4): render its math with renderLabMath (`{ display: false }` for a legend, an axis title or a label; a `{ math }` run for the caption clause), never with katex directly. Write a unit as `\\text{\u2026}`."
50
+ );
51
+ }
52
+ export {
53
+ assertFigureSymbolsBound,
54
+ unboundFigureSymbols
55
+ };
@@ -2,7 +2,7 @@ import { KeyedFloatLabelRecord, KeyedFloatLessonInput, KeyedFloatValidation } fr
2
2
 
3
3
  /** `validateKeyedFloats` for `<Figure>` — see `keyed-float-validate.ts`. */
4
4
 
5
- type FigureDiagnosticCode = 'fig-invalid-key' | 'fig-duplicate-key' | 'fig-ref-resolves' | 'fig-key-unused' | 'fig-removed-prop';
5
+ type FigureDiagnosticCode = 'fig-invalid-key' | 'fig-duplicate-key' | 'fig-ref-resolves' | 'fig-key-unused' | 'fig-removed-prop' | 'fig-unread-prop';
6
6
  interface FigureDiagnostic {
7
7
  readonly code: FigureDiagnosticCode;
8
8
  readonly severity: 'error' | 'warning' | 'info';
@@ -1,6 +1,6 @@
1
1
  import { MathGateDiagnostic } from './gate-math.js';
2
2
  import { NotationRegistryInput, AbbreviationDefinitionInput } from './types.js';
3
- import { a as CourseAbbreviations } from '../course-config-Dsnri9Tk.js';
3
+ import { h as CourseAbbreviations } from '../course-config-rRtME5UV.js';
4
4
  import '../rules/catalog.js';
5
5
  import '../rules/course-rules.js';
6
6
 
@@ -14,7 +14,7 @@ function findAbbreviationLikeTokens(text) {
14
14
  }
15
15
  return out.sort((a, b) => a.index - b.index);
16
16
  }
17
- const FIX = "Add it to an abbreviations-table entry (content/abbreviations/*.md), allowlist it in abbreviations.allow in course.config.ts if it is common, non-technical usage, or \u2014 if it names a math quantity \u2014 introduce it as \\mathrm{\u2026} (or \\operatorname{\u2026}) bound to a notation entry the reader can hover. See docs/authoring/notation.md#n14.";
17
+ const FIX = "Add it to an abbreviations-table entry (content/abbreviations/*.md), allowlist it in rules.N14.allow in the course config (course.config.ts, or the course.settings.json it reads) if it is common, non-technical usage, or \u2014 if it names a math quantity \u2014 introduce it as \\mathrm{\u2026} (or \\operatorname{\u2026}) bound to a notation entry the reader can hover. See docs/authoring/notation.md#n14.";
18
18
  function gateAbbreviations(input) {
19
19
  const { notationInput, abbreviations, courseAbbreviations } = input;
20
20
  const tableTokens = /* @__PURE__ */ new Set();
@@ -9,9 +9,10 @@ import '../rules/catalog.js';
9
9
  *
10
10
  * One loop over every enabled course rule's `forbid` patterns, matched against
11
11
  * the same "is this really prose" surface rule N9 uses — `proseOnly` blanks
12
- * code fences, inline code, `$…$` / `$$…$$`, `[[key]]` references, `import`
13
- * lines, JSX tags and MDX comments to spaces, so offsets still point at the
14
- * source. A match becomes a `MathGateDiagnostic` with the rule's own ID as its
12
+ * code fences, inline code, `$…$` / `$$…$$`, `[[key]]` references, link
13
+ * addresses, `import` lines, JSX tags and MDX comments to spaces, so offsets
14
+ * still point at the source — plus the text of each visible prop (`caption`,
15
+ * `short`, `title`, `label`) as a paragraph of its own. A match becomes a `MathGateDiagnostic` with the rule's own ID as its
15
16
  * code and a lesson `file` + `line`, which is what lets `{/* explico-allow X1:
16
17
  * reason *\/}` waive one place.
17
18
  *
@@ -1,4 +1,5 @@
1
1
  import { lessonLine, proseOnly } from "./gate-prose-notation.js";
2
+ import { propStrings } from "./gate-prop-text.js";
2
3
  const HEADING = /^[ \t]{0,3}(#{1,6})[ \t]+(.*)$/;
3
4
  function paragraphs(text) {
4
5
  const units = [];
@@ -32,14 +33,18 @@ function headings(text) {
32
33
  }
33
34
  return units;
34
35
  }
35
- function unitsFor(scope, prose) {
36
+ const VISIBLE_PROPS = /* @__PURE__ */ new Set(["caption", "short", "title", "label"]);
37
+ function visibleProps(body) {
38
+ return propStrings(body).filter((prop) => VISIBLE_PROPS.has(prop.name) && prop.value.trim() !== "").map((prop) => ({ text: prop.value, index: prop.offset }));
39
+ }
40
+ function unitsFor(scope, prose, body) {
36
41
  switch (scope) {
37
42
  case "prose":
38
- return [{ text: prose, index: 0 }];
43
+ return [{ text: prose, index: 0 }, ...visibleProps(body)];
39
44
  case "heading":
40
45
  return headings(prose);
41
46
  case "paragraph":
42
- return paragraphs(prose);
47
+ return [...paragraphs(prose), ...visibleProps(body)];
43
48
  }
44
49
  }
45
50
  function* matches(pattern, unit) {
@@ -59,7 +64,7 @@ function gateCourseRules(lessons, rules) {
59
64
  const cache = /* @__PURE__ */ new Map();
60
65
  for (const rule of active) {
61
66
  for (const pattern of rule.forbid) {
62
- if (!cache.has(pattern.scope)) cache.set(pattern.scope, unitsFor(pattern.scope, prose));
67
+ if (!cache.has(pattern.scope)) cache.set(pattern.scope, unitsFor(pattern.scope, prose, lesson.body));
63
68
  for (const unit of cache.get(pattern.scope)) {
64
69
  for (const [index, token] of matches(pattern, unit)) {
65
70
  diagnostics.push({
@@ -10,6 +10,8 @@ declare const HEADING_LENGTH_LIMIT = 65;
10
10
  */
11
11
  declare function gateHeadingLength(lessons: readonly NotationLessonInput[], options?: {
12
12
  readonly maxLength?: number;
13
+ /** A `[[key]]` term's label on a lesson's page, as a heading shows it. */
14
+ readonly labelFor?: (lessonId: string, key: string) => string | undefined;
13
15
  }): MathGateDiagnostic[];
14
16
 
15
17
  export { HEADING_LENGTH_LIMIT, gateHeadingLength };
@@ -1,5 +1,6 @@
1
1
  import { EQ_CAPTION_PATTERN, stripFencedCode } from "./equations.mjs";
2
2
  import { stripHeadingMarker } from "./rehype-heading-anchors.mjs";
3
+ import { headingDisplayText } from "./sections.mjs";
3
4
  import { lessonLine } from "./gate-prose-notation.js";
4
5
  const HEADING_LENGTH_LIMIT = 65;
5
6
  const HEADING_LINE = /^[ \t]{0,3}(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/gm;
@@ -20,13 +21,13 @@ function readAttribute(attributesSource, name) {
20
21
  function overLimitMessage(kind, text, limit) {
21
22
  return `${kind} ${JSON.stringify(text)} is ${text.length} characters, over the ${limit}-character limit (rule S1, rules.S1.maxLength in the course settings). Shorten it and move the rest into the paragraph or caption that follows.`;
22
23
  }
23
- function headingFindings(text, limit) {
24
+ function headingFindings(text, limit, labelFor) {
24
25
  const out = [];
25
26
  const re = new RegExp(HEADING_LINE.source, HEADING_LINE.flags);
26
27
  let match;
27
28
  while (match = re.exec(text)) {
28
29
  const level = match[1]?.length ?? 1;
29
- const title = stripHeadingMarker(match[2] ?? "").trim();
30
+ const title = headingDisplayText(stripHeadingMarker(match[2] ?? ""), { labelFor });
30
31
  if (title.length > limit) {
31
32
  out.push({
32
33
  index: match.index,
@@ -71,9 +72,9 @@ function equationCaptionFindings(text, limit) {
71
72
  }
72
73
  return out;
73
74
  }
74
- function findingsIn(text, limit) {
75
+ function findingsIn(text, limit, labelFor) {
75
76
  return [
76
- ...headingFindings(text, limit),
77
+ ...headingFindings(text, limit, labelFor),
77
78
  ...floatTitleFindings(text, limit),
78
79
  ...equationCaptionFindings(text, limit)
79
80
  ].sort((a, b) => a.index - b.index);
@@ -83,7 +84,8 @@ function gateHeadingLength(lessons, options = {}) {
83
84
  const limit = options.maxLength ?? HEADING_LENGTH_LIMIT;
84
85
  for (const lesson of lessons) {
85
86
  const text = stripFencedCode(lesson.body);
86
- for (const finding of findingsIn(text, limit)) {
87
+ const labelFor = options.labelFor ? (key) => options.labelFor?.(lesson.lessonId, key) : void 0;
88
+ for (const finding of findingsIn(text, limit, labelFor)) {
87
89
  diagnostics.push({
88
90
  context: "structure",
89
91
  code: "title-length",
@@ -67,6 +67,31 @@ function skipFindings(blocks) {
67
67
  }
68
68
  return out;
69
69
  }
70
+ const HOSTS = ["Memo", "ShortNote"];
71
+ function nestedFindings(body) {
72
+ const blank = (match) => match.replace(/[^\n]/g, " ");
73
+ const text = body.replace(/```[\s\S]*?```/g, blank).replace(/\{\/\*[\s\S]*?\*\/\}/g, blank);
74
+ const out = [];
75
+ for (const host of HOSTS) {
76
+ const opening = new RegExp(`<${host}\\b(?:[^>"']|"[^"]*"|'[^']*')*(?<!/)>`, "g");
77
+ for (const open of text.matchAll(opening)) {
78
+ const start = (open.index ?? 0) + open[0].length;
79
+ const end = text.indexOf(`</${host}>`, start);
80
+ if (end === -1) continue;
81
+ for (const inner of text.slice(start, end).matchAll(/<(AssessmentSet|CompactExamples)\b/g)) {
82
+ const name = inner[1];
83
+ const what = name === "CompactExamples" ? "set of worked examples" : "knowledge check";
84
+ out.push({
85
+ code: "heading-nested",
86
+ index: start + (inner.index ?? 0),
87
+ token: `<${name}>`,
88
+ message: `\`<${name}>\` sits inside a \`<${host}>\`, ${TITLED_COMPONENTS[name].what}, ${host === "Memo" ? "under the memo\u2019s own `<h6>`" : "in the middle of the note"}: the heading order jumps, and "On this page" never lists it (rule S6). A ${what} is a section of the lesson: put it at the top level, after the ${host === "Memo" ? "memo" : "paragraph"}.`
89
+ });
90
+ }
91
+ }
92
+ }
93
+ return out;
94
+ }
70
95
  function adjacentFindings(blocks, lesson) {
71
96
  const out = [];
72
97
  blocks.forEach((block, position) => {
@@ -92,6 +117,7 @@ function gateHeadingStructure(lessons, options = {}) {
92
117
  const blocks = blocksOf(lesson.body);
93
118
  const findings = [
94
119
  ...levels ? skipFindings(blocks) : [],
120
+ ...levels ? nestedFindings(lesson.body) : [],
95
121
  ...adjacency ? adjacentFindings(blocks, lesson) : []
96
122
  ].sort((a, b) => a.index - b.index);
97
123
  for (const finding of findings) {
@@ -0,0 +1,35 @@
1
+ import { ScopeEntry, AssessmentLike, LessonAssessmentLink, MathGateDiagnostic } from './gate-math.js';
2
+ import { ResolvedGloss } from './gloss.js';
3
+ import { NotationLessonInput, DerivedUse, IndexedSymbol } from './types.js';
4
+
5
+ /** What the check needs of an entry besides its glyph-table row. */
6
+ interface IndexEntryFacts extends ScopeEntry {
7
+ readonly range?: string;
8
+ readonly formula?: string;
9
+ readonly glosses?: readonly ResolvedGloss[];
10
+ readonly source?: {
11
+ readonly file: string;
12
+ };
13
+ }
14
+ /**
15
+ * Run rule N23 over every lesson (and non-chapter page) body, every notation
16
+ * formula and every assessment, and collect the derived cards each body
17
+ * uses. `entries` holds every shared entry by key; a lesson's own come from
18
+ * its `localDefinitions`. The index-set check (`index-range`) reads lesson
19
+ * bodies only: a letter's set is stated on its card or on the page.
20
+ */
21
+ declare function gateIndices(lessons: readonly NotationLessonInput[], entries: ReadonlyMap<string, IndexEntryFacts>, extras?: {
22
+ readonly assessments?: readonly AssessmentLike[];
23
+ readonly lessonAssessments?: readonly LessonAssessmentLink[];
24
+ }): {
25
+ diagnostics: MathGateDiagnostic[];
26
+ derivedByLesson: Map<string, DerivedUse[]>;
27
+ };
28
+ /**
29
+ * Rule N23's findings in one math source rendered outside a lesson body — a
30
+ * lab's or a diagram's, which fail the build as they render — as one line
31
+ * each: the form, what explains none of it, and the fix.
32
+ */
33
+ declare function indexFindings(indices: readonly IndexedSymbol[]): string[];
34
+
35
+ export { DerivedUse, type IndexEntryFacts, gateIndices, indexFindings };