explico 0.12.0 → 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 (279) hide show
  1. package/CHANGELOG.md +1122 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +4 -3
  4. package/dist/compiler/astro.js +45 -15
  5. package/dist/compiler/cli.d.ts +26 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +9 -1
  8. package/dist/compiler/collections.js +55 -16
  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 +14 -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 +87 -6
  51. package/dist/compiler/manifest.js +174 -31
  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 +2 -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 +2 -2
  61. package/dist/reference/base-operators.mjs +2 -2
  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/figures-validate.d.ts +1 -1
  88. package/dist/reference/gate-abbreviations.d.ts +1 -1
  89. package/dist/reference/gate-abbreviations.js +1 -1
  90. package/dist/reference/gate-course-rules.d.ts +4 -3
  91. package/dist/reference/gate-course-rules.js +9 -4
  92. package/dist/reference/gate-heading-length.d.ts +2 -0
  93. package/dist/reference/gate-heading-length.js +7 -5
  94. package/dist/reference/gate-heading-structure.js +26 -0
  95. package/dist/reference/gate-indices.d.ts +35 -0
  96. package/dist/reference/gate-indices.js +299 -0
  97. package/dist/reference/gate-math.d.ts +32 -2
  98. package/dist/reference/gate-math.js +68 -12
  99. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  100. package/dist/reference/gate-mdx-expression.js +29 -0
  101. package/dist/reference/gate-part-pages.d.ts +1 -1
  102. package/dist/reference/gate-prop-text.d.ts +28 -0
  103. package/dist/reference/gate-prop-text.js +106 -0
  104. package/dist/reference/gate-prose-notation.d.ts +3 -1
  105. package/dist/reference/gate-prose-notation.js +4 -2
  106. package/dist/reference/gate-retired-keys.d.ts +35 -0
  107. package/dist/reference/gate-retired-keys.js +58 -0
  108. package/dist/reference/gloss.d.ts +2 -0
  109. package/dist/reference/katex-measure.d.ts +17 -0
  110. package/dist/reference/katex-measure.js +223 -0
  111. package/dist/reference/keyed-float-validate.js +12 -0
  112. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  113. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  114. package/dist/reference/lab-math-scope.d.ts +1 -0
  115. package/dist/reference/lab-math-scope.js +13 -2
  116. package/dist/reference/math-glyphs.d.mts +94 -2
  117. package/dist/reference/math-glyphs.mjs +851 -46
  118. package/dist/reference/mixed-content.d.ts +8 -3
  119. package/dist/reference/mixed-content.js +4 -2
  120. package/dist/reference/page-notation.d.ts +34 -4
  121. package/dist/reference/page-notation.js +54 -2
  122. package/dist/reference/references.d.ts +11 -1
  123. package/dist/reference/references.js +6 -0
  124. package/dist/reference/registry.js +104 -10
  125. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  126. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  127. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  128. package/dist/reference/remark-citation.mjs +0 -0
  129. package/dist/reference/remark-notation.mjs +84 -20
  130. package/dist/reference/render-lab-math.d.ts +2 -0
  131. package/dist/reference/render-lab-math.js +17 -5
  132. package/dist/reference/sections-validate.d.ts +28 -3
  133. package/dist/reference/sections-validate.js +31 -4
  134. package/dist/reference/sections.d.mts +24 -6
  135. package/dist/reference/sections.mjs +92 -17
  136. package/dist/reference/tables-validate.d.ts +1 -1
  137. package/dist/reference/tables-validate.js +24 -1
  138. package/dist/reference/text-measure.d.ts +10 -0
  139. package/dist/reference/text-measure.js +41 -0
  140. package/dist/reference/text-widths.d.ts +10 -0
  141. package/dist/reference/text-widths.js +1238 -0
  142. package/dist/reference/types.d.ts +64 -8
  143. package/dist/rules/catalog.js +191 -30
  144. package/dist/rules/in-effect.d.ts +1 -1
  145. package/dist/rules/rules.schema.json +79 -24
  146. package/dist/rules/settings.d.ts +1 -1
  147. package/dist/rules/settings.js +7 -0
  148. package/dist/session/PreferenceStore.d.ts +18 -1
  149. package/dist/session/PreferenceStore.js +29 -1
  150. package/docs/authoring/README.md +32 -21
  151. package/docs/authoring/agents.md +38 -7
  152. package/docs/authoring/components.md +111 -6
  153. package/docs/authoring/migrating.md +663 -0
  154. package/docs/authoring/notation.md +281 -29
  155. package/docs/authoring/retired.md +1 -0
  156. package/docs/authoring/structure-rules.md +10 -41
  157. package/docs/authoring/structure.md +53 -2
  158. package/docs/authoring/visualizations.md +144 -18
  159. package/package.json +12 -7
  160. package/src/compiler/astro.ts +57 -17
  161. package/src/compiler/cli.ts +143 -35
  162. package/src/compiler/collections.ts +92 -19
  163. package/src/compiler/content-schema.ts +108 -0
  164. package/src/compiler/content-schemas.ts +391 -0
  165. package/src/compiler/course-config.ts +86 -15
  166. package/src/compiler/exports/combined-html.ts +669 -0
  167. package/src/compiler/exports/download-index.ts +36 -0
  168. package/src/compiler/exports/export-chapters.ts +233 -0
  169. package/src/compiler/exports/export-pages.ts +44 -0
  170. package/src/compiler/exports/html-tree.ts +62 -0
  171. package/src/compiler/exports/latex/capture.ts +651 -0
  172. package/src/compiler/exports/latex/compile.ts +168 -0
  173. package/src/compiler/exports/latex/document.ts +479 -0
  174. package/src/compiler/exports/latex/explico.cls +415 -0
  175. package/src/compiler/exports/latex/numbering.ts +74 -0
  176. package/src/compiler/exports/latex/serialize.ts +787 -0
  177. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  178. package/src/compiler/exports/latex-export.ts +255 -0
  179. package/src/compiler/exports/markdown/serialize.ts +632 -0
  180. package/src/compiler/exports/markdown-export.ts +329 -0
  181. package/src/compiler/exports/standalone-html.ts +39 -70
  182. package/src/compiler/exports/static-tree.ts +944 -0
  183. package/src/compiler/exports/zip.ts +88 -0
  184. package/src/compiler/lesson-derivation.ts +13 -8
  185. package/src/compiler/manifest.ts +299 -28
  186. package/src/compiler/render-diagnostics.ts +111 -0
  187. package/src/components/HoverPanel.astro +204 -6
  188. package/src/components/PartOverview.astro +12 -9
  189. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  190. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  191. package/src/components/assessments/AssessmentSet.astro +14 -1
  192. package/src/components/block-heading.ts +38 -0
  193. package/src/components/card-arrow.ts +18 -4
  194. package/src/components/card-carry.ts +406 -0
  195. package/src/components/card-drag.ts +1 -30
  196. package/src/components/card-fetch.ts +87 -0
  197. package/src/components/card-manager.ts +1222 -38
  198. package/src/components/card-memory.ts +5 -1
  199. package/src/components/card-placement.ts +211 -13
  200. package/src/components/card-side.ts +281 -31
  201. package/src/components/citation/CitationLayer.astro +45 -40
  202. package/src/components/diagram/Diagram.astro +123 -44
  203. package/src/components/figure/Figure.astro +11 -0
  204. package/src/components/figure/FloatCaption.astro +4 -17
  205. package/src/components/float-toolbar.ts +274 -0
  206. package/src/components/memo/Memo.astro +21 -10
  207. package/src/components/memo/ShortNote.astro +4 -0
  208. package/src/components/notation/NotationGlossary.astro +22 -17
  209. package/src/components/notation/NotationLayer.astro +300 -79
  210. package/src/components/notation/NotationMath.astro +8 -1
  211. package/src/components/notation/render-notation-math.ts +51 -5
  212. package/src/components/readable-text.ts +25 -0
  213. package/src/components/reference/FloatRefLayer.astro +162 -89
  214. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  215. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  216. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  217. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  218. package/src/components/starlight/LessonExportActions.astro +384 -310
  219. package/src/components/starlight/LessonFooter.astro +47 -9
  220. package/src/components/starlight/PrintController.astro +28 -0
  221. package/src/components/starlight/TextSizeControl.astro +137 -0
  222. package/src/components/starlight/print-controller.ts +380 -0
  223. package/src/components/starlight/section-summary.ts +13 -5
  224. package/src/components/table/Table.astro +13 -21
  225. package/src/components/table/table-truncation.ts +15 -0
  226. package/src/content-config.ts +20 -312
  227. package/src/curriculum/validation.ts +72 -24
  228. package/src/reference/base-abbreviations.mjs +2 -2
  229. package/src/reference/base-operators.mjs +2 -2
  230. package/src/reference/base-roman-labels.mjs +4 -3
  231. package/src/reference/citation-format.mjs +74 -0
  232. package/src/reference/consistency.ts +47 -61
  233. package/src/reference/curriculum-alignment.ts +4 -41
  234. package/src/reference/derived-entries.ts +68 -0
  235. package/src/reference/derived-notation.mjs +121 -0
  236. package/src/reference/diagram-fit.ts +104 -0
  237. package/src/reference/diagram-layout.ts +182 -51
  238. package/src/reference/diagrams-validate.ts +2 -1
  239. package/src/reference/equations-validate.ts +41 -2
  240. package/src/reference/equations.mjs +120 -22
  241. package/src/reference/examples-validate.ts +1 -0
  242. package/src/reference/figure-fit.ts +139 -0
  243. package/src/reference/figure-pictures.ts +96 -0
  244. package/src/reference/figures-validate.ts +2 -1
  245. package/src/reference/gate-abbreviations.ts +1 -1
  246. package/src/reference/gate-course-rules.ts +22 -7
  247. package/src/reference/gate-heading-length.ts +25 -7
  248. package/src/reference/gate-heading-structure.ts +40 -1
  249. package/src/reference/gate-indices.ts +470 -0
  250. package/src/reference/gate-math.ts +102 -13
  251. package/src/reference/gate-mdx-expression.ts +45 -0
  252. package/src/reference/gate-prop-text.ts +145 -0
  253. package/src/reference/gate-prose-notation.ts +19 -5
  254. package/src/reference/gate-retired-keys.ts +101 -0
  255. package/src/reference/gloss.ts +2 -0
  256. package/src/reference/katex-measure.ts +384 -0
  257. package/src/reference/keyed-float-validate.ts +22 -0
  258. package/src/reference/keyed-jsx-floats.mjs +11 -14
  259. package/src/reference/lab-math-scope.ts +15 -1
  260. package/src/reference/math-glyphs.mjs +851 -46
  261. package/src/reference/mixed-content.ts +11 -2
  262. package/src/reference/page-notation.ts +100 -3
  263. package/src/reference/references.ts +14 -0
  264. package/src/reference/registry.ts +154 -8
  265. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  266. package/src/reference/rehype-heading-anchors.mjs +11 -1
  267. package/src/reference/remark-citation.mjs +0 -0
  268. package/src/reference/remark-notation.mjs +84 -20
  269. package/src/reference/render-lab-math.ts +29 -5
  270. package/src/reference/sections-validate.ts +67 -3
  271. package/src/reference/sections.mjs +92 -17
  272. package/src/reference/tables-validate.ts +40 -2
  273. package/src/reference/text-measure.ts +63 -0
  274. package/src/reference/text-widths.ts +133 -0
  275. package/src/reference/types.ts +75 -8
  276. package/src/rules/catalog.ts +205 -29
  277. package/src/rules/settings.ts +10 -0
  278. package/src/session/PreferenceStore.ts +53 -0
  279. package/src/styles/global.css +291 -75
