explico 0.13.1 → 0.14.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 (239) hide show
  1. package/CHANGELOG.md +879 -1
  2. package/README.md +200 -30
  3. package/dist/compiler/astro.d.ts +15 -2
  4. package/dist/compiler/astro.js +40 -11
  5. package/dist/compiler/cli-main.d.ts +43 -0
  6. package/dist/compiler/cli-main.js +392 -0
  7. package/dist/compiler/cli.d.ts +424 -9
  8. package/dist/compiler/cli.js +1289 -8
  9. package/dist/compiler/collections.d.ts +88 -1
  10. package/dist/compiler/collections.js +170 -25
  11. package/dist/compiler/course-config.d.ts +1 -1
  12. package/dist/compiler/course-config.js +105 -1
  13. package/dist/compiler/decks.d.ts +161 -0
  14. package/dist/compiler/decks.js +235 -0
  15. package/dist/compiler/exports/combined-html.d.ts +5 -1
  16. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  17. package/dist/compiler/exports/deck-fit.js +90 -0
  18. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  19. package/dist/compiler/exports/export-chapters.js +64 -5
  20. package/dist/compiler/exports/export-pages.d.ts +30 -3
  21. package/dist/compiler/exports/export-pages.js +24 -1
  22. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  23. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  24. package/dist/compiler/exports/latex/cache.js +129 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  26. package/dist/compiler/exports/latex/capture.js +3 -1
  27. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  28. package/dist/compiler/exports/latex/compile.js +12 -1
  29. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  30. package/dist/compiler/exports/latex/deck.js +349 -0
  31. package/dist/compiler/exports/latex/document.d.ts +51 -6
  32. package/dist/compiler/exports/latex/document.js +71 -12
  33. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  34. package/dist/compiler/exports/latex/explico.cls +13 -183
  35. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  36. package/dist/compiler/exports/latex/serialize.js +4 -2
  37. package/dist/compiler/exports/latex-export.d.ts +24 -2
  38. package/dist/compiler/exports/latex-export.js +107 -26
  39. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  40. package/dist/compiler/exports/markdown/serialize.js +85 -8
  41. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  42. package/dist/compiler/exports/markdown-export.js +50 -11
  43. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  44. package/dist/compiler/exports/standalone-html.js +3 -3
  45. package/dist/compiler/exports/static-tree.d.ts +38 -1
  46. package/dist/compiler/exports/static-tree.js +120 -4
  47. package/dist/compiler/knowledge-version.d.ts +26 -2
  48. package/dist/compiler/knowledge-version.js +27 -17
  49. package/dist/compiler/lesson-derivation.d.ts +143 -6
  50. package/dist/compiler/lesson-derivation.js +145 -13
  51. package/dist/compiler/manifest.d.ts +133 -4
  52. package/dist/compiler/manifest.js +359 -58
  53. package/dist/compiler/page-versions.d.ts +160 -0
  54. package/dist/compiler/page-versions.js +198 -0
  55. package/dist/compiler/render-diagnostics.d.ts +5 -1
  56. package/dist/compiler/resolution-report.d.ts +24 -6
  57. package/dist/compiler/resolution-report.js +22 -4
  58. package/dist/compiler/validate-content.d.ts +7 -0
  59. package/dist/compiler/validate-content.js +160 -0
  60. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  61. package/dist/curriculum/competency-graph.d.ts +54 -0
  62. package/dist/curriculum/competency-graph.js +41 -0
  63. package/dist/reference/base-library.d.mts +8 -0
  64. package/dist/reference/base-library.mjs +19 -0
  65. package/dist/reference/consistency.d.ts +8 -4
  66. package/dist/reference/consistency.js +76 -26
  67. package/dist/reference/deck-remark.d.mts +123 -0
  68. package/dist/reference/deck-remark.mjs +600 -0
  69. package/dist/reference/deck-source.d.ts +107 -0
  70. package/dist/reference/deck-source.js +140 -0
  71. package/dist/reference/decks-validate.d.ts +133 -0
  72. package/dist/reference/decks-validate.js +359 -0
  73. package/dist/reference/derived-entries.d.ts +5 -1
  74. package/dist/reference/diagram-fit.d.ts +1 -1
  75. package/dist/reference/figure-symbols.d.ts +23 -6
  76. package/dist/reference/figure-symbols.js +51 -15
  77. package/dist/reference/float-name.d.ts +43 -0
  78. package/dist/reference/float-name.js +58 -0
  79. package/dist/reference/folds-validate.d.ts +1 -1
  80. package/dist/reference/gate-abbreviations.d.ts +1 -1
  81. package/dist/reference/gate-indices.js +5 -4
  82. package/dist/reference/gate-math.d.ts +29 -2
  83. package/dist/reference/gate-math.js +99 -18
  84. package/dist/reference/gate-part-pages.d.ts +1 -1
  85. package/dist/reference/lab-katex-validate.d.ts +10 -6
  86. package/dist/reference/lab-katex-validate.js +17 -9
  87. package/dist/reference/lab-math-scope.js +29 -4
  88. package/dist/reference/math-glyphs.d.mts +25 -0
  89. package/dist/reference/math-glyphs.mjs +189 -25
  90. package/dist/reference/page-notation.d.ts +22 -3
  91. package/dist/reference/page-notation.js +32 -10
  92. package/dist/reference/references.d.ts +14 -1
  93. package/dist/reference/references.js +13 -0
  94. package/dist/reference/registry-scope.d.mts +33 -0
  95. package/dist/reference/registry-scope.mjs +34 -0
  96. package/dist/reference/registry.js +29 -10
  97. package/dist/reference/rehype-deck.d.mts +6 -0
  98. package/dist/reference/rehype-deck.mjs +286 -0
  99. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  100. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  101. package/dist/reference/remark-notation.mjs +249 -40
  102. package/dist/reference/source-lines.d.mts +12 -0
  103. package/dist/reference/source-lines.mjs +65 -0
  104. package/dist/reference/types.d.ts +28 -0
  105. package/dist/rules/catalog.d.ts +21 -4
  106. package/dist/rules/catalog.js +221 -2
  107. package/dist/rules/course-rules.d.ts +5 -1
  108. package/dist/rules/course-rules.js +17 -3
  109. package/dist/rules/course-rules.schema.json +12 -0
  110. package/dist/rules/in-effect.d.ts +13 -3
  111. package/dist/rules/in-effect.js +30 -7
  112. package/dist/rules/rules.schema.json +259 -1
  113. package/dist/rules/settings.d.ts +1 -1
  114. package/dist/session/PreferenceStore.d.ts +6 -0
  115. package/dist/session/PreferenceStore.js +18 -1
  116. package/dist/session/deck-memory.d.ts +116 -0
  117. package/dist/session/deck-memory.js +143 -0
  118. package/dist/session/reading-memory.d.ts +96 -0
  119. package/dist/session/reading-memory.js +133 -0
  120. package/docs/VERSIONING.md +21 -18
  121. package/docs/authoring/README.md +29 -9
  122. package/docs/authoring/agents.md +63 -16
  123. package/docs/authoring/components.md +15 -0
  124. package/docs/authoring/deck.md +588 -0
  125. package/docs/authoring/migrating.md +1045 -16
  126. package/docs/authoring/notation.md +49 -6
  127. package/docs/authoring/structure.md +80 -1
  128. package/docs/authoring/versioning.md +54 -2
  129. package/docs/authoring/visualizations.md +7 -2
  130. package/docs/course-agent/AGENTS.md +37 -0
  131. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  132. package/docs/course-agent/skills/draft-lesson/SKILL.md +37 -0
  133. package/docs/course-agent/skills/migrate-course/SKILL.md +31 -0
  134. package/docs/course-agent/skills/release-content/SKILL.md +24 -0
  135. package/docs/diagram-scaling.md +148 -0
  136. package/docs/privacy.md +59 -0
  137. package/package.json +8 -2
  138. package/src/compiler/astro.ts +60 -21
  139. package/src/compiler/cli-main.ts +469 -0
  140. package/src/compiler/cli.ts +1905 -16
  141. package/src/compiler/collections.ts +284 -25
  142. package/src/compiler/course-config.ts +267 -0
  143. package/src/compiler/decks.ts +455 -0
  144. package/src/compiler/exports/deck-fit.ts +127 -0
  145. package/src/compiler/exports/export-chapters.ts +105 -6
  146. package/src/compiler/exports/export-pages.ts +46 -2
  147. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  148. package/src/compiler/exports/latex/cache.ts +183 -0
  149. package/src/compiler/exports/latex/capture.ts +5 -2
  150. package/src/compiler/exports/latex/compile.ts +18 -0
  151. package/src/compiler/exports/latex/deck.ts +419 -0
  152. package/src/compiler/exports/latex/document.ts +117 -21
  153. package/src/compiler/exports/latex/explico-common.sty +198 -0
  154. package/src/compiler/exports/latex/explico.cls +13 -183
  155. package/src/compiler/exports/latex/serialize.ts +25 -21
  156. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  157. package/src/compiler/exports/latex-export.ts +187 -45
  158. package/src/compiler/exports/markdown/serialize.ts +118 -9
  159. package/src/compiler/exports/markdown-export.ts +56 -11
  160. package/src/compiler/exports/standalone-html.ts +8 -7
  161. package/src/compiler/exports/static-tree.ts +175 -3
  162. package/src/compiler/knowledge-version.ts +56 -21
  163. package/src/compiler/lesson-derivation.ts +282 -16
  164. package/src/compiler/manifest.ts +558 -63
  165. package/src/compiler/page-versions.ts +359 -0
  166. package/src/compiler/resolution-report.ts +38 -7
  167. package/src/compiler/validate-content.ts +212 -0
  168. package/src/components/CourseContents.astro +124 -6
  169. package/src/components/CurriculumMap.astro +49 -1
  170. package/src/components/FloatIndex.astro +1 -1
  171. package/src/components/PartOverview.astro +51 -5
  172. package/src/components/assessments/AssessmentSet.astro +41 -15
  173. package/src/components/block-heading.ts +15 -6
  174. package/src/components/card-carry.ts +7 -4
  175. package/src/components/card-manager.ts +14 -2
  176. package/src/components/card-memory.ts +13 -9
  177. package/src/components/deck/DeckEnhancer.astro +13 -0
  178. package/src/components/deck/deck-context.ts +104 -0
  179. package/src/components/deck/deck-controller.ts +1287 -0
  180. package/src/components/deck/deck-map.ts +91 -0
  181. package/src/components/deck-lane.ts +163 -0
  182. package/src/components/diagram/Diagram.astro +131 -86
  183. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  184. package/src/components/figure/Figure.astro +7 -2
  185. package/src/components/figure/FloatCaption.astro +5 -3
  186. package/src/components/fold/Fold.astro +2 -2
  187. package/src/components/notation/NotationFormula.astro +81 -11
  188. package/src/components/notation/NotationGlossary.astro +132 -1
  189. package/src/components/notation/NotationLayer.astro +15 -7
  190. package/src/components/starlight/AboutPanel.astro +186 -3
  191. package/src/components/starlight/CollapsibleSections.astro +24 -3
  192. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  193. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  194. package/src/components/starlight/LayoutHeader.astro +17 -0
  195. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  196. package/src/components/starlight/LayoutSidebar.astro +807 -21
  197. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  198. package/src/components/starlight/LessonExportActions.astro +27 -9
  199. package/src/components/starlight/LessonFooter.astro +59 -0
  200. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  201. package/src/components/starlight/TextSizeControl.astro +24 -13
  202. package/src/components/starlight/page-marks.ts +139 -0
  203. package/src/components/starlight/section-summary.ts +1 -1
  204. package/src/components/starlight/sidebar-editions.ts +245 -0
  205. package/src/components/table/Table.astro +15 -7
  206. package/src/content-config.ts +87 -1
  207. package/src/curriculum/competency-graph.ts +95 -0
  208. package/src/reference/base-library.mjs +19 -0
  209. package/src/reference/consistency.ts +126 -39
  210. package/src/reference/deck-remark.mjs +600 -0
  211. package/src/reference/deck-source.ts +273 -0
  212. package/src/reference/decks-validate.ts +604 -0
  213. package/src/reference/diagram-layout.ts +3 -1
  214. package/src/reference/figure-symbols.ts +108 -31
  215. package/src/reference/folds-validate.ts +1 -1
  216. package/src/reference/gate-indices.ts +5 -3
  217. package/src/reference/gate-math.ts +169 -25
  218. package/src/reference/lab-katex-validate.ts +30 -15
  219. package/src/reference/lab-math-scope.ts +34 -3
  220. package/src/reference/math-glyphs.mjs +189 -25
  221. package/src/reference/page-notation.ts +69 -15
  222. package/src/reference/references.ts +28 -0
  223. package/src/reference/registry-scope.mjs +34 -0
  224. package/src/reference/registry.ts +41 -11
  225. package/src/reference/rehype-deck.mjs +286 -0
  226. package/src/reference/rehype-heading-anchors.mjs +48 -6
  227. package/src/reference/remark-notation.mjs +249 -40
  228. package/src/reference/source-lines.mjs +65 -0
  229. package/src/reference/types.ts +28 -0
  230. package/src/rules/catalog.ts +257 -4
  231. package/src/rules/course-rules.ts +21 -1
  232. package/src/rules/in-effect.ts +54 -8
  233. package/src/session/PreferenceStore.ts +23 -0
  234. package/src/session/deck-memory.ts +263 -0
  235. package/src/session/reading-memory.ts +243 -0
  236. package/src/styles/deck.css +1150 -0
  237. package/src/styles/global.css +958 -27
  238. package/src/styles/zones.css +277 -0
  239. /package/src/{components → reference}/float-name.ts +0 -0
