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.
- package/CHANGELOG.md +1292 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +11 -4
- package/dist/compiler/astro.js +48 -15
- package/dist/compiler/cli.d.ts +27 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +33 -2
- package/dist/compiler/collections.js +91 -19
- package/dist/compiler/content-schema.d.ts +31 -0
- package/dist/compiler/content-schema.js +82 -0
- package/dist/compiler/content-schemas.d.ts +422 -0
- package/dist/compiler/content-schemas.js +229 -0
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +30 -2
- package/dist/compiler/exports/combined-html.d.ts +97 -0
- package/dist/compiler/exports/combined-html.js +492 -0
- package/dist/compiler/exports/download-index.d.ts +9 -0
- package/dist/compiler/exports/download-index.js +33 -0
- package/dist/compiler/exports/export-chapters.d.ts +98 -0
- package/dist/compiler/exports/export-chapters.js +164 -0
- package/dist/compiler/exports/export-pages.d.ts +48 -0
- package/dist/compiler/exports/export-pages.js +22 -0
- package/dist/compiler/exports/html-tree.d.ts +22 -0
- package/dist/compiler/exports/html-tree.js +47 -0
- package/dist/compiler/exports/latex/capture.d.ts +106 -0
- package/dist/compiler/exports/latex/capture.js +435 -0
- package/dist/compiler/exports/latex/compile.d.ts +73 -0
- package/dist/compiler/exports/latex/compile.js +115 -0
- package/dist/compiler/exports/latex/document.d.ts +102 -0
- package/dist/compiler/exports/latex/document.js +349 -0
- package/dist/compiler/exports/latex/explico.cls +415 -0
- package/dist/compiler/exports/latex/numbering.d.ts +18 -0
- package/dist/compiler/exports/latex/numbering.js +57 -0
- package/dist/compiler/exports/latex/serialize.d.ts +112 -0
- package/dist/compiler/exports/latex/serialize.js +606 -0
- package/dist/compiler/exports/latex-export.d.ts +44 -0
- package/dist/compiler/exports/latex-export.js +169 -0
- package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
- package/dist/compiler/exports/markdown/serialize.js +527 -0
- package/dist/compiler/exports/markdown-export.d.ts +39 -0
- package/dist/compiler/exports/markdown-export.js +240 -0
- package/dist/compiler/exports/standalone-html.d.ts +15 -5
- package/dist/compiler/exports/standalone-html.js +23 -51
- package/dist/compiler/exports/static-tree.d.ts +253 -0
- package/dist/compiler/exports/static-tree.js +625 -0
- package/dist/compiler/exports/zip.d.ts +16 -0
- package/dist/compiler/exports/zip.js +72 -0
- package/dist/compiler/lesson-derivation.d.ts +8 -2
- package/dist/compiler/lesson-derivation.js +6 -7
- package/dist/compiler/manifest.d.ts +115 -8
- package/dist/compiler/manifest.js +221 -39
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +3 -1
- package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
- package/dist/curriculum/validation.d.ts +9 -3
- package/dist/curriculum/validation.js +35 -17
- package/dist/reference/base-abbreviations.d.mts +2 -2
- package/dist/reference/base-abbreviations.mjs +2 -2
- package/dist/reference/base-operators.d.mts +8 -2
- package/dist/reference/base-operators.mjs +8 -10
- package/dist/reference/base-roman-labels.d.mts +4 -3
- package/dist/reference/base-roman-labels.mjs +4 -3
- package/dist/reference/citation-format.d.mts +35 -0
- package/dist/reference/citation-format.mjs +74 -0
- package/dist/reference/consistency.d.ts +5 -9
- package/dist/reference/consistency.js +34 -38
- package/dist/reference/curriculum-alignment.js +3 -24
- package/dist/reference/derived-entries.d.ts +45 -0
- package/dist/reference/derived-entries.js +50 -0
- package/dist/reference/derived-notation.d.mts +125 -0
- package/dist/reference/derived-notation.mjs +121 -0
- package/dist/reference/diagram-fit.d.ts +45 -0
- package/dist/reference/diagram-fit.js +54 -0
- package/dist/reference/diagram-layout.d.ts +59 -10
- package/dist/reference/diagram-layout.js +85 -24
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +1 -1
- package/dist/reference/equations-validate.js +26 -1
- package/dist/reference/equations.d.mts +27 -0
- package/dist/reference/equations.mjs +120 -22
- package/dist/reference/examples-validate.d.ts +1 -1
- package/dist/reference/figure-fit.d.ts +70 -0
- package/dist/reference/figure-fit.js +60 -0
- package/dist/reference/figure-pictures.d.ts +27 -0
- package/dist/reference/figure-pictures.js +49 -0
- package/dist/reference/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-abbreviations.js +1 -1
- package/dist/reference/gate-course-rules.d.ts +4 -3
- package/dist/reference/gate-course-rules.js +9 -4
- package/dist/reference/gate-heading-length.d.ts +2 -0
- package/dist/reference/gate-heading-length.js +7 -5
- package/dist/reference/gate-heading-structure.js +26 -0
- package/dist/reference/gate-indices.d.ts +35 -0
- package/dist/reference/gate-indices.js +299 -0
- package/dist/reference/gate-math.d.ts +32 -2
- package/dist/reference/gate-math.js +68 -12
- package/dist/reference/gate-mdx-expression.d.ts +17 -0
- package/dist/reference/gate-mdx-expression.js +29 -0
- package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +4 -2
- package/dist/reference/gate-part-pages.js +2 -1
- package/dist/reference/gate-prop-text.d.ts +28 -0
- package/dist/reference/gate-prop-text.js +106 -0
- package/dist/reference/gate-prose-notation.d.ts +3 -1
- package/dist/reference/gate-prose-notation.js +4 -2
- package/dist/reference/gate-retired-keys.d.ts +35 -0
- package/dist/reference/gate-retired-keys.js +58 -0
- package/dist/reference/gloss.d.ts +2 -0
- package/dist/reference/katex-measure.d.ts +17 -0
- package/dist/reference/katex-measure.js +223 -0
- package/dist/reference/keyed-float-validate.js +12 -0
- package/dist/reference/keyed-jsx-floats.d.mts +2 -2
- package/dist/reference/keyed-jsx-floats.mjs +11 -14
- package/dist/reference/lab-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/lint-ignore.js +1 -1
- package/dist/reference/math-glyphs.d.mts +94 -2
- package/dist/reference/math-glyphs.mjs +851 -46
- package/dist/reference/mixed-content.d.ts +8 -3
- package/dist/reference/mixed-content.js +4 -2
- package/dist/reference/page-notation.d.ts +39 -7
- package/dist/reference/page-notation.js +62 -10
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +115 -11
- package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
- package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
- package/dist/reference/rehype-heading-anchors.mjs +11 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-notation.mjs +275 -23
- package/dist/reference/render-lab-math.d.ts +13 -2
- package/dist/reference/render-lab-math.js +18 -6
- package/dist/reference/sections-validate.d.ts +28 -3
- package/dist/reference/sections-validate.js +31 -4
- package/dist/reference/sections.d.mts +24 -6
- package/dist/reference/sections.mjs +92 -17
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/tables-validate.js +24 -1
- package/dist/reference/text-measure.d.ts +10 -0
- package/dist/reference/text-measure.js +41 -0
- package/dist/reference/text-widths.d.ts +10 -0
- package/dist/reference/text-widths.js +1238 -0
- package/dist/reference/types.d.ts +64 -8
- package/dist/rules/catalog.js +223 -32
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +100 -27
- package/dist/rules/settings.d.ts +1 -1
- package/dist/rules/settings.js +7 -0
- package/dist/session/PreferenceStore.d.ts +18 -1
- package/dist/session/PreferenceStore.js +29 -1
- package/docs/authoring/README.md +33 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +115 -10
- package/docs/authoring/migrating.md +878 -0
- package/docs/authoring/notation.md +285 -30
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +84 -45
- package/docs/authoring/structure.md +67 -2
- package/docs/authoring/visualizations.md +167 -25
- package/package.json +12 -7
- package/src/compiler/astro.ts +72 -21
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +158 -27
- package/src/compiler/content-schema.ts +108 -0
- package/src/compiler/content-schemas.ts +391 -0
- package/src/compiler/course-config.ts +86 -15
- package/src/compiler/exports/combined-html.ts +669 -0
- package/src/compiler/exports/download-index.ts +36 -0
- package/src/compiler/exports/export-chapters.ts +233 -0
- package/src/compiler/exports/export-pages.ts +44 -0
- package/src/compiler/exports/html-tree.ts +62 -0
- package/src/compiler/exports/latex/capture.ts +651 -0
- package/src/compiler/exports/latex/compile.ts +168 -0
- package/src/compiler/exports/latex/document.ts +479 -0
- package/src/compiler/exports/latex/explico.cls +415 -0
- package/src/compiler/exports/latex/numbering.ts +74 -0
- package/src/compiler/exports/latex/serialize.ts +787 -0
- package/src/compiler/exports/latex/texlive-packages.txt +76 -0
- package/src/compiler/exports/latex-export.ts +255 -0
- package/src/compiler/exports/markdown/serialize.ts +632 -0
- package/src/compiler/exports/markdown-export.ts +329 -0
- package/src/compiler/exports/standalone-html.ts +39 -70
- package/src/compiler/exports/static-tree.ts +944 -0
- package/src/compiler/exports/zip.ts +88 -0
- package/src/compiler/lesson-derivation.ts +13 -8
- package/src/compiler/manifest.ts +387 -40
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/CourseContents.astro +6 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +18 -9
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
- package/src/components/assessments/AssessmentRunner.tsx +7 -1
- package/src/components/assessments/AssessmentSet.astro +14 -1
- package/src/components/block-heading.ts +38 -0
- package/src/components/card-arrow.ts +18 -4
- package/src/components/card-carry.ts +406 -0
- package/src/components/card-drag.ts +1 -30
- package/src/components/card-fetch.ts +87 -0
- package/src/components/card-manager.ts +1222 -38
- package/src/components/card-memory.ts +5 -1
- package/src/components/card-placement.ts +211 -13
- package/src/components/card-side.ts +281 -31
- package/src/components/citation/CitationLayer.astro +45 -40
- package/src/components/diagram/Diagram.astro +123 -44
- package/src/components/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +17 -1
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/figure/figure-state.ts +26 -17
- package/src/components/float-toolbar.ts +274 -0
- package/src/components/memo/Memo.astro +21 -10
- package/src/components/memo/ShortNote.astro +4 -0
- package/src/components/notation/NotationGlossary.astro +22 -17
- package/src/components/notation/NotationLayer.astro +300 -79
- package/src/components/notation/NotationMath.astro +8 -1
- package/src/components/notation/render-notation-math.ts +51 -5
- package/src/components/readable-text.ts +25 -0
- package/src/components/reference/FloatRefLayer.astro +162 -89
- package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
- package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
- package/src/components/starlight/LayoutPageSidebar.astro +17 -3
- package/src/components/starlight/LayoutSocialIcons.astro +79 -29
- package/src/components/starlight/LessonExportActions.astro +384 -310
- package/src/components/starlight/LessonFooter.astro +66 -8
- package/src/components/starlight/PrintController.astro +28 -0
- package/src/components/starlight/TextSizeControl.astro +137 -0
- package/src/components/starlight/print-controller.ts +380 -0
- package/src/components/starlight/section-summary.ts +13 -5
- package/src/components/table/Table.astro +14 -22
- package/src/components/table/table-truncation.ts +15 -0
- package/src/content-config.ts +20 -312
- package/src/curriculum/validation.ts +72 -24
- package/src/reference/base-abbreviations.mjs +2 -2
- package/src/reference/base-operators.mjs +8 -10
- package/src/reference/base-roman-labels.mjs +4 -3
- package/src/reference/citation-format.mjs +74 -0
- package/src/reference/consistency.ts +47 -61
- package/src/reference/curriculum-alignment.ts +4 -41
- package/src/reference/derived-entries.ts +68 -0
- package/src/reference/derived-notation.mjs +121 -0
- package/src/reference/diagram-fit.ts +104 -0
- package/src/reference/diagram-layout.ts +182 -51
- package/src/reference/diagrams-validate.ts +2 -1
- package/src/reference/equations-validate.ts +41 -2
- package/src/reference/equations.mjs +120 -22
- package/src/reference/examples-validate.ts +1 -0
- package/src/reference/figure-fit.ts +139 -0
- package/src/reference/figure-pictures.ts +96 -0
- package/src/reference/figure-symbols.ts +117 -0
- package/src/reference/figures-validate.ts +2 -1
- package/src/reference/gate-abbreviations.ts +1 -1
- package/src/reference/gate-course-rules.ts +22 -7
- package/src/reference/gate-heading-length.ts +25 -7
- package/src/reference/gate-heading-structure.ts +40 -1
- package/src/reference/gate-indices.ts +470 -0
- package/src/reference/gate-math.ts +102 -13
- package/src/reference/gate-mdx-expression.ts +45 -0
- package/src/reference/gate-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- package/src/reference/gate-prop-text.ts +145 -0
- package/src/reference/gate-prose-notation.ts +19 -5
- package/src/reference/gate-retired-keys.ts +101 -0
- package/src/reference/gloss.ts +2 -0
- package/src/reference/katex-measure.ts +384 -0
- package/src/reference/keyed-float-validate.ts +22 -0
- package/src/reference/keyed-jsx-floats.mjs +11 -14
- package/src/reference/lab-katex-validate.ts +156 -0
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +114 -14
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +170 -9
- package/src/reference/rehype-fail-katex-errors.mjs +19 -0
- package/src/reference/rehype-heading-anchors.mjs +11 -1
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-notation.mjs +275 -23
- package/src/reference/render-lab-math.ts +40 -5
- package/src/reference/sections-validate.ts +67 -3
- package/src/reference/sections.mjs +92 -17
- package/src/reference/tables-validate.ts +40 -2
- package/src/reference/text-measure.ts +63 -0
- package/src/reference/text-widths.ts +133 -0
- package/src/reference/types.ts +75 -8
- package/src/rules/catalog.ts +241 -31
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +322 -83
|
@@ -106,6 +106,13 @@ Part 2 Projectile motion
|
|
|
106
106
|
|
|
107
107
|
## Part pages
|
|
108
108
|
|
|
109
|
+
A part is listed in `course.settings.json` → `parts` as `{ "dir", "label" }`
|
|
110
|
+
and lives in `content/docs/<dir>/`. A part listed before anything is written
|
|
111
|
+
in it — no chapter and no overview page yet — is left out of the sidebar and
|
|
112
|
+
the numbering, with a `part-empty` warning, so the parts after it keep their
|
|
113
|
+
numbers until it has content. A lesson in a directory no part lists fails
|
|
114
|
+
`validate:content` (`lesson-outside-parts`).
|
|
115
|
+
|
|
109
116
|
A part is a directory, so `/<part>/` is a URL a reader will eventually type,
|
|
110
117
|
follow, or be handed by a search engine. Without a page it is a 404, and the
|
|
111
118
|
part exists only as a sidebar heading that cannot be linked to.
|
|
@@ -154,6 +161,14 @@ missing, holds more than one paragraph, or falls outside the course's
|
|
|
154
161
|
takes no float numbers, and never appears in `manifest.lessons` or a track. A
|
|
155
162
|
course adds one part at a time; a part without one behaves exactly as before.
|
|
156
163
|
|
|
164
|
+
**It explains its symbols, and links its floats**
|
|
165
|
+
([S10](structure-rules.md#s10)). A `[[key]]` term, math, a citation, an
|
|
166
|
+
abbreviation and a `[[chapter#tab-key]]` link each open their card on an
|
|
167
|
+
overview page, as on the landing page and any other page outside a part; the
|
|
168
|
+
manifest lists each such page under `pages` with its notation bundle. A
|
|
169
|
+
`<Table>`, `<Figure>`, `<Diagram>`, lab or `\label{eq:…}` stays in its
|
|
170
|
+
chapter, which alone can number it, and the overview links it.
|
|
171
|
+
|
|
157
172
|
`<PartOverview>` reads the manifest for everything it lists — chapters in
|
|
158
173
|
reading order with their descriptions, the competencies the part teaches, the
|
|
159
174
|
parts it builds on and leads to — so the page cannot fall out of date with the
|
|
@@ -301,7 +316,11 @@ punctuation dropped, so `## Two ways to name the same point` is
|
|
|
301
316
|
`-1`, `-2`. A reference to a heading without a number (deeper than `depth`, or
|
|
302
317
|
with `show: false`) prints the heading's title in quotes instead: a reader
|
|
303
318
|
could not find the number. A reference that names no top-level heading fails
|
|
304
|
-
`pnpm validate:content` (`sec-ref-resolves`) and the build.
|
|
319
|
+
`pnpm validate:content` (`sec-ref-resolves`) and the build. So does a heading
|
|
320
|
+
whose anchor the page already gives something else
|
|
321
|
+
(`heading-anchor-collision`): `## References` on a page that cites a source
|
|
322
|
+
(the References section is generated, at `#references`), or `## Tab totals`
|
|
323
|
+
beside `<Table id="totals">` (`#tab-totals`).
|
|
305
324
|
|
|
306
325
|
### Structure from the bottom up
|
|
307
326
|
|
|
@@ -339,6 +358,13 @@ worked-example set (`<CompactExamples>`) is numbered **`‹part›.‹chapter›
|
|
|
339
358
|
- **Keyed or not, everything counts.** An equation with no `\label{eq:…}` still
|
|
340
359
|
takes a number and a positional anchor (`#eq-2-1-4`); it just cannot be
|
|
341
360
|
referenced by name.
|
|
361
|
+
- **The lesson's flow counts, a callout does not.** An equation or a float
|
|
362
|
+
in a list item or a `>` quote counts like one at the top level, and so
|
|
363
|
+
does one in a `<Memo>`, `<ShortNote>` or `<Fold>`. One in a `:::note`
|
|
364
|
+
aside (or `<Aside>`, or a worked example) is illustrative and takes no
|
|
365
|
+
number; a `<Table>` or `<Figure>` there fails the build. A display
|
|
366
|
+
equation is a `$$` block whose fences sit on lines of their own:
|
|
367
|
+
`$$x$$` inside a sentence, or on one line, is inline math.
|
|
342
368
|
- **The number sits with the caption.** A figure or diagram prints it in the
|
|
343
369
|
caption line below its picture, a table in the caption line above, and a
|
|
344
370
|
display equation beside it. What names each
|
|
@@ -432,6 +458,12 @@ that wants them addressed should wrap the table in `<Table id="…">`/
|
|
|
432
458
|
`<Figure id="…">`, or give the diagram an `id` and move it to the top level (or
|
|
433
459
|
inside `<Memo>`/`<ShortNote>`), or wrap it in `<Figure>`.
|
|
434
460
|
|
|
461
|
+
A raw table counts only where a `<Table>` would number it: at the top level,
|
|
462
|
+
or in a `<Memo>`, `<ShortNote>` or `<Fold>`. One in a worked example
|
|
463
|
+
(`<CompactExample>`) or another component is illustrative, like an equation
|
|
464
|
+
there, and stays a plain table: a `<Table>` or a `<Figure>` there gets no
|
|
465
|
+
number, and the build fails on it, naming the component it sits in.
|
|
466
|
+
|
|
435
467
|
### Knowledge checks
|
|
436
468
|
|
|
437
469
|
An assessment title is authored once, in `content/assessments/<id>.json`, and
|
|
@@ -557,7 +589,9 @@ A block folds in one of two ways, and every default lives in one place, the
|
|
|
557
589
|
`sections`, `subsections` and `knowledgeChecks` take `{ collapsible,
|
|
558
590
|
defaultOpen }`; the others take `{ defaultOpen }`; `tables` takes
|
|
559
591
|
`{ truncateAfter }` — a table with more rows shows that many, half from each
|
|
560
|
-
end, and a "… N more rows …" line
|
|
592
|
+
end, and a "… N more rows …" line, unless that would hide a single row (the
|
|
593
|
+
line is as tall as the row, so the table shows whole). There is no
|
|
594
|
+
`disclosure.diagrams`: a
|
|
561
595
|
diagram does not fold. Every collapsible remembers the reader's own choice,
|
|
562
596
|
per page, over the default (`rememberState={false}` on a memo, a fold, a
|
|
563
597
|
figure's tools or an example set opts that one out). A short note's in-place
|
|
@@ -632,3 +666,34 @@ While collapsed, the surrounding sentence remains one paragraph and only the
|
|
|
632
666
|
point in the reading flow, and exposes a “Show less” control that restores the
|
|
633
667
|
original sentence. Short notes are for a locally optional explanation, not for
|
|
634
668
|
material that needs a number or citations elsewhere; use a memo for that.
|
|
669
|
+
|
|
670
|
+
## Retired keys
|
|
671
|
+
|
|
672
|
+
A key the engine has stopped reading fails the build wherever a content file
|
|
673
|
+
still sets it, so a course cannot keep maintaining a field that does nothing.
|
|
674
|
+
Each `retired-key` error names the file and the key's line; deleting the line
|
|
675
|
+
clears it.
|
|
676
|
+
|
|
677
|
+
| Key | Where it was set | Why it went |
|
|
678
|
+
| --- | --- | --- |
|
|
679
|
+
| `editorialStatus` | pages, notation entries, abbreviations, competencies, assessments, sources, tracks | Lessons are reviewed continuously, so no page ever earns a one-time "reviewed" stamp. The draft / in-review / reviewed labels it put on part pages, notation cards, glossary entries and citations are gone. |
|
|
680
|
+
|
|
681
|
+
Two of Starlight's own keys do not work in a course, because the manifest,
|
|
682
|
+
not Starlight, decides which chapters a course lists and in what order. A
|
|
683
|
+
lesson's `draft: true` fails the build as `unsupported-key`: Starlight would
|
|
684
|
+
leave the page out of a production build while the sidebar still names it.
|
|
685
|
+
Keep an unfinished lesson outside `content/docs/` until it is ready. A
|
|
686
|
+
chapter's `sidebar.order` warns as `ignored-key`: chapters follow the order of
|
|
687
|
+
the tracks (`content/tracks/*.json`).
|
|
688
|
+
|
|
689
|
+
## Content schemas
|
|
690
|
+
|
|
691
|
+
Every content file is checked against its collection's schema — the same
|
|
692
|
+
one `astro build` loads it with (`src/compiler/content-schemas.ts`) — by
|
|
693
|
+
`pnpm validate:content` and `pnpm content check`, so a value the build would
|
|
694
|
+
reject fails there first, at the key's line, as a `content-schema` error:
|
|
695
|
+
a notation `meaning` with `$` or `\`, a source `type` outside `primary`,
|
|
696
|
+
`official-guidance`, `paper`, `book` and `secondary`, or a `studyMinutes`
|
|
697
|
+
range whose low end exceeds its high end. A lesson is checked for the fields
|
|
698
|
+
explico adds (`teaches`, `assumptions`, `studyMinutes`, `notation`);
|
|
699
|
+
Starlight checks its own (`title`, `description`, …) when the site builds.
|
|
@@ -48,8 +48,8 @@ an independent diagram number, a `#dia-<id>` anchor, and references of the form
|
|
|
48
48
|
equation, table, or figure sequences.
|
|
49
49
|
|
|
50
50
|
A diagram is named like every float: a long `caption`, shown below the graph
|
|
51
|
-
in the same caption line a figure has (`Diagram 2.1.1`,
|
|
52
|
-
copies the diagram's link, the caption), and an optional `short` caption for
|
|
51
|
+
in the same caption line a figure has (`Diagram 2.1.1`, whose toolbar
|
|
52
|
+
copies the diagram's link, then the caption), and an optional `short` caption for
|
|
53
53
|
the `[[dia-…]]` preview card
|
|
54
54
|
([components.md § The caption pair](components.md#the-caption-pair)).
|
|
55
55
|
|
|
@@ -63,14 +63,16 @@ or Escape, restores its original width. A diagram never folds by itself: it
|
|
|
63
63
|
is its graph and then its caption line, in every state. To let a reader fold
|
|
64
64
|
it away, put it in a collapsible section.
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
Each node's box is sized from what it holds: its `text` measured in DejaVu
|
|
67
|
+
Sans, the widest common system font, and its `formula` measured from KaTeX's
|
|
68
|
+
own layout, so neither spills out of the box in any reader's font. A `text`
|
|
69
|
+
wider than about 190px wraps onto more lines and the box grows taller; a
|
|
70
|
+
`formula` never wraps, so a wide one widens its box and the text wraps to it.
|
|
71
|
+
Give a node a `width` to narrow it (its text wraps inside it and the height
|
|
72
|
+
follows) or a `height` to make it taller. A diagram that comes out wider than
|
|
73
|
+
the lesson column is [V8](#v8)'s concern.
|
|
72
74
|
|
|
73
|
-
_Enforcement._ Review-only
|
|
75
|
+
_Enforcement._ Review-only.
|
|
74
76
|
|
|
75
77
|
_Exceptions._ A one-off illustrative schematic that is not a graph (a labelled
|
|
76
78
|
photo, a geometric figure) is a [V2](#v2) figure, not a `Diagram`.
|
|
@@ -115,16 +117,23 @@ equations.**
|
|
|
115
117
|
An interactive figure is a React island, hydrated `client:visible` (or
|
|
116
118
|
`client:idle`). It carries:
|
|
117
119
|
|
|
118
|
-
- **Gate-checked math.**
|
|
120
|
+
- **Gate-checked math.** Every symbol the figure shows — an equation, a
|
|
121
|
+
legend entry, an axis title, a slider label — goes through the
|
|
119
122
|
`labMathScope` / `renderLabMath` / `\slot{}` seam so its symbols are resolved
|
|
120
123
|
against the page's real notation at build time and the island only patches
|
|
121
|
-
numbers into pre-rendered KaTeX.
|
|
122
|
-
|
|
124
|
+
numbers into pre-rendered KaTeX. `renderLabMath(template, scope)` renders a
|
|
125
|
+
display equation; `renderLabMath(template, scope, { display: false })`
|
|
126
|
+
renders inline math for a formula that sits in a line, which never scrolls.
|
|
127
|
+
A lab never calls KaTeX itself: its symbols would open no card. A wrapper
|
|
128
|
+
whose template names notation not otherwise present in lesson MDX passes
|
|
123
129
|
`{ locals: Astro.locals, context: '<ComponentName> <template>' }` as the third
|
|
124
130
|
`labMathScope` argument; that is the declaration that ships those definitions
|
|
125
131
|
in this page's popup payload. See
|
|
126
132
|
`playground/content/labs/ProjectileRangeExplorer.tsx` +
|
|
127
|
-
`ProjectileRangeMath.astro`.
|
|
133
|
+
`ProjectileRangeMath.astro`. Pass the table `labMathScope` returned to
|
|
134
|
+
`renderLabMath` unchanged: the page's index entries reach the lab through
|
|
135
|
+
it, so `\mathbf{v}_0` gets its derived card ([N23](notation.md#n23)), and a copy
|
|
136
|
+
(`[...scope]`) gets neither the cards nor the index check.
|
|
128
137
|
- **Labelled controls.** Every control is a labelled `<input>` — the visible
|
|
129
138
|
label is how any reader knows what a slider sets; where precision
|
|
130
139
|
matters, pair a `range` slider with a `number` field (see `RangeField` in
|
|
@@ -136,8 +145,7 @@ An interactive figure is a React island, hydrated `client:visible` (or
|
|
|
136
145
|
that describes the plot ("the dot marks the landing point") belongs in the
|
|
137
146
|
figure's caption.
|
|
138
147
|
- **Picture, then caption, then tools.** A lab is laid out as a LaTeX figure
|
|
139
|
-
with its controls after it
|
|
140
|
-
decision 3). The plot goes in the `<Figure>`'s default slot — its
|
|
148
|
+
with its controls after it. The plot goes in the `<Figure>`'s default slot — its
|
|
141
149
|
picture — and the controls in its `tools` slot: an instruction line, the
|
|
142
150
|
sliders and fields, the live equation. The two are separate islands that
|
|
143
151
|
share their values. The tools render after the caption line, in a tray of
|
|
@@ -151,20 +159,32 @@ An interactive figure is a React island, hydrated `client:visible` (or
|
|
|
151
159
|
case shown — "Shown for v₀ = 20 m/s and θ = 30°: range 35.3 m, time of
|
|
152
160
|
flight 2.04 s." The lab writes it with `setFigureState(element, runs)`
|
|
153
161
|
(`explico/components/figure/figure-state.ts`), `element` being any element
|
|
154
|
-
of its own inside the figure and
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
of its own inside the figure and each run a string of plain text or
|
|
163
|
+
`{ math }`, a symbol rendered at build time with
|
|
164
|
+
`renderLabMath(template, scope, { display: false })` and handed to the
|
|
165
|
+
island — never HTML, and never a symbol typed as text, which would open no
|
|
166
|
+
card. `ProjectileRangePicture.astro` renders the playground's. The clause
|
|
167
|
+
prints with the caption; a reader of the long caption elsewhere (a
|
|
168
|
+
preview card, a fold's bar) sees the caption alone.
|
|
158
169
|
- **Which text goes where.** The caption names the figure and says what it
|
|
159
170
|
shows — its subject, its marks and symbols, the case shown — and may state
|
|
160
171
|
the one thing to notice. The argument drawn from the figure is lesson
|
|
161
172
|
text, after the figure. Instructions for the controls belong to the
|
|
162
173
|
controls: the tools' first line, not the caption.
|
|
163
|
-
- **
|
|
164
|
-
|
|
165
|
-
plot
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
- **The build draws the default picture.** The island draws its plot in
|
|
175
|
+
the browser, after the page loads. So the lab's Astro wrapper draws the
|
|
176
|
+
same plot at the island's default values at build time and hands it to
|
|
177
|
+
the island as its children, which the island shows until it has drawn
|
|
178
|
+
its own. The built page then has the picture before hydration, with
|
|
179
|
+
JavaScript off, in print and in the standalone HTML export. With
|
|
180
|
+
Observable Plot that is one options function the island and the wrapper
|
|
181
|
+
share, and `Plot.plot({ ...options, document })` on a server-side
|
|
182
|
+
document in the wrapper; see `playground/content/labs/still-plot.ts`,
|
|
183
|
+
`LaunchAnglePicture.astro` and `LaunchAngleExplorer.tsx`.
|
|
184
|
+
- **A preview shows the plot.** A `[[fig-…]]` reference to a lab previews a
|
|
185
|
+
still copy of its plot, from this page or another: as drawn once the
|
|
186
|
+
island has run, else the build's default picture. The tools are never
|
|
187
|
+
copied, so a lab needs no `previewable={false}`.
|
|
168
188
|
|
|
169
189
|
The figure itself never hides; a lab that should fold away whole, with the
|
|
170
190
|
prose around it, goes in a `<Fold>` or a collapsible section.
|
|
@@ -184,7 +204,22 @@ _How to apply._ Keep the island's job to "patch numbers and redraw". Domain
|
|
|
184
204
|
math lives in a plain module (`playground/content/domain/motion.ts`), not in the
|
|
185
205
|
component.
|
|
186
206
|
|
|
187
|
-
_Enforcement._
|
|
207
|
+
_Enforcement._ Partly build-enforced. `pnpm validate:content` follows each
|
|
208
|
+
lesson's relative imports to the course's components and fails one that
|
|
209
|
+
imports `katex` itself (`lab-katex-validate.ts`, `lab-katex-direct`, an
|
|
210
|
+
error), naming the chain of imports that reaches it. `astro build` then reads
|
|
211
|
+
every figure it rendered and fails a symbol KaTeX drew outside a notation
|
|
212
|
+
binding (`figure-symbols.ts`, rule [N4](notation.md#n4)); a course's browser
|
|
213
|
+
test runs the same `unboundFigureSymbols` on a hydrated figure's
|
|
214
|
+
`outerHTML`, which also holds what an island draws only in the browser, as
|
|
215
|
+
the playground's `labs.spec.ts` does. The same build warns about a figure
|
|
216
|
+
whose picture is an island with nothing drawn in the built page
|
|
217
|
+
(`figure-pictures.ts`, `undrawnFigurePictures`, `lab-picture-undrawn`, a
|
|
218
|
+
warning). `renderLabMath` and a `<Diagram>` formula fail `astro build` on
|
|
219
|
+
an index no card explains (rule [N23](notation.md#n23)), as they render.
|
|
220
|
+
`setFigureState` takes no symbol typed as text, so `astro check` fails an
|
|
221
|
+
italic or a `sub` run. That a lab labels its controls and names
|
|
222
|
+
nothing of its own is review-only.
|
|
188
223
|
|
|
189
224
|
_Exceptions._ None.
|
|
190
225
|
|
|
@@ -348,3 +383,110 @@ _Exceptions._
|
|
|
348
383
|
are only two.
|
|
349
384
|
- A sketch that draws axes is a chart. If the axes are worth drawing, their
|
|
350
385
|
scale is worth labelling.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## V7
|
|
390
|
+
|
|
391
|
+
**A figure’s picture takes at most `maxPercent` of the screen height.**
|
|
392
|
+
|
|
393
|
+
The picture is the default slot of a `<Figure>`: the plot, its key and any
|
|
394
|
+
data tables, without the caption line or the tools. On the screens the course
|
|
395
|
+
targets it stays within `maxPercent` (80% unless the course sets
|
|
396
|
+
`rules.V7.maxPercent`) of the viewport height. Then a reader who scrolls to the
|
|
397
|
+
figure sees the whole picture and its caption line at once, with a line of the
|
|
398
|
+
text that points to it.
|
|
399
|
+
|
|
400
|
+
- **A square or tall plot gets a width, not the column.** A chart drawn with
|
|
401
|
+
`aspectRatio: 1` (Observable Plot) or a height derived from the container
|
|
402
|
+
width grows with the column: a square in a 760px column is 760px tall, almost
|
|
403
|
+
all of a laptop screen. Give such a plot a fixed width (`width: 460`,
|
|
404
|
+
`aspectRatio: 1`) and centre it, or cap it with
|
|
405
|
+
`Math.min(containerWidth, 460)`.
|
|
406
|
+
- **Stacked panels share the budget.** Two panels of 300px each are one 600px
|
|
407
|
+
picture. Shorten both, put them side by side on a wide screen, or keep the
|
|
408
|
+
secondary one in the tools.
|
|
409
|
+
- **Measure, don't estimate.** Font size, the column width and a lab's
|
|
410
|
+
hydrated layout all change the height, so judge it from the rendered page.
|
|
411
|
+
|
|
412
|
+
_Rationale._ A figure is read with its caption and with the sentence that
|
|
413
|
+
sends the reader to it. A picture taller than the screen has to be scrolled
|
|
414
|
+
past to reach its caption, and a control in the tools that redraws it can no
|
|
415
|
+
longer be watched while it is moved.
|
|
416
|
+
|
|
417
|
+
_How to apply._ In the course’s Playwright suite, open every lesson that has
|
|
418
|
+
a figure, at each viewport the course supports, and expect no findings:
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
import { readFileSync } from 'node:fs';
|
|
422
|
+
import { expect, test } from '@playwright/test';
|
|
423
|
+
import { checkFigureFit, lessonsWithFigures } from 'explico/reference/figure-fit';
|
|
424
|
+
|
|
425
|
+
const manifest = JSON.parse(readFileSync('build/manifest.json', 'utf8'));
|
|
426
|
+
|
|
427
|
+
for (const path of lessonsWithFigures(manifest)) {
|
|
428
|
+
test(`${path} figures fit on one screen (rule V7)`, async ({ page }) => {
|
|
429
|
+
await page.setViewportSize({ width: 1280, height: 800 });
|
|
430
|
+
await page.goto(path);
|
|
431
|
+
const findings = await checkFigureFit(page);
|
|
432
|
+
expect(findings.map((finding) => finding.message)).toEqual([]);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Pass `{ maxPercent }` when the course sets `rules.V7.maxPercent`. The check
|
|
438
|
+
opens a figure inside a closed `<Fold>` before it measures, because a reader
|
|
439
|
+
can open it, and waits for each figure’s islands to hydrate and its height to
|
|
440
|
+
settle.
|
|
441
|
+
|
|
442
|
+
_Enforcement._ Partial. `checkFigureFit` in `src/reference/figure-fit.ts`
|
|
443
|
+
reports `figure-too-tall` for each picture over the budget, on the pages and
|
|
444
|
+
viewports a course’s browser tests open; the build cannot measure a laid-out
|
|
445
|
+
page.
|
|
446
|
+
|
|
447
|
+
_Exceptions._ A figure whose picture is itself the subject at full size — a
|
|
448
|
+
long table of values, a tall timeline the reader is meant to scroll — may be
|
|
449
|
+
exempted in the test with a stated reason,
|
|
450
|
+
`checkFigureFit(page, { allow: { 'fig-timeline': 'read by scrolling, one row per year' } })`,
|
|
451
|
+
or kept in a `<Fold>` with a
|
|
452
|
+
`<Table>` instead of a figure.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## V8
|
|
457
|
+
|
|
458
|
+
**A diagram is no wider than the lesson column.**
|
|
459
|
+
|
|
460
|
+
A `<Diagram>` lays out to at most `columnWidth` px (707 unless the course sets
|
|
461
|
+
`rules.V8.columnWidth`): the lesson column at a 1366px-wide window, the most
|
|
462
|
+
common laptop screen (the column is 632px at 1280 and 832px at 1920). Then a
|
|
463
|
+
reader sees the whole graph where the text sends them to it, without scrolling
|
|
464
|
+
it sideways or pressing its widen button ↔.
|
|
465
|
+
|
|
466
|
+
The engine does the first part itself. A diagram that comes out wider than
|
|
467
|
+
its limit is laid out again with its labels wrapped narrower, down to 120px
|
|
468
|
+
boxes, and only one that still does not fit is reported. Then:
|
|
469
|
+
|
|
470
|
+
- **Turn a long chain.** A left-to-right chain of five or more nodes rarely
|
|
471
|
+
fits; `direction="TB"` stacks it.
|
|
472
|
+
- **Shorten a label or a formula.** A node names a step; the explanation goes
|
|
473
|
+
in the prose or the caption. A formula never wraps, so a long one sets its
|
|
474
|
+
box's width.
|
|
475
|
+
- **Narrow a node.** A node's `width` makes its text wrap inside it, and its
|
|
476
|
+
height follows.
|
|
477
|
+
|
|
478
|
+
_Rationale._ A diagram is a picture of how steps connect. One that scrolls
|
|
479
|
+
shows only some of them at a time, and the reader has to remember the rest.
|
|
480
|
+
|
|
481
|
+
_How to apply._ Build the course. Each diagram wider than its limit is a
|
|
482
|
+
`dia-too-wide` warning naming the lesson file and the diagram `id`, printed by
|
|
483
|
+
`astro build` (and by `astro dev` as the page renders) and listed by
|
|
484
|
+
`pnpm content warnings` until the next build. A diagram meant to be read by
|
|
485
|
+
scrolling — a long timeline — says `warnAboveWidth={false}`; a number in
|
|
486
|
+
`warnAboveWidth` sets that one diagram's limit instead of the column.
|
|
487
|
+
|
|
488
|
+
_Enforcement._ Tier-3 warning `dia-too-wide`, for every rendered diagram:
|
|
489
|
+
`Diagram.astro` marks one wider than its limit, `oversizedDiagrams` in
|
|
490
|
+
`src/reference/diagram-fit.ts` finds the mark on each page `astro build`
|
|
491
|
+
writes, and `build/render-diagnostics.json` keeps the findings for
|
|
492
|
+
`content warnings`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explico",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -36,13 +36,17 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@dagrejs/dagre": "3.1.1",
|
|
38
38
|
"esbuild": "0.27.7",
|
|
39
|
-
"
|
|
39
|
+
"mdast-util-gfm-table": "2.0.0",
|
|
40
|
+
"mdast-util-math": "3.0.0",
|
|
41
|
+
"mdast-util-to-markdown": "2.1.2",
|
|
42
|
+
"parse5": "7.3.0",
|
|
43
|
+
"remark-directive": "^4.0.0"
|
|
40
44
|
},
|
|
41
45
|
"peerDependencies": {
|
|
42
|
-
"@astrojs/markdown-remark": "7.
|
|
43
|
-
"@astrojs/react": "
|
|
44
|
-
"@astrojs/starlight": "0.
|
|
45
|
-
"astro": "7.
|
|
46
|
+
"@astrojs/markdown-remark": "7.3.1",
|
|
47
|
+
"@astrojs/react": "7.0.0",
|
|
48
|
+
"@astrojs/starlight": "0.42.4",
|
|
49
|
+
"astro": "7.3.5",
|
|
46
50
|
"katex": "0.16.47",
|
|
47
51
|
"react": "19.2.8",
|
|
48
52
|
"react-dom": "19.2.8",
|
|
@@ -51,7 +55,8 @@
|
|
|
51
55
|
"remark-mdx": "3.1.1"
|
|
52
56
|
},
|
|
53
57
|
"devDependencies": {
|
|
54
|
-
"@astrojs/markdown-remark": "7.
|
|
58
|
+
"@astrojs/markdown-remark": "7.3.1",
|
|
59
|
+
"@types/mdast": "4.0.4",
|
|
55
60
|
"@types/node": "24.13.3",
|
|
56
61
|
"@types/react": "19.2.18",
|
|
57
62
|
"@types/react-dom": "19.2.5",
|
package/src/compiler/astro.ts
CHANGED
|
@@ -12,7 +12,20 @@ import {
|
|
|
12
12
|
writeManifest,
|
|
13
13
|
} from './manifest';
|
|
14
14
|
import { missingNotationPayloadKeys } from '../reference/page-notation';
|
|
15
|
+
import { assertFigureSymbolsBound } from '../reference/figure-symbols';
|
|
16
|
+
import { undrawnFigurePictures, undrawnFigureWarning } from '../reference/figure-pictures';
|
|
17
|
+
import {
|
|
18
|
+
pageRenderDiagnostics,
|
|
19
|
+
type RenderDiagnostic,
|
|
20
|
+
writeRenderDiagnostics,
|
|
21
|
+
} from './render-diagnostics';
|
|
15
22
|
import { writeStandaloneLessonExports } from './exports/standalone-html';
|
|
23
|
+
import { writeCombinedExports } from './exports/combined-html';
|
|
24
|
+
import { writeLatexExports } from './exports/latex-export';
|
|
25
|
+
import { writeMarkdownExports } from './exports/markdown-export';
|
|
26
|
+
import { prepareChapters } from './exports/export-chapters';
|
|
27
|
+
import { writeDownloadIndex } from './exports/download-index';
|
|
28
|
+
import { standaloneFilename, standalonePageSlugs } from './exports/export-pages';
|
|
16
29
|
import remarkAbbreviations from '../reference/remark-abbreviations.mjs';
|
|
17
30
|
import remarkCitation from '../reference/remark-citation.mjs';
|
|
18
31
|
import remarkNotation from '../reference/remark-notation.mjs';
|
|
@@ -26,21 +39,21 @@ function normalizedBasePath(base: string): string {
|
|
|
26
39
|
return segment ? `/${segment}/` : '/';
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
/** Match only a configured
|
|
42
|
+
/** Match only a configured page artifact below Astro's deployment base. */
|
|
30
43
|
export function matchStandaloneDownload(
|
|
31
44
|
pathname: string,
|
|
32
45
|
base: string,
|
|
33
|
-
|
|
46
|
+
pageSlugs: readonly string[],
|
|
34
47
|
): string | undefined {
|
|
35
48
|
const prefix = `${normalizedBasePath(base)}downloads/`;
|
|
36
49
|
if (!pathname.startsWith(prefix) || !pathname.endsWith('.html')) return undefined;
|
|
37
50
|
const slug = pathname.slice(prefix.length, -'.html'.length);
|
|
38
|
-
return
|
|
51
|
+
return pageSlugs.includes(slug) ? slug : undefined;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
async function buildDevelopmentDownloads(
|
|
42
55
|
config: Pick<AstroConfig, 'root'>,
|
|
43
|
-
|
|
56
|
+
pageSlugs: readonly string[],
|
|
44
57
|
): Promise<ReadonlyMap<string, Buffer>> {
|
|
45
58
|
const temporaryRoot = await mkdtemp(join(tmpdir(), 'explico-standalone-'));
|
|
46
59
|
const outputRoot = join(temporaryRoot, 'dist');
|
|
@@ -88,7 +101,7 @@ async function buildDevelopmentDownloads(
|
|
|
88
101
|
});
|
|
89
102
|
return new Map(
|
|
90
103
|
await Promise.all(
|
|
91
|
-
|
|
104
|
+
pageSlugs.map(async (slug) => [
|
|
92
105
|
slug,
|
|
93
106
|
await readFile(join(outputRoot, 'downloads', `${slug}.html`)),
|
|
94
107
|
] as const),
|
|
@@ -99,7 +112,12 @@ async function buildDevelopmentDownloads(
|
|
|
99
112
|
}
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Fail loudly instead of rendering math triggers whose local cards are
|
|
117
|
+
* missing. The manifest bundles every `notation.local` entry with its page,
|
|
118
|
+
* so the two lists differ only when the manifest is older than the page, or
|
|
119
|
+
* when `validate:content` rejected an entry.
|
|
120
|
+
*/
|
|
103
121
|
export function assertLocalNotationParity(
|
|
104
122
|
lessonId: string,
|
|
105
123
|
authoredKeys: readonly string[],
|
|
@@ -114,10 +132,13 @@ export function assertLocalNotationParity(
|
|
|
114
132
|
return;
|
|
115
133
|
}
|
|
116
134
|
throw new Error(
|
|
117
|
-
`
|
|
118
|
-
`[${authored.join(', ')}], but the
|
|
119
|
-
`[${rendered.join(', ')}].
|
|
120
|
-
`
|
|
135
|
+
`Explico notation mismatch for ${lessonId}: the page's frontmatter ` +
|
|
136
|
+
`notation.local declares [${authored.join(', ')}], but the manifest's ` +
|
|
137
|
+
`notation bundle for the page holds [${rendered.join(', ')}]. The ` +
|
|
138
|
+
`manifest bundles every notation.local entry with its page, so either ` +
|
|
139
|
+
`it is stale — pass courseConfig to loadManifest() and add ` +
|
|
140
|
+
`explicoContentRefresh() to the Astro integrations — or ` +
|
|
141
|
+
`\`pnpm validate:content\` rejected an entry: fix the errors it reports.`,
|
|
121
142
|
);
|
|
122
143
|
}
|
|
123
144
|
|
|
@@ -220,19 +241,35 @@ export function assertRemarkPluginsWired(
|
|
|
220
241
|
);
|
|
221
242
|
}
|
|
222
243
|
|
|
223
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Check every built page, and return the findings only a rendered page gives
|
|
246
|
+
* (`render-diagnostics.ts`) for `content warnings`.
|
|
247
|
+
*/
|
|
248
|
+
async function validateGeneratedPages(
|
|
249
|
+
directory: URL,
|
|
250
|
+
manifest: Manifest,
|
|
251
|
+
warn: (message: string) => void,
|
|
252
|
+
): Promise<RenderDiagnostic[]> {
|
|
224
253
|
const root = fileURLToPath(directory);
|
|
254
|
+
const rendered: RenderDiagnostic[] = [];
|
|
225
255
|
const walk = async (path: string): Promise<void> => {
|
|
226
256
|
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
227
257
|
const child = join(path, entry.name);
|
|
228
258
|
if (entry.isDirectory()) {
|
|
229
259
|
await walk(child);
|
|
230
260
|
} else if (entry.isFile() && entry.name.endsWith('.html')) {
|
|
231
|
-
|
|
261
|
+
const html = await readFile(child, 'utf8');
|
|
262
|
+
const page = relative(root, child);
|
|
263
|
+
assertNotationPayloadHtml(page, html);
|
|
264
|
+
assertFigureSymbolsBound(page, html);
|
|
265
|
+
const undrawn = undrawnFigurePictures(html);
|
|
266
|
+
if (undrawn.length > 0) warn(undrawnFigureWarning(page, undrawn));
|
|
267
|
+
rendered.push(...pageRenderDiagnostics(manifest, page, html));
|
|
232
268
|
}
|
|
233
269
|
}
|
|
234
270
|
};
|
|
235
271
|
await walk(root);
|
|
272
|
+
return rendered;
|
|
236
273
|
}
|
|
237
274
|
|
|
238
275
|
function replaceInPlace(target: unknown, source: unknown): void {
|
|
@@ -370,19 +407,19 @@ export function explicoContentRefresh(
|
|
|
370
407
|
const slug = matchStandaloneDownload(
|
|
371
408
|
pathname,
|
|
372
409
|
configuredDevelopment.base,
|
|
373
|
-
manifest
|
|
410
|
+
standalonePageSlugs(manifest),
|
|
374
411
|
);
|
|
375
412
|
if (!slug) return next();
|
|
376
413
|
|
|
377
414
|
try {
|
|
378
415
|
logger.info(
|
|
379
416
|
developmentDownloads
|
|
380
|
-
? `Serving cached standalone
|
|
381
|
-
: 'Building standalone
|
|
417
|
+
? `Serving cached standalone page ${slug}.html`
|
|
418
|
+
: 'Building standalone page downloads for local development…',
|
|
382
419
|
);
|
|
383
420
|
developmentDownloads ??= buildDevelopmentDownloads(
|
|
384
421
|
configuredDevelopment,
|
|
385
|
-
manifest
|
|
422
|
+
standalonePageSlugs(manifest),
|
|
386
423
|
);
|
|
387
424
|
const currentDownloads = developmentDownloads;
|
|
388
425
|
currentDownloads.catch(() => {
|
|
@@ -392,7 +429,7 @@ export function explicoContentRefresh(
|
|
|
392
429
|
});
|
|
393
430
|
const artifact = (await currentDownloads).get(slug);
|
|
394
431
|
if (!artifact) return next();
|
|
395
|
-
const filename =
|
|
432
|
+
const filename = standaloneFilename(slug);
|
|
396
433
|
response.statusCode = 200;
|
|
397
434
|
response.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
398
435
|
response.setHeader('Content-Length', artifact.byteLength);
|
|
@@ -404,17 +441,31 @@ export function explicoContentRefresh(
|
|
|
404
441
|
response.end(request.method === 'HEAD' ? undefined : artifact);
|
|
405
442
|
} catch (error) {
|
|
406
443
|
const message = error instanceof Error ? error.message : String(error);
|
|
407
|
-
logger.error(`Could not build standalone
|
|
444
|
+
logger.error(`Could not build standalone page downloads: ${message}`);
|
|
408
445
|
response.statusCode = 500;
|
|
409
446
|
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
410
|
-
response.end(`Could not build standalone
|
|
447
|
+
response.end(`Could not build standalone page download.\n${message}\n`);
|
|
411
448
|
}
|
|
412
449
|
});
|
|
413
450
|
}
|
|
414
451
|
},
|
|
415
|
-
'astro:build:done': async ({ dir }) => {
|
|
416
|
-
await
|
|
452
|
+
'astro:build:done': async ({ dir, logger }) => {
|
|
453
|
+
const rendered = await validateGeneratedPages(dir, manifest, (message) =>
|
|
454
|
+
logger.warn(message),
|
|
455
|
+
);
|
|
456
|
+
for (const diagnostic of writeRenderDiagnostics(rendered)) {
|
|
457
|
+
logger.warn(`[${diagnostic.code}] ${diagnostic.file}: ${diagnostic.message}`);
|
|
458
|
+
}
|
|
417
459
|
await writeStandaloneLessonExports(dir, manifest);
|
|
460
|
+
await writeCombinedExports(dir, manifest);
|
|
461
|
+
// The print exports share one reading of the chapters and one
|
|
462
|
+
// capture of their figures.
|
|
463
|
+
const { latex, markdown } = manifest.lessonExports;
|
|
464
|
+
const prepared =
|
|
465
|
+
latex || markdown ? await prepareChapters(fileURLToPath(dir), manifest, { logger }) : undefined;
|
|
466
|
+
await writeLatexExports(dir, manifest, { logger, ...(prepared ? { prepared } : {}) });
|
|
467
|
+
await writeMarkdownExports(dir, manifest, { logger, ...(prepared ? { prepared } : {}) });
|
|
468
|
+
await writeDownloadIndex(dir);
|
|
418
469
|
},
|
|
419
470
|
},
|
|
420
471
|
};
|