@@ -1,5 +1,5 @@
1
1
  import { ScopeEntry } from './gate-math.js';
2
- import './types.js';
2
+ import { DerivedUse } from './types.js';
3
3
 
4
4
  type ContentSegment = {
5
5
  readonly type: 'text';
@@ -10,8 +10,13 @@ type ContentSegment = {
10
10
  };
11
11
  /**
12
12
  * Split `text` into ordered segments, resolving each math span's glyphs
13
- * against `scope`. Never throws.
13
+ * against `scope`. Never throws. With `derive` (an assessment, which is page
14
+ * math), an indexed symbol takes its derived card (rule N23), and each one
15
+ * used is added to `derived`.
14
16
  */
15
- declare function splitMixedContent(text: string, scope: readonly ScopeEntry[]): ContentSegment[];
17
+ declare function splitMixedContent(text: string, scope: readonly ScopeEntry[], options?: {
18
+ readonly derive?: boolean;
19
+ readonly derived?: Map<string, DerivedUse>;
20
+ }): ContentSegment[];
16
21
 
17
22
  export { type ContentSegment, splitMixedContent };
@@ -1,6 +1,6 @@
1
1
  import { resolveMathGlyphs } from "./math-glyphs.mjs";
2
2
  const MATH_SPAN = /\$\$([\s\S]+?)\$\$|(?<![\\$])\$(?!\s)([^$\n]+?)(?<![\\\s])\$(?!\d)/g;
3
- function splitMixedContent(text, scope) {
3
+ function splitMixedContent(text, scope, options = {}) {
4
4
  const segments = [];
5
5
  let cursor = 0;
6
6
  const pattern = new RegExp(MATH_SPAN);
@@ -12,7 +12,9 @@ function splitMixedContent(text, scope) {
12
12
  const raw = match[1] ?? match[2] ?? "";
13
13
  let latex = raw;
14
14
  try {
15
- latex = resolveMathGlyphs(raw, [...scope]).latex;
15
+ const result = resolveMathGlyphs(raw, [...scope], { derive: options.derive === true });
16
+ latex = result.latex;
17
+ for (const use of result.derived) options.derived?.set(use.key, use);
16
18
  } catch {
17
19
  latex = raw;
18
20
  }
@@ -1,11 +1,12 @@
1
- import { Manifest, ManifestLessonNotation } from '../compiler/manifest.js';
1
+ import { Manifest, ManifestDerivedNotation, ManifestLessonNotation } from '../compiler/manifest.js';
2
+ import { DerivedUse } from './types.js';
2
3
  import '../curriculum/validation.js';
3
4
  import './consistency.js';
4
- import '../course-config-Dsnri9Tk.js';
5
+ import '../course-config-rRtME5UV.js';
5
6
  import '../rules/catalog.js';
6
7
  import '../rules/course-rules.js';
7
8
  import '../compiler/collections.js';
8
- import './types.js';
9
+ import './gate-retired-keys.js';
9
10
  import './lint-ignore.js';
10
11
  import './equations-validate.js';
11
12
  import './tables-validate.js';
@@ -35,6 +36,27 @@ interface PageNotationDependency {
35
36
  * between lessons and defeat page-local notation scope.
36
37
  */
37
38
  declare function registerPageNotationDependencies(locals: object, keys: Iterable<string>, context: string): void;
39
+ /**
40
+ * Record the derived cards (rule N23) a nested server component's math uses —
41
+ * a lab's or a diagram's — so the page ships them with its own, and their
42
+ * base and index entries, which a derived card's formula opens. Keyed by
43
+ * `Astro.locals`, like {@link registerPageNotationDependencies}.
44
+ */
45
+ declare function registerPageDerivedNotation(locals: object, uses: Iterable<DerivedUse>, context: string): void;
46
+ /** The page a glyph table was built for, and whether rule N23 is on there. */
47
+ interface ScopePage {
48
+ readonly locals: object;
49
+ readonly context: string;
50
+ readonly checkIndices: boolean;
51
+ }
52
+ /**
53
+ * Tie a lab's glyph table to the page it renders on (`labMathScope` does), so
54
+ * `renderLabMath` can give its indexed symbols derived cards and register
55
+ * them, and knows whether to hold them to rule N23. A table a course copies
56
+ * loses the tie: its math then composes no derived card.
57
+ */
58
+ declare function bindScopeToPage(scope: object, page: ScopePage): void;
59
+ declare function pageOfScope(scope: object): ScopePage | undefined;
38
60
  declare function pageNotationDependencies(locals: object): PageNotationDependency[];
39
61
  /** Starlight's route id as exposed to nested Astro components. */
40
62
  declare function routeIdFromLocals(locals: object): string | undefined;
@@ -44,6 +66,14 @@ declare function notationKeysInMathNodes(nodes: readonly {
44
66
  readonly properties?: Readonly<Record<string, string>>;
45
67
  readonly children?: readonly unknown[];
46
68
  }[]): string[];
69
+ /**
70
+ * The derived cards (rule N23) a page uses: the ones its body and assessment
71
+ * math use, as the manifest composed them, and the ones a lab or a diagram on
72
+ * it registered while rendering (`locals`), composed here the same way. They
73
+ * travel with the page as hidden sources; their base and index entries are
74
+ * in its payload already.
75
+ */
76
+ declare function derivedNotationForPage(manifest: Manifest, pageId: string, locals?: object): readonly ManifestDerivedNotation[];
47
77
  /**
48
78
  * Extend one compiled page bundle — a lesson's, or a non-chapter page's
49
79
  * (rule S10) — with only the definitions requested by components rendered on
@@ -56,4 +86,4 @@ declare function notationDefinitionsForRenderedPage(manifest: Manifest, pageId:
56
86
  */
57
87
  declare function missingNotationPayloadKeys(html: string): string[];
58
88
 
59
- export { type PageNotationDependency, missingNotationPayloadKeys, notationDefinitionsForRenderedPage, notationKeysInMathNodes, pageNotationDependencies, registerPageNotationDependencies, routeIdFromLocals };
89
+ export { type PageNotationDependency, type ScopePage, bindScopeToPage, derivedNotationForPage, missingNotationPayloadKeys, notationDefinitionsForRenderedPage, notationKeysInMathNodes, pageNotationDependencies, pageOfScope, registerPageDerivedNotation, registerPageNotationDependencies, routeIdFromLocals };
@@ -3,6 +3,7 @@ import {
3
3
  formulaGlyphScope
4
4
  } from "./gloss.js";
5
5
  import { resolveReferenceId } from "./references.js";
6
+ import { derivedNotationEntries } from "./derived-entries.js";
6
7
  const dependenciesByLocals = /* @__PURE__ */ new WeakMap();
7
8
  function registerPageNotationDependencies(locals, keys, context) {
8
9
  let byKey = dependenciesByLocals.get(locals);
@@ -19,6 +20,27 @@ function registerPageNotationDependencies(locals, keys, context) {
19
20
  contexts.add(context);
20
21
  }
21
22
  }
23
+ const derivedByLocals = /* @__PURE__ */ new WeakMap();
24
+ function registerPageDerivedNotation(locals, uses, context) {
25
+ let byKey = derivedByLocals.get(locals);
26
+ if (!byKey) {
27
+ byKey = /* @__PURE__ */ new Map();
28
+ derivedByLocals.set(locals, byKey);
29
+ }
30
+ const entries = [];
31
+ for (const use of uses) {
32
+ byKey.set(use.key, use);
33
+ entries.push(use.base, use.index);
34
+ }
35
+ registerPageNotationDependencies(locals, entries, context);
36
+ }
37
+ const pageByScope = /* @__PURE__ */ new WeakMap();
38
+ function bindScopeToPage(scope, page) {
39
+ pageByScope.set(scope, page);
40
+ }
41
+ function pageOfScope(scope) {
42
+ return pageByScope.get(scope);
43
+ }
22
44
  function pageNotationDependencies(locals) {
23
45
  const byKey = dependenciesByLocals.get(locals);
24
46
  if (!byKey) return [];
@@ -52,7 +74,6 @@ function manifestDefinition(definition, cardsByKey) {
52
74
  notation: definition.notation,
53
75
  label: definition.label,
54
76
  summary: definition.summary,
55
- editorialStatus: definition.status,
56
77
  seeAlso: [...definition.seeAlso],
57
78
  glosses: [...definition.glosses],
58
79
  formulaScope: formulaGlyphScope({
@@ -63,9 +84,36 @@ function manifestDefinition(definition, cardsByKey) {
63
84
  ...definition.formula === void 0 ? {} : { formula: definition.formula }
64
85
  }),
65
86
  ...definition.units === void 0 ? {} : { units: definition.units },
66
- ...definition.formula === void 0 ? {} : { formula: definition.formula }
87
+ ...definition.formula === void 0 ? {} : { formula: definition.formula },
88
+ ...definition.range === void 0 ? {} : { range: definition.range }
67
89
  };
68
90
  }
91
+ function derivedNotationForPage(manifest, pageId, locals) {
92
+ const page = manifest.lessons.find((entry) => entry.id === pageId) ?? manifest.pages.find((entry) => entry.id === pageId);
93
+ const compiled = page?.notation.derived ?? [];
94
+ const registered = [...(locals && derivedByLocals.get(locals)?.values()) ?? []].filter(
95
+ (use) => !compiled.some((entry) => entry.key === use.key)
96
+ );
97
+ if (registered.length === 0) return compiled;
98
+ const definitionsById = new Map(
99
+ manifest.notation.definitions.map((definition) => [definition.id, definition])
100
+ );
101
+ const availableIds = new Set(definitionsById.keys());
102
+ const records = /* @__PURE__ */ new Map();
103
+ for (const use of registered) {
104
+ for (const key of [use.base, use.index]) {
105
+ const id = resolveReferenceId(key, { kind: "page", lessonId: pageId }, availableIds);
106
+ const record = id === void 0 ? void 0 : definitionsById.get(id);
107
+ if (record) records.set(key, record);
108
+ }
109
+ }
110
+ const cardsByKey = new Map(
111
+ manifest.notation.definitions.filter((definition) => definition.kind === "shared").map((definition) => [definition.key, { key: definition.key, notation: definition.notation }])
112
+ );
113
+ return [...compiled, ...derivedNotationEntries(registered, records, cardsByKey)].sort(
114
+ (left, right) => left.key.localeCompare(right.key)
115
+ );
116
+ }
69
117
  function notationDefinitionsForRenderedPage(manifest, pageId, dependencies) {
70
118
  const page = manifest.lessons.find((entry) => entry.id === pageId) ?? manifest.pages.find((entry) => entry.id === pageId);
71
119
  if (!page) return [];
@@ -162,10 +210,14 @@ function missingNotationPayloadKeys(html) {
162
210
  return [...triggers].filter((key) => !entries.has(key)).sort();
163
211
  }
164
212
  export {
213
+ bindScopeToPage,
214
+ derivedNotationForPage,
165
215
  missingNotationPayloadKeys,
166
216
  notationDefinitionsForRenderedPage,
167
217
  notationKeysInMathNodes,
168
218
  pageNotationDependencies,
219
+ pageOfScope,
220
+ registerPageDerivedNotation,
169
221
  registerPageNotationDependencies,
170
222
  routeIdFromLocals
171
223
  };
@@ -2,6 +2,16 @@ import { NotationDiagnostic, SourceSpan, NotationDefinitionScope } from './types
2
2
 
3
3
  declare const NOTATION_KEY_PATTERN: RegExp;
4
4
  declare function isNotationKey(value: string): boolean;
5
+ /**
6
+ * The `[[…]]` prefixes that name something other than a notation entry: an
7
+ * equation, a table, a figure, a diagram, a worked-example set, a knowledge
8
+ * check, a section, a memo. `remark-notation.mjs` and `remark-memo.mjs` route
9
+ * a reference by its prefix before notation keys are looked at, so a key
10
+ * that starts with one could never be reached.
11
+ */
12
+ declare const RESERVED_KEY_PREFIXES: readonly ["eq-", "tab-", "fig-", "dia-", "ex-", "check-", "sec-", "memo-"];
13
+ /** The reserved prefix `key` starts with, if any. */
14
+ declare function reservedKeyPrefix(key: string): string | undefined;
5
15
  declare function sharedDefinitionId(key: string): string;
6
16
  declare function localDefinitionId(lessonId: string, key: string): string;
7
17
  declare function definitionId(scope: NotationDefinitionScope, key: string): string;
@@ -14,4 +24,4 @@ declare function resolveReferenceId(key: string, scope: NotationDefinitionScope,
14
24
  declare function compareSourceSpans(left: SourceSpan | undefined, right: SourceSpan | undefined): number;
15
25
  declare function compareDiagnostics(left: NotationDiagnostic, right: NotationDiagnostic): number;
16
26
 
17
- export { NOTATION_KEY_PATTERN, compareDiagnostics, compareSourceSpans, definitionId, isNotationKey, localDefinitionId, resolveReferenceId, sharedDefinitionId };
27
+ export { NOTATION_KEY_PATTERN, RESERVED_KEY_PREFIXES, compareDiagnostics, compareSourceSpans, definitionId, isNotationKey, localDefinitionId, reservedKeyPrefix, resolveReferenceId, sharedDefinitionId };
@@ -2,6 +2,10 @@ const NOTATION_KEY_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
2
2
  function isNotationKey(value) {
3
3
  return NOTATION_KEY_PATTERN.test(value);
4
4
  }
5
+ const RESERVED_KEY_PREFIXES = ["eq-", "tab-", "fig-", "dia-", "ex-", "check-", "sec-", "memo-"];
6
+ function reservedKeyPrefix(key) {
7
+ return RESERVED_KEY_PREFIXES.find((prefix) => key.startsWith(prefix));
8
+ }
5
9
  function sharedDefinitionId(key) {
6
10
  return `shared:${key}`;
7
11
  }
@@ -33,11 +37,13 @@ function severityRank(severity) {
33
37
  }
34
38
  export {
35
39
  NOTATION_KEY_PATTERN,
40
+ RESERVED_KEY_PREFIXES,
36
41
  compareDiagnostics,
37
42
  compareSourceSpans,
38
43
  definitionId,
39
44
  isNotationKey,
40
45
  localDefinitionId,
46
+ reservedKeyPrefix,
41
47
  resolveReferenceId,
42
48
  sharedDefinitionId
43
49
  };
@@ -3,9 +3,12 @@ import {
3
3
  compareSourceSpans,
4
4
  isNotationKey,
5
5
  localDefinitionId,
6
+ RESERVED_KEY_PREFIXES,
7
+ reservedKeyPrefix,
6
8
  resolveReferenceId,
7
9
  sharedDefinitionId
8
10
  } from "./references.js";
11
+ import { lineOf, proseOnly } from "./gate-prose-notation.js";
9
12
  function buildNotationRegistry(rawInput) {
10
13
  const diagnostics = [];
11
14
  const lessons = buildLessonIndex(rawInput.lessons, diagnostics);
@@ -105,7 +108,16 @@ function buildNotationRegistry(rawInput) {
105
108
  )
106
109
  );
107
110
  }
108
- detectCycles(definitions, edgeIndex, diagnostics);
111
+ const dependencyEdges = /* @__PURE__ */ new Map();
112
+ for (const candidate of definitions.values()) {
113
+ dependencyEdges.set(
114
+ candidate.id,
115
+ uniqueSorted(
116
+ candidate.resolvedReferences.filter((reference) => reference.kind !== "formula").map((reference) => reference.definitionId).filter((definitionId) => definitionId !== candidate.id)
117
+ )
118
+ );
119
+ }
120
+ detectCycles(definitions, dependencyEdges, diagnostics);
109
121
  const localIdsByLesson = /* @__PURE__ */ new Map();
110
122
  for (const candidate of definitions.values()) {
111
123
  if (candidate.kind !== "local") continue;
@@ -132,7 +144,6 @@ function buildNotationRegistry(rawInput) {
132
144
  }
133
145
  bundles.push({
134
146
  lessonId: lesson.lessonId,
135
- status: lesson.status,
136
147
  source: lesson.source,
137
148
  bindings: [...bindingByKey].map(([key, definitionId]) => ({ key, definitionId })).sort(
138
149
  (left, right) => left.key.localeCompare(right.key) || left.definitionId.localeCompare(right.definitionId)
@@ -150,6 +161,21 @@ function buildNotationRegistry(rawInput) {
150
161
  });
151
162
  }
152
163
  }
164
+ for (const candidate of definitions.values()) {
165
+ if (candidate.kind !== "local") continue;
166
+ const shared = definitions.get(sharedDefinitionId(candidate.input.key));
167
+ if (shared === void 0) continue;
168
+ diagnostics.push({
169
+ code: "local-shadows-shared",
170
+ severity: "warning",
171
+ message: `${candidate.lesson.lessonId} defines notation.local ${JSON.stringify(candidate.input.key)}, which is also a shared entry (${shared.input.source.file}): on this page [[${candidate.input.key}]] means "${candidate.input.label}", everywhere else "${shared.input.label}". Give the local entry a key of its own, or use the shared one.`,
172
+ key: candidate.input.key,
173
+ lessonId: candidate.lesson.lessonId,
174
+ definitionId: candidate.id,
175
+ source: candidate.input.source,
176
+ relatedSources: [shared.input.source]
177
+ });
178
+ }
153
179
  for (const candidate of definitions.values()) {
154
180
  if (!reachableDefinitions.has(candidate.id)) {
155
181
  diagnostics.push({
@@ -203,6 +229,7 @@ function buildLessonIndex(rawLessons, diagnostics) {
203
229
  );
204
230
  continue;
205
231
  }
232
+ diagnostics.push(...malformedReferences(lesson));
206
233
  const previous = lessons.get(lesson.lessonId);
207
234
  if (previous !== void 0) {
208
235
  diagnostics.push(
@@ -251,7 +278,21 @@ function buildDefinitionIndex(rawShared, lessons, diagnostics) {
251
278
  diagnostics.push(
252
279
  diagnostic({
253
280
  code: "invalid-key",
254
- message: `${candidate.id} has invalid notation key ${JSON.stringify(candidate.input.key)}`,
281
+ message: `${candidate.id} has invalid notation key ${JSON.stringify(candidate.input.key)}${keyHint(candidate.input.key)}`,
282
+ key: candidate.input.key,
283
+ lessonId: candidate.kind === "local" ? candidate.lesson.lessonId : void 0,
284
+ definitionId: candidate.id,
285
+ source: candidate.input.source
286
+ })
287
+ );
288
+ continue;
289
+ }
290
+ const reserved = reservedKeyPrefix(candidate.input.key);
291
+ if (reserved !== void 0) {
292
+ diagnostics.push(
293
+ diagnostic({
294
+ code: "invalid-key",
295
+ message: `${candidate.id} has notation key ${JSON.stringify(candidate.input.key)}, which starts with the reserved \`${reserved}\`: [[${candidate.input.key}]] is read as a reference to that kind of float and never reaches this entry. Rename the key; none may start with ${RESERVED_KEY_PREFIXES.map((prefix) => `\`${prefix}\``).join(", ")}.`,
255
296
  key: candidate.input.key,
256
297
  lessonId: candidate.kind === "local" ? candidate.lesson.lessonId : void 0,
257
298
  definitionId: candidate.id,
@@ -334,7 +375,7 @@ function validateReferenceKey(reference, owner, diagnostics) {
334
375
  diagnostics.push(
335
376
  diagnostic({
336
377
  code: "invalid-key",
337
- message: `${owner.id} references invalid notation key ${JSON.stringify(reference.key)}`,
378
+ message: `${owner.id} references invalid notation key ${JSON.stringify(reference.key)}${keyHint(reference.key)}`,
338
379
  key: reference.key,
339
380
  lessonId: owner.kind === "local" ? owner.lesson.lessonId : void 0,
340
381
  definitionId: owner.id,
@@ -348,7 +389,7 @@ function validatePageReferenceKey(reference, lesson, diagnostics) {
348
389
  diagnostics.push(
349
390
  diagnostic({
350
391
  code: "invalid-key",
351
- message: `${lesson.lessonId} references invalid notation key ${JSON.stringify(reference.key)}`,
392
+ message: `${lesson.lessonId} references invalid notation key ${JSON.stringify(reference.key)}${keyHint(reference.key)}`,
352
393
  key: reference.key,
353
394
  lessonId: lesson.lessonId,
354
395
  source: reference.source
@@ -399,14 +440,14 @@ function toDefinitionRecord(candidate) {
399
440
  sources: input2.sources,
400
441
  seeAlso: input2.seeAlso,
401
442
  alignment: input2.alignment,
402
- status: input2.status,
403
443
  aiAssisted: input2.aiAssisted,
404
444
  body: input2.body,
405
445
  glosses: input2.glosses,
406
446
  source: input2.source,
407
447
  resolvedReferences: candidate.resolvedReferences,
408
448
  ...input2.formula === void 0 ? {} : { formula: input2.formula },
409
- ...input2.units === void 0 ? {} : { units: input2.units }
449
+ ...input2.units === void 0 ? {} : { units: input2.units },
450
+ ...indexRecordFields(input2)
410
451
  };
411
452
  return record2;
412
453
  }
@@ -423,15 +464,23 @@ function toDefinitionRecord(candidate) {
423
464
  sources: input.sources,
424
465
  seeAlso: input.seeAlso,
425
466
  alignment: input.alignment,
426
- status: candidate.lesson.status,
427
467
  glosses: input.glosses,
428
468
  source: input.source,
429
469
  resolvedReferences: candidate.resolvedReferences,
430
470
  ...input.formula === void 0 ? {} : { formula: input.formula },
431
- ...input.units === void 0 ? {} : { units: input.units }
471
+ ...input.units === void 0 ? {} : { units: input.units },
472
+ ...indexRecordFields(input)
432
473
  };
433
474
  return record;
434
475
  }
476
+ function indexRecordFields(input) {
477
+ return {
478
+ ...input.reads === void 0 ? {} : { reads: input.reads },
479
+ ...input.range === void 0 ? {} : { range: input.range },
480
+ ...input.fixedIndex === void 0 ? {} : { fixedIndex: input.fixedIndex },
481
+ ...input.formulaTemplate === void 0 ? {} : { formulaTemplate: input.formulaTemplate }
482
+ };
483
+ }
435
484
  function definitionFingerprint(candidate) {
436
485
  const references = sortedReferences(candidate.input.references).map(
437
486
  (reference) => ({
@@ -462,7 +511,6 @@ function definitionFingerprint(candidate) {
462
511
  sources: [...input2.sources].sort(),
463
512
  seeAlso: [...input2.seeAlso].sort(),
464
513
  alignment,
465
- status: input2.status,
466
514
  aiAssisted: input2.aiAssisted,
467
515
  body: normalizeText(input2.body),
468
516
  glosses: input2.glosses,
@@ -574,6 +622,52 @@ function uniqueSorted(values) {
574
622
  function sortedSources(values) {
575
623
  return [...values].sort(compareSourceSpans);
576
624
  }
625
+ function malformedReferences(lesson) {
626
+ if (!lesson.body?.includes("[[")) return [];
627
+ const prose = proseOnly(lesson.body, { keepReferences: true });
628
+ const found = [];
629
+ for (let at = prose.indexOf("[["); at !== -1; at = prose.indexOf("[[", at + 2)) {
630
+ if (lesson.body[at - 1] === "\\") continue;
631
+ const closed = /^\[\[[^[\]\n]*\]\]/.exec(prose.slice(at));
632
+ if (closed) {
633
+ at += closed[0].length - 2;
634
+ continue;
635
+ }
636
+ const near = lesson.body.slice(at, at + 40).split("\n")[0];
637
+ const line = lineOf(lesson.body, at) + (lesson.bodyLine ?? 1) - 1;
638
+ found.push(
639
+ diagnostic({
640
+ code: "malformed-reference",
641
+ message: `${lesson.lessonId} opens a reference that never closes, near ${JSON.stringify(near)}: close it with ]], as [[semantic.key]], on the same line.`,
642
+ lessonId: lesson.lessonId,
643
+ source: { ...lesson.source, line }
644
+ })
645
+ );
646
+ }
647
+ return found;
648
+ }
649
+ const FLOAT_KINDS = {
650
+ "eq-": "an equation",
651
+ "tab-": "a table",
652
+ "fig-": "a figure",
653
+ "dia-": "a diagram",
654
+ "ex-": "a worked-example set",
655
+ "check-": "a knowledge check",
656
+ "sec-": "a section",
657
+ "memo-": "a memo"
658
+ };
659
+ function keyHint(key) {
660
+ const reserved = reservedKeyPrefix(key);
661
+ if (reserved !== void 0) {
662
+ return `: [[${reserved}\u2026]] names ${FLOAT_KINDS[reserved]}, whose id is lower-case words and digits joined by "-"; ${JSON.stringify(key.slice(reserved.length))} is not one.`;
663
+ }
664
+ const bar = key.indexOf("|");
665
+ if (bar > 0) {
666
+ const target = key.slice(0, bar).trim();
667
+ return `: a [[\u2026]] reference takes no link text of its own \u2014 it reads as the entry's label. Write [[${target}]], with the words around it.`;
668
+ }
669
+ return ': a key is lower-case words and digits joined by "-" or "." (motion.launch-speed).';
670
+ }
577
671
  function diagnostic(input) {
578
672
  return { ...input, severity: "error" };
579
673
  }
@@ -1,6 +1,15 @@
1
+ /**
2
+ * KaTeX writes each formula twice: as MathML, whose `<annotation>` holds the
3
+ * LaTeX source, for assistive technology, and as the visible glyphs. Mark the
4
+ * MathML copy `data-pagefind-ignore`, so the site search indexes a formula
5
+ * once, as it reads, and never its LaTeX (`\explain{…}{…}`, `\dfrac`).
6
+ */
7
+ export function markMathmlForSearch(node: any): void;
1
8
  /**
2
9
  * rehype-katex reports parse failures as non-fatal VFile messages and renders
3
10
  * a `.katex-error` fallback. Turn those recovered failures into compiler
4
11
  * errors so Astro cannot successfully build a lesson containing broken math.
12
+ * The pass every course runs right after KaTeX, it also keeps the LaTeX
13
+ * annotations out of the site search (`markMathmlForSearch`).
5
14
  */
6
15
  export default function rehypeFailKatexErrors(): (tree: any, file: any) => void;
@@ -31,13 +31,32 @@ function katexErrors(node, output = []) {
31
31
  return output;
32
32
  }
33
33
 
34
+ /**
35
+ * KaTeX writes each formula twice: as MathML, whose `<annotation>` holds the
36
+ * LaTeX source, for assistive technology, and as the visible glyphs. Mark the
37
+ * MathML copy `data-pagefind-ignore`, so the site search indexes a formula
38
+ * once, as it reads, and never its LaTeX (`\explain{…}{…}`, `\dfrac`).
39
+ */
40
+ export function markMathmlForSearch(node) {
41
+ if (!node || typeof node !== 'object') return;
42
+ const classes = node.properties?.className;
43
+ if (node.type === 'element' && Array.isArray(classes) && classes.includes('katex-mathml')) {
44
+ node.properties.dataPagefindIgnore = '';
45
+ return;
46
+ }
47
+ if (Array.isArray(node.children)) node.children.forEach(markMathmlForSearch);
48
+ }
49
+
34
50
  /**
35
51
  * rehype-katex reports parse failures as non-fatal VFile messages and renders
36
52
  * a `.katex-error` fallback. Turn those recovered failures into compiler
37
53
  * errors so Astro cannot successfully build a lesson containing broken math.
54
+ * The pass every course runs right after KaTeX, it also keeps the LaTeX
55
+ * annotations out of the site search (`markMathmlForSearch`).
38
56
  */
39
57
  export default function rehypeFailKatexErrors() {
40
58
  return function failOnKatexErrors(tree, file) {
59
+ markMathmlForSearch(tree);
41
60
  const errors = katexErrors(tree);
42
61
  if (errors.length === 0) return;
43
62
 
@@ -69,6 +69,11 @@ const HEADING_TAG = /^h([1-6])$/;
69
69
  const LINK_ICON_PATH =
70
70
  'm12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42';
71
71
 
72
+ /**
73
+ * A heading's text as a reader hears it. KaTeX writes each formula twice —
74
+ * MathML, whose `<annotation>` holds the LaTeX, and the `aria-hidden` glyphs —
75
+ * so a formula is read once, from its MathML, without the LaTeX.
76
+ */
72
77
  function headingText(node) {
73
78
  let text = '';
74
79
  const walk = (current) => {
@@ -77,10 +82,15 @@ function headingText(node) {
77
82
  text += String(current.value ?? '');
78
83
  return;
79
84
  }
85
+ if (current.type === 'element') {
86
+ const classes = current.properties?.className;
87
+ if (current.tagName === 'annotation') return;
88
+ if (Array.isArray(classes) && classes.includes('katex-html')) return;
89
+ }
80
90
  if (Array.isArray(current.children)) current.children.forEach(walk);
81
91
  };
82
92
  walk(node);
83
- return text.replace(/\s+/g, ' ').trim();
93
+ return text.replace(/[\u200b\u2061-\u2064]/g, '').replace(/\s+/g, ' ').trim();
84
94
  }
85
95
 
86
96
  // A heading's marker, e.g. `## Some title [collapsed]`, stripped from the
Binary file