@@ -0,0 +1,59 @@
1
+ # What an explico site keeps in a reader's browser
2
+
3
+ An explico course sets **no cookies**, loads **nothing from another site**
4
+ (fonts, KaTeX and search are served by the site itself), and sends nothing
5
+ anywhere: there is no analytics. What it keeps lives in the reader's own
6
+ browser storage (`localStorage` for what outlives the tab, `sessionStorage`
7
+ for what does not), and is read only by the pages of the course.
8
+
9
+ EU rules treat browser storage like cookies: storing something needs the
10
+ reader's consent unless it is strictly necessary for what the reader asked
11
+ for, such as a preference they set themselves (ePrivacy Directive Art. 5(3);
12
+ EDPB Guidelines 2/2023 on its technical scope; Article 29 Working Party
13
+ Opinion 04/2012 on the exemptions). explico is built so that one question
14
+ covers everything that needs consent.
15
+
16
+ ## Settings: kept because the reader set them
17
+
18
+ | What | Key | Written when |
19
+ | --- | --- | --- |
20
+ | Theme | `starlight-theme` (Starlight's own) | The reader picks one. Starlight also writes an empty value on each page load. |
21
+ | Text size (phones) | `explico:display:text-size` | The reader picks one |
22
+ | Panels folded away or resized | `explico:layout:*` | The reader folds or drags a panel |
23
+ | A part shown compact or labelled | `explico-sidebar-groups` | The reader clicks the part |
24
+ | Which edition a lesson opens as, and whether the other is listed | `explico-sidebar-editions` | The reader switches either in the sidebar's foot |
25
+ | A section folded or opened | `disclosure:<page>:<section>` | The reader folds or opens it (never on a mere visit) |
26
+ | "Carry cards" on or off; card width; article kept left | `explico:cards:carry`, `explico:layout:side-card-width*`, `explico:layout:keep-article-left` | The reader sets it |
27
+
28
+ These need no answer: each one is a choice the reader made, kept so it stays
29
+ made.
30
+
31
+ ## Reading memory: kept only with the reader's yes
32
+
33
+ | What | Key | With a yes | Without one |
34
+ | --- | --- | --- | --- |
35
+ | The chapters a reader opened, as they were (the sidebar marks; the line under a changed page's title) | `explico-page-marks:<base>` | Recorded on each visit | Not recorded; no marks |
36
+ | The cards kept open on a page | `explico:cards:<page>` | Kept in the browser | Kept for the tab only |
37
+ | The cards carried from page to page | `explico:cards:carried:<base>` | Kept in the browser | Kept for the tab only |
38
+ | The frame and step each deck stopped at, and the frames Present mode showed, as they were (each slide's "Shown" or "Not shown yet" on a deck's page, its slide map; "Continue at slide N") | `explico:decks:<base>` | Recorded as the reader presents; the frames the tab recorded before the yes are kept with it | Before an answer, kept and marked for the tab only; after a no, the stopping place alone, for the tab: the frames shown are cleared, and none is recorded |
39
+
40
+ The course asks once ("Remember your reading?", `readingMemory: "ask"`, the
41
+ default), at the bottom of the window, with two answers of equal weight. Until
42
+ the reader answers, nothing of their reading outlives the tab. A no deletes
43
+ any reading memory at once. The answer (`explico-reading-memory:<base>`) is
44
+ the one thing kept without asking, as storing a consent choice is, and stands
45
+ for 13 months. The `[?]` panel's "In this browser" section changes it, and
46
+ "Forget what this course stored" deletes every key above for the course and
47
+ asks again. `readingMemory: "off"` never asks and never keeps reading memory.
48
+
49
+ ## For the course's own privacy notice
50
+
51
+ - The site's host sees each request, as any web server does: GitHub Pages,
52
+ Cloudflare Pages or the course's own server keeps its access logs under its
53
+ own privacy terms. Name the host.
54
+ - Anything the course adds outside explico changes this page: an analytics
55
+ script, fonts or scripts from a CDN, an embedded video. Each sends the
56
+ reader's address to another site, and most need consent of their own.
57
+ - `src/session/reading-memory.ts` is the source of truth for what counts as
58
+ reading memory; `tests/e2e/reading-memory.spec.ts` in the playground checks
59
+ that an unanswered browser keeps nothing of the reading.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "explico",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "Build-time engine for prerequisite-aware, notation-rigorous course content: one validated content model, a semantic notation/citation/equation system with a completeness gate, and a deterministic JSON manifest.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -31,11 +31,15 @@
31
31
  "CHANGELOG.md",
32
32
  "docs/VERSIONING.md",
33
33
  "docs/katex-bracket-scaling.md",
34
- "docs/authoring"
34
+ "docs/diagram-scaling.md",
35
+ "docs/privacy.md",
36
+ "docs/authoring",
37
+ "docs/course-agent"
35
38
  ],
36
39
  "dependencies": {
37
40
  "@dagrejs/dagre": "3.1.1",
38
41
  "esbuild": "0.27.7",
42
+ "github-slugger": "2.0.0",
39
43
  "mdast-util-gfm-table": "2.0.0",
40
44
  "mdast-util-math": "3.0.0",
41
45
  "mdast-util-to-markdown": "2.1.2",
@@ -70,7 +74,9 @@
70
74
  },
71
75
  "scripts": {
72
76
  "build": "tsup && node scripts/copy-assets.mjs && node scripts/fix-esm-extensions.mjs && tsc -p tsconfig.dts-mjs.json && node scripts/gen-rules-schema.mjs",
77
+ "check:dist-imports": "node scripts/check-dist-imports.mjs",
73
78
  "check:release-migration": "node scripts/check-release-migration.mjs",
79
+ "release": "node scripts/release.mjs",
74
80
  "test": "vitest run",
75
81
  "test:watch": "vitest"
76
82
  }
@@ -12,7 +12,11 @@ import {
12
12
  writeManifest,
13
13
  } from './manifest';
14
14
  import { missingNotationPayloadKeys } from '../reference/page-notation';
15
- import { assertFigureSymbolsBound } from '../reference/figure-symbols';
15
+ import {
16
+ assertNoUnboundFigureSymbols,
17
+ unboundFigureSymbols,
18
+ type PageUnboundFigureSymbols,
19
+ } from '../reference/figure-symbols';
16
20
  import { undrawnFigurePictures, undrawnFigureWarning } from '../reference/figure-pictures';
17
21
  import {
18
22
  pageRenderDiagnostics,
@@ -25,7 +29,8 @@ import { writeLatexExports } from './exports/latex-export';
25
29
  import { writeMarkdownExports } from './exports/markdown-export';
26
30
  import { prepareChapters } from './exports/export-chapters';
27
31
  import { writeDownloadIndex } from './exports/download-index';
28
- import { standaloneFilename, standalonePageSlugs } from './exports/export-pages';
32
+ import { checkFrameFit } from './exports/deck-fit';
33
+ import { standaloneDownloads, standaloneFilename } from './exports/export-pages';
29
34
  import remarkAbbreviations from '../reference/remark-abbreviations.mjs';
30
35
  import remarkCitation from '../reference/remark-citation.mjs';
31
36
  import remarkNotation from '../reference/remark-notation.mjs';
@@ -160,10 +165,11 @@ type MarkdownConfig = AstroConfig['markdown'];
160
165
 
161
166
  /**
162
167
  * Every configured remark plugin, regardless of which of Astro's two
163
- * mutually-exclusive wiring surfaces a course uses: the deprecated flat
164
- * `markdown.remarkPlugins` array, or a `markdown.processor` (e.g.
165
- * `unified({ remarkPlugins })`) whose own `options.remarkPlugins` a
166
- * non-`unified` processor (e.g. `satteri()`) may not have at all.
168
+ * mutually-exclusive wiring surfaces a course uses: a `markdown.processor`
169
+ * (e.g. `unified({ remarkPlugins })`, the form Astro 7.3 asks for) whose own
170
+ * `options.remarkPlugins` a non-`unified` processor (e.g. `satteri()`) may not
171
+ * have at all, or the flat `markdown.remarkPlugins` array Astro 7.3 deprecates
172
+ * and a course not yet moved still has.
167
173
  */
168
174
  function configuredRemarkPlugins(markdown: MarkdownConfig): readonly unknown[] {
169
175
  const direct = markdown?.remarkPlugins ?? [];
@@ -200,22 +206,24 @@ export function assertRemarkPluginsWired(
200
206
  !remarkPluginIsWired(remarkPlugins, remarkAbbreviations)
201
207
  ) {
202
208
  missing.push(
203
- `remarkAbbreviations is missing from markdown.remarkPlugins, but ` +
209
+ `remarkAbbreviations is missing from the remark plugins of astro.config.mjs, but ` +
204
210
  `manifest.abbreviations has ${manifest.abbreviations.length} ` +
205
211
  `entr${manifest.abbreviations.length === 1 ? 'y' : 'ies'}. Import it from ` +
206
212
  `'explico/reference/remark-abbreviations.mjs' and add ` +
207
- `[remarkAbbreviations, { abbreviations: manifest.abbreviations }] to ` +
208
- `markdown.remarkPlugins — see docs/authoring/migrating.md#4-rule-n14.`,
213
+ `[remarkAbbreviations, { abbreviations: manifest.abbreviations }] to the ` +
214
+ `\`remarkPlugins\` of \`markdown.processor: unified({ … })\` — see ` +
215
+ `docs/authoring/migrating.md#4-rule-n14.`,
209
216
  );
210
217
  }
211
218
 
212
219
  if (manifest.sources.length > 0 && !remarkPluginIsWired(remarkPlugins, remarkCitation)) {
213
220
  missing.push(
214
- `remarkCitation is missing from markdown.remarkPlugins, but ` +
221
+ `remarkCitation is missing from the remark plugins of astro.config.mjs, but ` +
215
222
  `manifest.sources has ${manifest.sources.length} ` +
216
223
  `entr${manifest.sources.length === 1 ? 'y' : 'ies'}. Import it from ` +
217
224
  `'explico/reference/remark-citation.mjs' and add ` +
218
- `[remarkCitation, { sources: manifest.sources }] to markdown.remarkPlugins.`,
225
+ `[remarkCitation, { sources: manifest.sources }] to the \`remarkPlugins\` of ` +
226
+ `\`markdown.processor: unified({ … })\`.`,
219
227
  );
220
228
  }
221
229
 
@@ -224,11 +232,12 @@ export function assertRemarkPluginsWired(
224
232
  !remarkPluginIsWired(remarkPlugins, remarkNotation)
225
233
  ) {
226
234
  missing.push(
227
- `remarkNotation is missing from markdown.remarkPlugins, but ` +
235
+ `remarkNotation is missing from the remark plugins of astro.config.mjs, but ` +
228
236
  `manifest.notation.raw has ${manifest.notation.raw.length} ` +
229
237
  `entr${manifest.notation.raw.length === 1 ? 'y' : 'ies'}. Import it from ` +
230
238
  `'explico/reference/remark-notation.mjs' and add ` +
231
- `[remarkNotation, createRemarkNotationOptions(manifest)] to markdown.remarkPlugins.`,
239
+ `[remarkNotation, createRemarkNotationOptions(manifest)] to the \`remarkPlugins\` of ` +
240
+ `\`markdown.processor: unified({ … })\`.`,
232
241
  );
233
242
  }
234
243
 
@@ -243,25 +252,41 @@ export function assertRemarkPluginsWired(
243
252
 
244
253
  /**
245
254
  * Check every built page, and return the findings only a rendered page gives
246
- * (`render-diagnostics.ts`) for `content warnings`.
255
+ * (`render-diagnostics.ts`) for `content warnings`. A page that fails a check
256
+ * does not stop the walk: every page is read first, then the build throws once
257
+ * with the whole list, so it reports each page at once and not one per run.
258
+ * Exported for the test.
247
259
  */
248
- async function validateGeneratedPages(
260
+ export async function validateGeneratedPages(
249
261
  directory: URL,
250
- manifest: Manifest,
262
+ manifest: Pick<Manifest, 'lessons'>,
251
263
  warn: (message: string) => void,
252
264
  ): Promise<RenderDiagnostic[]> {
253
265
  const root = fileURLToPath(directory);
254
266
  const rendered: RenderDiagnostic[] = [];
267
+ const failures: string[] = [];
268
+ const unbound: PageUnboundFigureSymbols[] = [];
269
+ const messageOf = (error: unknown): string =>
270
+ error instanceof Error ? error.message : String(error);
255
271
  const walk = async (path: string): Promise<void> => {
256
- for (const entry of await readdir(path, { withFileTypes: true })) {
272
+ // A fixed order, so the list a build prints is the same on every machine.
273
+ const entries = (await readdir(path, { withFileTypes: true })).sort((a, b) =>
274
+ a.name.localeCompare(b.name),
275
+ );
276
+ for (const entry of entries) {
257
277
  const child = join(path, entry.name);
258
278
  if (entry.isDirectory()) {
259
279
  await walk(child);
260
280
  } else if (entry.isFile() && entry.name.endsWith('.html')) {
261
281
  const html = await readFile(child, 'utf8');
262
282
  const page = relative(root, child);
263
- assertNotationPayloadHtml(page, html);
264
- assertFigureSymbolsBound(page, html);
283
+ try {
284
+ assertNotationPayloadHtml(page, html);
285
+ } catch (error) {
286
+ failures.push(messageOf(error));
287
+ }
288
+ const figures = unboundFigureSymbols(html);
289
+ if (figures.length > 0) unbound.push({ page, figures });
265
290
  const undrawn = undrawnFigurePictures(html);
266
291
  if (undrawn.length > 0) warn(undrawnFigureWarning(page, undrawn));
267
292
  rendered.push(...pageRenderDiagnostics(manifest, page, html));
@@ -269,6 +294,12 @@ async function validateGeneratedPages(
269
294
  }
270
295
  };
271
296
  await walk(root);
297
+ try {
298
+ assertNoUnboundFigureSymbols(unbound);
299
+ } catch (error) {
300
+ failures.push(messageOf(error));
301
+ }
302
+ if (failures.length > 0) throw new Error(failures.join('\n\n'));
272
303
  return rendered;
273
304
  }
274
305
 
@@ -407,7 +438,7 @@ export function explicoContentRefresh(
407
438
  const slug = matchStandaloneDownload(
408
439
  pathname,
409
440
  configuredDevelopment.base,
410
- standalonePageSlugs(manifest),
441
+ standaloneDownloads(manifest).map(({ download }) => download),
411
442
  );
412
443
  if (!slug) return next();
413
444
 
@@ -419,7 +450,7 @@ export function explicoContentRefresh(
419
450
  );
420
451
  developmentDownloads ??= buildDevelopmentDownloads(
421
452
  configuredDevelopment,
422
- standalonePageSlugs(manifest),
453
+ standaloneDownloads(manifest).map(({ download }) => download),
423
454
  );
424
455
  const currentDownloads = developmentDownloads;
425
456
  currentDownloads.catch(() => {
@@ -456,6 +487,14 @@ export function explicoContentRefresh(
456
487
  for (const diagnostic of writeRenderDiagnostics(rendered)) {
457
488
  logger.warn(`[${diagnostic.code}] ${diagnostic.file}: ${diagnostic.message}`);
458
489
  }
490
+ // Rule D2: every frame of every deck fits its canvas, measured in a browser.
491
+ const overflowing = await checkFrameFit(fileURLToPath(dir), manifest, { logger });
492
+ if (overflowing.length > 0) {
493
+ throw new Error(
494
+ `${overflowing.length} deck frame(s) do not fit their canvas (rule D2, frame-overflow):\n` +
495
+ overflowing.map((finding) => ` - ${finding.message}`).join('\n'),
496
+ );
497
+ }
459
498
  await writeStandaloneLessonExports(dir, manifest);
460
499
  await writeCombinedExports(dir, manifest);
461
500
  // The print exports share one reading of the chapters and one